Doctrine doesn't load associations from session correctly - php

I'm having this behavior with Doctrine 2.1 where I'm looking for a nice 'workaround'. The problem is as follows:
I have a user Entity:
/**
* #Entity(repositoryClass="Application\Entity\Repository\UserRepository")
* #HasLifecycleCallbacks
*/
class User extends AbstractEntity
{
/**
*
* #var integer
*
* #Column(type="integer",nullable=false)
* #Id
* #GeneratedValue(strategy="IDENTITY")
*/
protected $id;
/**
*
* #var \DateTime
* #Column(type="datetime",nullable=false)
*/
protected $insertDate;
/**
*
* #var string
* #Column(type="string", nullable=false)
*/
protected $username;
/**
*
* #ManyToOne(targetEntity="UserGroup", cascade={"merge"})
*/
protected $userGroup;
}
And a usergroup entity:
/**
* #Entity
*/
class UserGroup extends AbstractEntity
{
/**
*
* #var integer
*
* #Column(type="integer",nullable=false)
* #Id
* #GeneratedValue(strategy="IDENTITY")
*/
protected $id;
/**
*
* #var string
* #Column(type="string",nullable=false)
*/
protected $name;
}
If I instantiate a user object (doing this with Zend_Auth) and Zend_Auth puts it automatically the session.
The problem is however, that is I pull it back from the session at a next page then the data in the user class is perfectly loaded but not in the userGroup association. If I add cascade={"merge"} into the annotation in the user object the userGroup object IS loaded but the data is empty. If you dump something like:
$user->userGroup->name
You will get NULL back. The problem is no data of the usergroup entity is accesed before the user object is saved in the session so a empty initialized object will be returned. If I do something like:
echo $user->userGroup->name;
Before I store the user object in the session all data of the assocication userGroup is succesfully saved and won't return NULL on the next page if I try to access the $user->userGroup->name variable.
Is there a simple way to fix this? Can I manually load the userGroup object/association with a lifecycle callback #onLoad in the user class maybe? Any suggestions?

Your problem is a combination of what mjh_ca answered and a problem with your AbstractEntity implementation.
Since you show that you access entity fields in this fashion:
$user->userGroup->name;
I assume your AbstractEntity base class is using __get() and __set() magic methods instead of proper getters and setters:
function getUserGroup()
{
return $this->userGroup;
}
function setUserGroup(UserGroup $userGroup)
{
$this->userGroup = $userGroup;
}
You are essentially breaking lazy loading:
"... whenever you access a public property of a proxy object that hasn’t been initialized yet the return value will be null. Doctrine cannot hook into this process and magically make the entity lazy load."
Source: Doctrine Best Practices: Don't Use Public Properties on Entities
You should instead be accessing fields this way:
$user->getUserGroup()->getName();
The second part of your problem is exactly as mjh_ca wrote - Zend_Auth detaches your entity from the entity manager when it serializes it for storage in the session. Setting cascade={"merge"} on your association will not work because it is the actual entity that is detached. You have to merge the deserialized User entity into the entity manager.
$detachedIdentity = Zend_Auth::getInstance()->getIdentity();
$identity = $em->merge($detachedIdentity);
The question, is how to do this cleanly. You could look into implementing a __wakeup() magic method for your User entity, but that is also against doctrine best practices...
Source: Implementing Wakeup or Clone
Since we are talking about Zend_Auth, you could extend Zend_Auth and override the getIdentity() function so that it is entity aware.
use Doctrine\ORM\EntityManager,
Doctrine\ORM\UnitOfWork;
class My_Auth extends \Zend_Auth
{
protected $_entityManager;
/**
* override otherwise self::$_instance
* will still create an instance of Zend_Auth
*/
public static function getInstance()
{
if (null === self::$_instance) {
self::$_instance = new self();
}
return self::$_instance;
}
public function getEntityManager()
{
return $this->_entityManager;
}
public function setEntityManager(EntityManager $entityManager)
{
$this->_entityManager = $entityManager;
}
public function getIdentity()
{
$storage = $this->getStorage();
if ($storage->isEmpty()) {
return null;
}
$identity = $storage->read();
$em = $this->getEntityManager();
if(UnitOfWork::STATE_DETACHED === $em->getUnitOfWork()->getEntityState($identity))
{
$identity = $em->merge($identity);
}
return $identity;
}
}
And than add an _init function to your Bootstrap:
public function _initAuth()
{
$this->bootstrap('doctrine');
$em = $this->getResource('doctrine')->getEntityManager();
$auth = My_Auth::getInstance();
$auth->setEntityManager($em);
}
At this point calling $user->getUserGroup()->getName(); should work as intended.

When you store the entity to a session (via Zend_Auth or otherwise), the object is serialized and no longer maintained by Doctrine when subsequently retrieved and unserialized. Try merging the entity back into the EntityManager. See http://www.doctrine-project.org/docs/orm/2.1/en/reference/working-with-objects.html

Related

Create Doctrine entity from JSON column

I'm new to using Doctrine and am struggling to find the best way to handle the following scenario.
I have a table payment_gateways, which stores payment gateway config for users. Most of the data is common between payment gateways but there is also a JSON column config, the purpose of this column is to store configuration which is unique to specific payment gateways since I can't guarantee all payment gateways will share the same configuration fields.
I want to create a Doctrine ORM entity for my payment_gateways table, but I also want the config property to be its own entity where I can use its own getters and setters instead of accessing and setting properties directly from the config array.
Is single table inheritance a good way to approach this? I have tried this by creating a separate entity for each payment gateway I have integrated with and have extended the base entity PaymentGateway. In these entities I define the properties that I expect to be in the config property. Then I get/set the properties like so:
/**
* #ORM\Entity
*/
class PaypalGateway extends PaymentGateway
{
/**
* #return string|null
*/
public function getApiKey(): ?string
{
return $this->getConfig()['apiKey'] ?? null;
}
/**
* #param string $apiKey
*/
public function setApiKey(string $apiKey)
{
$data = $this->getConfig();
$data['apiKey'] = $apiKey;
$this->setConfig($data);
}
The parent PaymentGateway class looks like the following:
/**
* #ORM\Entity
* #ORM\Table(name="payment_gateways")
* #ORM\InheritanceType("SINGLE_TABLE")
* #ORM\DiscriminatorColumn(name="provider_type", type="string")
* #ORM\DiscriminatorMap({
* "paypal" = "PaypalGateway",
* "stripe" = "StripeGateway"
* })
*/
abstract class PaymentGateway
{
**
* #ORM\Column(type="json")
*/
private ?array $config = null;
public function getConfig()
{
return $this->config;
}
public function setConfig($config)
{
$this->config = $config;
}
As far as I can tell, this is working correctly for me but I'm not sure if it's a good way to go about it. I was wondering if this is the correct approach or if there's something I'm missing completely?

FOSUserBundle adding a group to the user wont do anything

I am using FOSuser with SonataUserBundle and I am trying to add a user to the Clients group everytime someone registers, but it doesnt work. I dont get any errors, but I am not adding the group either... I tried it in two ways:
1) I overwritten the registrationController and made the confirmAction save the new group like this:
/**
* Tell the user his account is now confirmed
*/
public function confirmedAction()
{
$repository = $em->getRepository('ApplicationSonataUserBundle:Group');
$group = $repository->findOneByName('Clients');
$em = $this->getDoctrine()->getEntityManager();
$user = $this->getUser();
$user->addGroup($group);
$this->em->flush();
$userManager = $this->get('fos_user.user_manager');
$userManager->updateUser($user);
}
}
2) Icreated an eventListener and made the groupping there:
<?php
namespace Application\Sonata\UserBundle\EventListener;
use FOS\UserBundle\FOSUserEvents;
use FOS\UserBundle\Event\FormEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Doctrine\ORM\EntityManager;
/**
* Listener responsible to change the redirection at the end of the password resetting
*/
class GrouppingListener implements EventSubscriberInterface
{
protected $em;
protected $user;
public function __construct(EntityManager $em)
{
$this->em = $em;
}
/**
* {#inheritDoc}
*/
public static function getSubscribedEvents()
{
return array(
FOSUserEvents::REGISTRATION_SUCCESS => 'onRegistrationSuccess',
);
}
public function onRegistrationSuccess(FormEvent $event)
{
$this->user = $event->getForm()->getData();
$entity = $this->em->getRepository('ApplicationSonataUserBundle:Group')->findOneByName('Clients'); // You could do that by Id, too
$this->user->addGroup($entity);
$this->em->flush();
}
}
My group entity is being extended like this:
<?php
/**
* This file is part of the <name> project.
*
* (c) <yourname> <youremail>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Application\Sonata\UserBundle\Entity;
use Sonata\UserBundle\Entity\BaseGroup as BaseGroup;
/**
* This file has been generated by the Sonata EasyExtends bundle ( http://sonata-project.org/bundles/easy-extends )
*
* References :
* working with object : http://www.doctrine-project.org/projects/orm/2.0/docs/reference/working-with-objects/en
*
* #author <yourname> <youremail>
*/
class Group extends BaseGroup
{
/**
* #var integer $id
*/
protected $id;
/**
* Get id
*
* #return integer $id
*/
public function getId()
{
return $this->id;
}
}
None of these options worked... I did this based on other stackoverflow answers...Why wont it work?
You're missing persist in both of your cases. In order for an entity to become manageable, it needs to be persisted first.
$user->addGroup($group);
$this->em->flush();
change this in your controller action to:
$user->addGroup($group);
$this->em->persist($user);
$this->em->flush();
A paragraph from Doctrine manual:
An entity can be made persistent by passing it to the EntityManager#persist($entity) method. By applying the persist operation on some entity, that entity becomes MANAGED, which means that its persistence is from now on managed by an EntityManager. As a result the persistent state of such an entity will subsequently be properly synchronized with the database when EntityManager#flush() is invoked.

Custom functions in Doctrine2 auto generated classes

Is there a way to extend classes auto-generated from database by Doctrine2 ?
Example: I have this User class generated by Doctrine.
<?php
namespace Entities;
/**
* User
*/
class User
{
/**
* #var integer
*/
private $id;
/**
* #var string
*/
private $firstName;
/**
* #var string
*/
private $lastName;
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set firstName
*
* #param string $firstName
*
* #return User
*/
public function setFirstName($firstName)
{
$this->firstName = $firstName;
return $this;
}
/**
* Get firstName
*
* #return string
*/
public function getFirstName()
{
return $this->firstName;
}
/**
* Set lastName
*
* #param string $lastName
*
* #return User
*/
public function setLastName($lastName)
{
$this->lastName = $lastName;
return $this;
}
/**
* Get lastName
*
* #return string
*/
public function getLastName()
{
return $this->lastName;
}
I would like to add this function :
public function getFullName()
{
return $this->getFirstName().' '.$this->getLastname();
}
Is there a cleaner way than adding it directly into this class?
I tried to create another class (Test) in libraries and extends it, then add it in autoload (which is working), but i get an error when I try to save object :
class Test extends Entities\User {
public function getFullName() {
return $this->getFirstName().' '.$this->getLastname();
}
}
Message: No mapping file found named 'Test.dcm.yml' for class 'Test'.
I'm using Doctrine2 in CodeIgniter3.
Thanks.
As explained in the Doctrine 2 FAQ:
The EntityGenerator is not a full fledged code-generator that solves all tasks. [...] The EntityGenerator is supposed to kick-start you, but not towards 100%.
In plain English this means you ask Doctrine to generate the Entity files only once. After that, you are on your own and do whatever changes you like (or it needs) to them.
Because an Entity is not just a container for some properties but it's where the entire action happens, this is how the flow should happen, Doctrine cannot write more code for you.
The only way to add functionality to the stub Entities generated by Doctrine is to complete the generated classes by writing the code that implements the functionality of each Entity according to its role in your Domain Model.
Regarding the other issue, on the Test class, the error message is self-explanatory: any class passed to the EntityManager for handling needs to be mapped.
Take a look at the help page about Inheritance Mapping. You can either map class User as a Mapped Superclass (it acts like a template for the derived classes and its instances are not persisted in the database) or you can use Single Table Inheritance to store the instances of all classes derived from User in a single table (useful when they have the same properties but different behaviour).
Or, in case you created class Test just because you were afraid to modify the code generated by Doctrine, put the behaviour you need in class User and drop class Test.
Seems you are having trouble while accessing the user entity class. You mentioned that test is a library class. Why not try to access the User entity class from a controller. If can do this then may be something is wrong with the configuration of test file. Besides, you need to map you doctrine entity class properly. You can have a look here to learn about doctrine mapping using yml: http://doctrine-orm.readthedocs.org/en/latest/reference/yaml-mapping.html
you can do this:
<?php
namespace Entities;
/**
* User
*/
class User extends Test
{
//... and extends Test
}
or
<?php
namespace Entities;
/**
* User
*/
class User
{
//...
public function getFullName() {
return $this->getFirstName().' '.$this->getLastname();
}
}
view more
Symfony 2 - Extending generated Entity class
http://www.theodo.fr/blog/2013/11/dynamic-mapping-in-doctrine-and-symfony-how-to-extend-entities/
http://doctrine-orm.readthedocs.org/en/latest/reference/inheritance-mapping.html
Annotation allows you to specify repository class to add more methods to entity class.
/**
* #ORM\Entity(repositoryClass="App\Entity\UserRepository")
*/
class User
{
}
class UserRepository extends EntityRepository
{
public function getFullName() {
return $this->getFirstName().' '.$this->getLastname();
}
}
// calling repository method
$entityManager->getRepository('User')->getFullName();
Here's a link [http://doctrine-orm.readthedocs.org/en/latest/reference/working-with-objects.html]
7.8.8. Custom Repositories

Symfony2 organization: where to put entity functions that interact with database and other entities

I've converted a PHP application over to Symfony2 and have yet another structural question...
In my old application I would have entity classes that might act upon other entity classes...for instance, I have a search class and a result class. A function like search->updateSearch() would operate upon the search class, and upon its child result class ($this->result->setFoo('bar'). This is just one example of an entity-related function that doesn't belong in Symfony2's entity class.
From what I can tell it seems like the most symfonyesque method would be to create a service, something along the lines of a searchHelper class, to which I could pass the entity manager, $search, and $result classes, and operate on them there.
Does that sound like the best course of action?
Thank you!
For this scenario I use Model Managers, it's intended to be a business layer ORM agnostic interface for operating with entities. Something like:
<?php
/**
* Group entity manager
*/
class GroupManager
{
/**
* Holds the Doctrine entity manager for database interaction
* #var EntityManager
*/
protected $em;
/**
* Holds the Symfony2 event dispatcher service
* #var EventDispatcherInterface
*/
protected $dispatcher;
/**
* Entity specific repository, useful for finding entities, for example
* #var EntityRepository
*/
protected $repository;
/**
* Constructor
*
* #param EventDispatcherInterface $dispatcher
* #param EntityManager $em
* #param string $class
*/
public function __construct(EventDispatcherInterface $dispatcher, EntityManager $em)
{
$this->dispatcher = $dispatcher;
$this->em = $em;
$this->repository = $em->getRepository($class);
}
/**
* #return Group
*/
public function findGroupBy(array $criteria)
{
return $this->repository->findOneBy($criteria);
}
/**
* #return Group
*/
public function createGroup()
{
$group = new Group();
// Some initialization or creation logic
return $group;
}
/**
* Update a group object
*
* #param Group $group
* #param boolean $andFlush
*/
public function updateGroup(Group $group, $andFlush = true)
{
$this->em->persist($group);
if ($andFlush) {
$this->em->flush();
}
}
/**
* Add a user to a group
*
* #param User $user
* #param Group $group
* #return Membership
*/
public function addUserToGroup(User $user, Group $group)
{
$membership= $this->em->getRepository('GroupBundle:Membership')
->findOneBy(array(
'user' => $user->getId(),
'group' => $group->getId(),
));
if ($membership && $membership->isActive()) {
return null;
} elseif ($membership && !$membership->isActive()) {
$membership->setActive(true);
$this->em->persist($membership);
$this->em->flush();
} else {
$membership = new Membership();
$membership->setUser($user);
$membership->setGroup($group);
$this->em->persist($membership);
$this->em->flush();
}
$this->dispatcher->dispatch(
GroupEvents::USER_JOINED_GROUP, new MembershipEvent($user, $group)
);
return $membership;
}
And then the service definition:
<service id="app.model_manager.group" class="App\GroupBundle\Entity\GroupManager">
<argument type="service" id="event_dispatcher" />
<argument type="service" id="doctrine.orm.entity_manager" />
</service>
You can inject the logger, mailer, router, or whichever other service you could need.
Take a look to FOSUserBundle managers, to get examples and ideas about how to use them.
It sounds like you should be using doctrines custom repository classes. You can check them out here: http://symfony.com/doc/current/book/doctrine.html#custom-repository-classes
Basically they allow you to add custom logic above and beyond your basic entity. Also because they are basically an extension of the entity it makes it really easy to load them in and use their functions:
//Basic Entity File
/**
* #ORM\Entity(repositoryClass="Namespace\Bundle\Repository\ProductRepo")
*/
class Product
{
//...
}
Then the repo file for that entity:
//Basic Repo File
use Doctrine\ORM\EntityRepository;
class ProductRepo extends EntityRepository
{
public function updateSearch($passedParam)
{
// Custom query goes here
}
}
Then from your controller you can load the repo and use the function:
//Controller file
class ProductController extends Controller
{
public function updateSearchAction()
{
$productRepo = $this->getDoctrine()->getManager()->getRepository('Namespace\Bundle\Entity\Product');
// Set $passedParam to what ever it needs to be
$productRepo->updateSearch($passedParam);
}
}

Removing entities from another entity

To make it simple, let's say I have two objects with one-to-many relation:
User --(1:n)--> Request
with User defined as
class User {
...
/** #OneToMany(targetEntity="Request", mappedBy="user", cascade={"all"}) */
private $request;
...
}
and Request defined as
class Request {
...
/** #ManyToOne(targetEntity="User", inversedBy="request", cascade={"persist"}) */
private $user;
...
}
Is it possible to create a method that removes all Requests associated with User from within User entity?
What I need is something like this:
class User {
....
public function removeAllMyRequests() {
foreach ($this->getAllMyRequests() as $req)
$this->em->remove($req);
}
....
}
But apparently I'm not supposed to invoke entity manager from within entity.
You can mark the association with "Orphan Removal":
/**
* #Entity
*/
class User
{
/**
* #OneToMany(
* targetEntity="Request",
* mappedBy="user",
* cascade={"all"},
* orphanRemoval=true
* )
*/
private $requests;
}
Any Request object removed from the User#requests collection will be marked for removal during the next EntityManager#flush() call.
To remove all items at once, you can simply use Doctrine\Common\Collections\Collection#clear():
public function removeAllMyRequests() {
$this->requests->clear();
}
I think you are looking for the "cascade" option : http://docs.doctrine-project.org/en/2.0.x/reference/working-with-associations.html#transitive-persistence-cascade-operations

Categories