25

I'd like to use, something like:

$em = $this->getEntityManager();

Inside a Entity.

I understand I should do this as a service but for some testing purposes, I want to access it from an Entity.

Is it possible to achieve that?

I've tried to:

$em = $this->getEntityManager();
$profile_avatar = $em->getRepository('bundle:Perfils')->findOneByUser($this-getId());

But isn't working.

Fatal error: Call to undefined method Proxies\webBundleEntityUserProxy::getEntityManager() in /opt/lampp/htdocs/web/src/Pct/bundle/Entity/User.php on line 449

Why am I trying to do it this way?

I've 3 kinds of users: Facebook, Twitter and MyOwnWebsite users. Each of them have differents avatar which links facebook's profile, twitter's or otherwise, if its myownwebsite user, I retrieve the avatar from a URL in a database. For now, I don't want to create a service, because I'm just trying to make it working, to test it, not to create a final deployment. So this is why I'm trying to call Entity manager from an Entity. I don't want, by now, to modify configuration files, just this entity.

Reinherd
  • 5,476
  • 7
  • 51
  • 88

4 Answers4

34

As pointed out (again) by a commenter, an entity manager inside an entity is a code smell. For the OP's specific situation where he wished to acquire the entity manager, with the least bother, a simple setter injection would be most reliable (contrary to my original example injecting via constructor).

For anyone else ending up here looking for a superior solution to the same problem, there are 2 ways to achieve this:

  1. Implementing the ObjectManagerAware interface as suggested by https://stackoverflow.com/a/24766285/1349295

    use Doctrine\Common\Persistence\ObjectManagerAware;
    use Doctrine\Common\Persistence\ObjectManager;
    use Doctrine\Common\Persistence\Mapping\ClassMetadata;
    use Doctrine\ORM\Mapping as ORM;
    
    /**
     * @ORM\Entity
     */
    class Entity implements ObjectManagerAware
    {
        public function injectObjectManager(
            ObjectManager $objectManager,
            ClassMetadata $classMetadata
        ) {
            $this->em = $objectManager;
        }
    }
    
  2. Or, using the @postLoad/@postPersist life cycle callbacks and acquiring the entity manager using the LifecycleEventArgs argument as suggested by https://stackoverflow.com/a/23793897/1349295

    use Doctrine\Common\Persistence\Event\LifecycleEventArgs;
    use Doctrine\ORM\Mapping as ORM;
    
    /**
     * @ORM\Entity
     * @ORM\HasLifecycleCallbacks()
     */
    class Entity
    {
        /**
         * @ORM\PostLoad
         * @ORM\PostPersist
         */
        public function fetchEntityManager(LifecycleEventArgs $args)
        {
            $this->setEntityManager($args->getEntityManager());
        }
    }
    

Original answer

Using an EntityManager from within an Entity is VERY BAD PRACTICE. Doing so defeats the purpose of decoupling query and persist operations from the entity itself.

But, if you really, really, really need an entity manager in an entity and cannot do otherwise then inject it into the entity.

class Entity
{
    private $em;

    public function __contruct($em)
    {
        $this->em = $em;
    }
}

Then invoke as new Entity($em).

Community
  • 1
  • 1
Czar Pino
  • 6,258
  • 6
  • 35
  • 60
  • 6
    Yes, but you've forgot to say that is a VERY BAD PRACTICE – DonCallisto Feb 04 '13 at 13:13
  • 3
    @DonCallisto LOL, yes you're right. Let me ammend my answer. I simply wanted to answer how to "Get entityManager inside an Entity". I kinda feel stupid for not stating something I thought all along. – Czar Pino Feb 04 '13 at 13:41
  • 1
    If this is so bad, then there must be a way I don't know about to retrieve related objects just like in any doctrine relation, but filtered somehow. I mean something like: `$customer->getArchivedInvoices()` instead of `$customer->getInvoices()` and then filtering them. – Sebastián Grignoli Mar 07 '16 at 21:42
  • Even if this is bad practice, this solution is even worse. It is not automatic, does not ensure that entity manager is related to entity (entity is managed by manager). You can inject anything there. After hydrating in repository entity would not have entity manager since Doctrine does not use constructor. The real solution is to use lifecycle callbacks (@onalbi's answer) or ObjectManagerAware interface like here: http://stackoverflow.com/a/24766285/842480 – Wirone Feb 08 '17 at 10:30
  • Hey @Wirone, agree with you. Thanks for pointing that out -- construction injection is quite senseless really. I'm amused by my own answer. However (and let me prefix) afaik, the only solution to __obtain an entity manager inside an entity__ is unfortunately nothing other than an injection. Constructor injection is obviously not practical as you've pointed out leaving a setter injection the better option. – Czar Pino Feb 08 '17 at 12:43
  • @Czar with lifecycle callbacks you can register `postLoad` callback and then you get `Doctrine\ORM\Event\LifecycleEventArgs` as an argument. You are then able to access entity manager and save it inside entity for further usage. The same can be achieved with implementing `Doctrine\Common\Persistence\ObjectManagerAware` interface (with this solution you have setter which can be used when constructing new entity too, lifecycle callbacks will do the trick only for fetched entities). – Wirone Feb 08 '17 at 13:52
  • Great info there @Wirone! Both suggestions are superior solutions indeed. – Czar Pino Feb 09 '17 at 02:38
  • @Sebastián Grignoli You can. When you call $customer->getInvoices(), you get in fact a proxy class, on which you can apply filter() and matching(). filter() will get all the result, then filter them with a callback; matching will modify the query to add the WHERE. It's almost magic : foreach($customer->getInvoices() ...) will query (SQL) all the invoices, whereas foreach($customer->getInvoices()->matching(...) ... ) will trigger an optimized SQL query. – Pierre-Olivier Vares Aug 03 '17 at 14:34
  • It should be noted than injecting the Entity Manager (any way how) can (and probably will) take you into serious trouble when trying to serialize and deserialize your entity (if you wanna store it in session, for example), as it will (or try to) serialize the EntityManager. – Pierre-Olivier Vares Aug 03 '17 at 14:40
  • **ObjectManagerAware** worked fine or me, but in one of my twig, when retrieving an ArrayCollection of that entity I've got a strange error : `Serialization of 'Closure' is not allowed`. I was unable to find a solution/explanation for this error ! – Sami Aug 14 '19 at 09:04
  • 1
    It is true that adding the entity manager to the entity smells bad, but if you add a (lazy) association to your entity, Doctrine will use the PersistentCollection class to represent the associated entities and this class also has an EntityManager as one of its properties. So indirectly, Doctrine already adds an EntityManager to the entity. If you want to have some lazy loading in your entity as well and adding the EntityManager to the entity to make that happen, you are doing the same as Doctrine already does. – Jorrit Schippers Jun 04 '20 at 12:11
  • Symfony should really learn from Laravel in this area... It's so annoying that you can't just delete a relation with a simple method.. – Tofandel Feb 02 '22 at 18:31
6

Best way is to use Life Cycle: @ORM\HasLifecycleCallbacks

And you can use the appropriate Event as you want to get result:

@postLoad
@postPersist
...
onalbi
  • 2,609
  • 1
  • 24
  • 36
2

What I think you should do is, instead of using the Entity Manager inside your entity, is to create a custom repository for your entity.

In your entity ORM file, add an entry as follows (or in your entity class annotations if not using YML):

App\Bundle\Profils: 
# Replace the above as appropiate
    type: entity
    table: (your table)
    ....
    repositoryClass: App\Bundle\CustomRepos\ProfilsRepository
    # Replace the above as appropiate. 
    # I always put my custom repos in a common folder, 
    # such as CustomRepos

Now, create a new PHP class that has the namespace above:

//Your ProfilsRepository.php
<?php
namespace App\Bundle\CustomRepos;

use Doctrine\ORM\EntityRepository;

class ProfilsRepository extends EntityRepository
{
    /**
     * Will return the user url avatar given the user ID
     * @param integer $userID The user id.
       @return string The avatar url
     */
    public function getUserProfile($userId)
    {
       $em = $this->getEntityManager();
       $qb = $em->createQueryBuilder();
       $qb->select... (your logic to retrieve the profil object);

       $query = $qb->getQuery();
       $result = $query->getResult();

       return $result;
    }
}

Finally, in your Controller:

// Your controller
<?php
   namespace <class namespace>;
   ...
   use App\Bundle\CustomRepos\ProfilsRepository;
   use Symfony\Bundle\FrameworkBundle\Controller\Controller;
   ...
   class YourClassNameController extends Controller
   {
      public function yourAction()
      {
         $userId = <get the user ID>;
         // Pass the name of your entity manager to the 
         // getManager function if you have more than one and
         // didn't define any default
         $em = $this->getDoctrine()->getManager();
         $repo = $em->getRepository('Profils');
         $avatar = $repo->getUserProfile($userId);
         ...

      }
   }
Nicolas
  • 1,320
  • 2
  • 16
  • 28
0

You need to set the services.yml with:

services:
    your_service_name:
        class: AppBundle\Controller\ServiceController
        arguments: [ @doctrine.orm.entity_manager ]

You need to set also the Controller with the following constructor:

public function __construct(\Doctrine\ORM\EntityManager $em)
{
    $this->em = $em;
}

and use $this->em in the controller (for example $connection = $this->em->getConnection();)

A.L
  • 10,259
  • 10
  • 67
  • 98
Fabien Thetis
  • 1,666
  • 15
  • 11