How Symfony and Doctrine fetches associations and related entities - symfony

I cracked my head trying to realize how Symfony and Doctrine fetch associated entities.
Lets imagine: i have several associated entities:
Company (ManyToOne to City)
City (ManyToOne to Region, OneToMany to Company)
Region (ManyToOne to Country, OneToMany to City)
Country (OneToMany to Region)
When i render Company form i create Form Event Listener (on PRE_SET_DATA) that inserts Region and Country selectboxes to this form.
The values in these fields must be set according to associated Region.
I retrieve current Company Region via:
$company = $event->getData();
$city = $company->getCity()
That works good
But when i try this:
$region = $city->getRegion(); // returns NULL
$country = $region->getCountry(); // returns NULL
these methods returns NULL. But in fact all associations exists and Doctrine association mapping is correct. Why?
According to Doctrine documentation: when i call getter for the proxy object (these ovjects are proxies, right?) - Doctrine should fetch insufficient data from database and update the object.
In fact - methods return NULLs.
How can i get any associated entity? (from any association deep level)

I just needed to remove cache dir...

you should try to get it from your controller instead of the Form Class
$em = $this->getDoctrine()->getManager();
$em->initializeObject($obj);//this will initialize the object you need and fetch the real one from the database and not the proxy class Doctrine returns.
It's returning NULL because it has too many levels company->(1)city->(2)region and the doctrine get lost with the proxy class. if this doesn't work, try to make a DLQ query when you have the city.
if you want to understand how a proxy class looks like dump($company); and you will see that it only show the id of city.

Related

Can entity tracking be turned off?

I've got multiple entities set up with their respective repositories, all working properly. However, Doctrine seems to populate proxies where I don't want them to be populated.
I've got an entity called Item, which references a Category, both by having a $category_id and a $category field. The latter has the relationship set up with #ORM\ManyToOne() and #ORM\JoinColumn(), working correctly.
In my controller, when I'm querying Items, I receive a list of items with proxies to the related categories, which I can strip out from my response, identifying them as being proxies. However, if for whatever reason, I also query Categories in an unrelated query, the item-related query return with not proxies but actual hydrated Category instances, which I don't want.
$this->categoryRepository->findBy(...);
...
$items = $this->itemRepository->findBy(...);
return $this->respond($items);
Here, $items[0]->category will have been populated by the framework by the time the execution reaches the return statement.
Is it possible to turn this behaviour off?
You can specify the fetch policy on a relationship to be EXTRA_LAZY, this will fetch the least amount of date on execution as possible. Per the docs:
With Doctrine 2.1 a feature called Extra Lazy is introduced for associations. Associations are marked as Lazy by default, which means the whole collection object for an association is populated the first time its accessed.
So in your #ORM\ManyToOne() annotation, add a parameter: fetch="EXTRA_LAZY" to the others.

Doctrine2 ORM Ignore relations in Merge

I'm implementing a REST Api with FOSRestBundle and I have encountered a problem with the modification of existing entity (PUT)
I have a class Student with has a ManyToOne relation
/**
* #JMS\MaxDepth(2)
* #ORM\ManyToOne(targetEntity="ClassRoom", inversedBy="students")
* #ORM\JoinColumn(name="classroom_id", referencedColumnName="id")
*/
protected $classRoom;
When performing a PUT action I only receive the value attributes since i do not want to let the user modify the relations via a put request.
This is a received data example.
{
"id": 3,
"name": "pelayo",
"second_name": "ramon",
"last_name": "fernandez",
"birthday": "1983-08-15T00:00:00+0200"
}
Data gets deserialized with JMS serializer wich sets the $classRoom attribute to null since it didn't find it in the received data.
When performing a merge
$student2 = $this->get('doctrine')->getManager()->merge($student);
If the student2 gets persisted the current relation with classRoom gets erased from the database since the merge sets the relation to null.
This behavior can be dodged by retrieving the current classRoom and setting it to the deserialized entity by hand before the merge, but this is ugly.
Is there any way to tell doctrine to ignore an attribute in a merge from the detached one and make it always use the stored value?
Merge is not the only solution.
The JMSSerializerBundle includes an object constructor for Doctrine entities. When you enable this constructor, the deserialized entities are managed entities that can be persisted(with $em->persist($student)). The only attributes modified on the deserialized entity are the ones mentioned in the JSON from the request.
Here is how you can enable it.

Embedded form persistence : get or create

I work with Symfony2 and doctrine. I currently have an entity called Person. This entity is related to some other entities as a One-To-Many relation (as a Many-To-One unidirectional relation). I want each Person entity to be unique (what I have done in my database by using the UniqueConstraint annotation).
To be clear, I will assume that I have two entities called Home and Car which both have a Many-to-One relation to the target entity Person.
Then I am using forms to create or edit my entities Car and Home. In these forms, I display a embedded form to create a Person entity or select one existing. I explain : the first field of my embedded form is the name of the person. As the user type the name of the person, a list of the existing persons is displayed (using JQuery Autocomplete UI) and if the user select one of them the other fields are autocompleted.
The issue is when the user submit the form with an existing person, I caught an Integrity Error (and I know why, because of my Unique Constraint).
One of the first workaround is to add the id field as an hidden input in the embedded form.
But then the user can edit the other fields and corrupt the current entity.
So no.
Another one could be to prevent the persist in the controller if the Person already exist, but as I am using this in many other entities. I will have to duplicate my code, and I do not want to, as the unique constraint is related to the Person entity and not to the Car or Home entity.
So no again.
The workaround that I am working about is to use a PrePersist Listener waiting for a Person entity, but I do not know how to cancel persist (and maybe it is not possible).
I have the following code :
public function prePersist(LifecycleEventArgs $args) {
$entity = $args->getEntity();
if($entity instanceof Personne) {
$em = $args->getEntityManager();
$persons = $em->getRepository('MyBundle:Person')->findBy(array(
// unique fields
));
if(count($persons) > 0) {
// ... ???
}
}
I have tried a $em->detach but it is useless as I am already persisting the entity.
What I want it is just a kind of a "Get Or Create". I explain, there are only two cases :
the Person entity (not persisted) has all the same fields that one existing in the database (excepted the id field), so the Person entity is the one in the database. I have to "replace" it by the one in the database ;
the Person entity (not persisted) is unique in the database, so we create a new one (persist).
Create your own getOrCreate() method and call it inside your listener.
See this post Symfony2 doctrine FindOneOrCreate
Another possibility would be the data transformers. http://symfony.com/doc/current/cookbook/form/data_transformers.html

Updating entity with mappings in Doctrine2

I've been doing straight MySQL for a long time and am now trying to do things the ORM way. I have a site where users create designs. The Design table has a field "userId" that maps to User.id. The Design entity has a value, $user, that defines the relationship between entities:
/**
* #ORM\ManyToOne(targetEntity="User")
* #ORM\JoinColumn(name="userId", referencedColumnName="id")
*/
private $user;
It's nice, I can now load a Design entity and get the user's last name with $design->user->lastname.
Now let's say a user creates a new design. I save the entity like:
$design = new Design();
$design->setTitle($_POST['title']);
$design->setUserId($_POST['userId']);
$em = $this->getDoctrine()->getEntityManager();
$em->persist($design);
$em->flush();
When I do this, it fails with the message that userId, which allows no nulls, is null. I thought I might be passing it incorrectly, but when I remove $user from the Design entity it works fine, so I think it's expecting a user object to be passed as well, like:
$design->user = new User($userId)
I don't really want to add a database call just to grab the whole user, when I already know the ID which is all I need. What's the easiest way to handle this?
Hope I have understand your db design that you a user table and design table . Each user may have many designs. right? Then in your entity of design have a field user and its setter(public setUser()) method and a getter method(public getUser()). During the persistance of design entity ,it always expect a user object for User field (not an id) in ORM. In ORM a foreginkey relationship between User and Design it maps through by an object.
So in your case during persistance of design entity
1. Make the user object from userId.
$em = $this->getDoctrine()->getEntityManager();//make your entity manager
$userobj=$em->getRepository('yourBundle:User')->find($userId);
$design = new Design();
$design->setTitle($_POST['title']);
$design->setUser($userobj);
$em->persist($design);
$em->flush();
One Imoprtant thing Make sure that in your design entity that do not have the field userId instead of user. if you have you need to remove the getter and setter method of userid and also edit your yml file or anotation(if you use annotation)

GAE Datastore - workaround for updating entity?

Assume:
class Contacts(db.Model):
first_name = StringProperty()
last_name = StringProperty()
phone_number = PhoneNumberProperty()
new_contact = Contacts(first_name="Homer", last_name="Simpson", phone_number = 1234566)
new_contact.put()
I am new to GAE Datastore, but per GAE Datastore Docs (see below if interested), i can't modify a single property of the entity (eg, phone_number). I have to re-declare all properties, and then put() the new entity. Omitting previously-declared properties from the new entity results in them being declared as None. Is there a workaround for this -- such as manually overriding the key-name generation function -- so the entity maintains the same key?
#
from GAE Datastore Docs:
To update an existing entity, modify the attributes of the object, then call the put() method. The object data overwrites the existing entity. The entire object is sent to the datastore with every call to put(). Note: The datastore API does not distinguish between creating a new entity and updating an existing entity. If the object's key represents an entity that exists, calling its put() method overwrites the entity.
that's not true. You need to have a way to get the contact you want and you can update just that. Using the keyname is only one way to do it. If you know the ID of filter a query to only get one entity, you can update a field from it and the put() to update it.
You could have something like:
query = Contact.all().filter('first_name', 'john').filter('last_name', 'doe')
for contact in query:
contact.phone_number = 498340594834
contact.put()
Note that that code would update any contacts with that name to that phone number. If there is more than one with that name, both are updated. Using a keyname can prevent that but you have to create more complex keys since only the first and last name might colide.

Resources