In the Doctrine, How to Achieve Additional Model Functionality for Entity

Suppose I have a Doctrine entity (version 2) as follows:

<?php namespace AppBundle\Entity; use Doctrine\ORM\Mapping as ORM; /** * User * * @ORM\Table(name="users") * @ORM\Entity */ class User { /** * @var integer * * @ORM\Column(name="id", type="integer") * @ORM\Id * @ORM\GeneratedValue(strategy="IDENTITY") */ private $id /** * @var string * * @ORM\Column(name="name", type="string", length=50, nullable=false) */ private $name; /** * @var string * * @ORM\Column(name="group_id", type="string", length=6, nullable=true) */ private $groupId; // Getters and setters... } 

Now I would like to control the relationship of User to Group , but with some conditions, for example:

  1. returning NULL (or some skeleton / template \AppBundle\Entity\Group with fixed values ​​not loaded from the database) if Group of users.group_id does not exist, even if it is set to (without key restrictions to the database, to prevent this behavior), therefore some verification / verification is required
  2. lazy loading Group , when calling $user->getGroup()

I continue to read Google, and I do not understand how to achieve this correctly (in accordance with the principles of Doctrine / Symfony).

I could add ManyToOne to the entity class as follows:

 /** * @var \AppBundle\Entity\Group * * @ORM\ManyToOne(targetEntity="AppBundle\Entity\Group") * @ORM\JoinColumns({ * @ORM\JoinColumn(name="group_id", referencedColumnName="id") * }) */ private $group; 

But then how to prevent exceptions caused by non-existent foreign keys? That is, I would like to get information about a user group when they are available in my database, but when it is not, I do not want Doctrine to throw an exception when my application crashes.

People say using Entity Manager from Entity is a very bad practice , and I agree. But I'm confused about using proxy objects or Inheritance Mapping for these purposes.

It seems that you can use Models , but I could not find convincing documentation on how to implement them correctly in Doctrine2.

Please help if you can. In Cohan, everything was so simple (but so immature).


EDIT:

@Massimiliano Fedel suggested trying to catch the exception in the User::getGroup() method so that eventually non-existent groups return as NULL .

So, I sent this code:

 /** * Get group * * @return \AppBundle\Entity\Group */ public function getGroup() { try { $group = $this->group; } catch (\Doctrine\ORM\EntityNotFoundException $e) { $group = null; } return $group; } 

Unfortunately, it seems that the exception cannot be caught this way because the framework exits with Doctrine\ORM\EntityNotFoundException :

 Entity of type 'AppBundle\Entity\Group' for IDs id(999) was not found 

UPDATE 2:

Below you can find the basing scheme of the User stream, i.e. I cannot guarantee that all Users will be accessible by Groups in my database.

enter image description here

+7
source share
4 answers

1) Have you tried to catch an exception in the getter method from the "group"? so you can catch the exception and return "null" if an exception occurs.

2) as from the documentation of doctrine 2.1: "Associations are marked by default as" Lazy ", which means that the entire collection object for the association is populated at first access." http://doctrine-orm.readthedocs.org/projects/doctrine-orm/en/latest/tutorials/extra-lazy-associations.html

+1
source

Well, what about creating a service that is going to wrap your CRUD operations in a User object, say UserService ? Thus, you can leave group_id as it is and add a group field that is not managed (no annotations that are not stored in the database).

UserService will have a getGroup method that will take User as an argument, then extract its group_id and use EntityManager (or indeed GroupService ) to retrieve the group object, if none were found, you will return null , otherwise you will return the returned group to the unmanaged field of the object. so you don’t have to come again next time.

+1
source

Given your specific use case, the best option is Doctrine event subscribers .

Please be careful as they are separate from regular Symfony event subscribers . The latter are related to the general Symfony workflow, while the former relates to Doctrine.

In particular, the postLoad event is what you need. The idea here is as follows:

  • Add the group property to User with the corresponding type getter and setter typehinted
  • Create a Doctrine user subscriber called MyBundle\Entity\EventSubscriber\UserSubscriber
  • Deploy it using the Doctrine Entity Management Service
  • Create the postLoad method as follows:

     public function postLoad(LifecycleEventArgs $args) { $user = $args->getEntity(); if ($user instanceof User && $user->getGroupId()) { $r = $this->em->getRepository('MyBundle:Group'); $group = $r->find($user->getGroupId()); if ($group instanceof Group) { $user->setGroup($group); } // If not an instance of Group, no group can be found: do nothing and leave it NULL } } 

The postLoad method postLoad called whenever a Doctrine object is loaded from the manager, which means that all objects found through Doctrine will be populated by their group whenever possible.

If you need lazy loading ... first, are you sure you want this? User groups are typically used on most Symfony application pages β€” for example, in authorization checks. If groups load on every page, it really is not worth it.

If you absolutely need this, you can use the proxy factory Doctrine service and populate your User proxy instead of the actual object (don't know how this works):

 $group = $this->em->getProxyFactory()->getProxy('MyBundle:Group', $user->getGroupId()); $user->setGroup($group); 

In this case, you will need to save the following, because some download attempts will fail if the identifier does not exist:

 /** * Get group * * @return \AppBundle\Entity\Group */ public function getGroup() { static $loaded = false; if (!$loaded) { try { $this->group; } catch (\Doctrine\ORM\EntityNotFoundException $e) { $this->group = null; } $loaded = true; } return $this->group; } 

I changed the source code of Jan Mares, because with every call to getGroup() Doctrine would try to load group .

+1
source

OK, firstly, your User - Group relationship is not defined correctly. You need add ManyToOne relationships:

 /** * @var \AppBundle\Entity\Group * * @ORM\ManyToOne(targetEntity="AppBundle\Entity\Group") * @ORM\JoinColumns({ * @ORM\JoinColumn(name="group_id", referencedColumnName="id") * }) */ private $group; 

A few words about this. In the JoinColumns by default, the relation is null, which means that you can have NULL instead of the foreign key in the resulting group_id column (the join column that Doctrine will create for you).

If you want to have a relation other than zero, the definition should look like this:

 /** * @var \AppBundle\Entity\Group * * @ORM\ManyToOne(targetEntity="AppBundle\Entity\Group") * @ORM\JoinColumns({ * @ORM\JoinColumn(name="group_id", referencedColumnName="id", nullable=false) * }) */ private $group; 

So, by default, you can have a NULL group for User . Once this is determined correctly, regarding your questions:

1.

If your application is created correctly, you should never end up with a User associated with a Group that has not yet been bound to a database. Each Group that you bind to User must be delivered from the Doctrine in some way before continuing to use User . Whether from the application or from the form. If you need to verify that a Group exists before continuing with User , you must solve the problem upstream, and are 100% sure that any Group that you use is taken from the database.

In this particular context, the setGroup installer should already ensure that what is provided is indeed a Group object (if you must not add typehint), and Doctrine already ensures that this object already exists in the database. I agree that Doctrine errors can become ugly, but the supplied product should by no means include any of them.

Understand that Symfony authentication is typically used with forms in order to check for potential violations entered by the end user, not the developer (usually).

2.

With the right relationship, as shown above, Doctrine will take care of this automatically for you. By default, all Doctrine relationships load lazily.

also:

 /** * Get group * * @return \AppBundle\Entity\Group */ public function getGroup() { try { $group = $this->group; } catch (\Doctrine\ORM\EntityNotFoundException $e) { $group = null; } return $group; } 

You do not need to do this. Remove it.

0
source

All Articles