Symfony2 - Share Entity Between Bundles with different relationships

2.1k views Asked by At

How do you share an entity between multiple bundles with different relationships?

For example both the ZooAnimalBundle and FarmAnimalBundle need a User Entity. A third Bundle AccountUserBundle has the User Entity.

In both the Zoo and Farm AnimalBundles I create a User Entity like so:

use Account\UserBundle\Entity\User as BaseUser;


class User extends BaseUser 
{
}

I then have a Hospital entity in Zoo:

class Hospital {
/**
 * @ORM\ManyToMany(targetEntity="Zoo\AnaimalBundle\Entity\User")
 * @ORM\JoinTable(name="users_zoo_animals")
 */
protected $users; 

And a Room entity in Farm:

class Room {
/**
 * @ORM\ManyToMany(targetEntity="Farm\AnaimalBundle\Entity\User")
 * @ORM\JoinTable(name="users_farm_animals")
 */
protected $users; 

Everything works so far in that I can call Zoo.Room->getUsers() or Farm.Hospital->getUsers()

However the problem is I'm not sure on how to set up the inverse relationship in their respective User entities.

If for example I update the FarmAnimal User Entity and run doctrine:generate:entities

/**
 * @ORM\Entity
 */
class User extends BaseUser
{
    /**
     * @ORM\ManyToMany(targetEntity="Room", mappedBy="users", cascade={"persist"})
     */
    protected $rooms;
}

It will copy the protected $properties from BaseUser and create all the set and get methods which is not what I want. What is the correct way of setting up these relationships?

Update

If you don't setup the inverse relationship, how would you select all users where hospital.id = 1

    $qb = $this->getEntityManager()->createQueryBuilder()
        ->select(
            'u'
        )
        ->from('Account\UserBundle\Entity\User','u')
        ->leftJoin('u.hospitals', 'h')
        ->andWhere('h.id = :hospital_id')
            ->setParameter('hospital_id',$hospital_id);

This gives the error:

   Class Account\UserBundle\Entity\User has no association named hospitals 

I know I could select from hospital and join user because that relationship does exist but I need to select users because I am using them with Doctrine\ORM\Tools\Pagination\Paginator

The query would be

    $qb = $this->createQueryBuilder('a')
        ->select(
            'h', 'u'
        )
        ->leftJoin('h.users', 'u')

The problem with this is Paginator only sees one result Hospital because the Users are attached to it.

2

There are 2 answers

2
lxg On BEST ANSWER

You can define abstract entity dependencies and implement them with other bundles.

First, each of the bundles depending on a user entity should define a User interface. For example:

namespace Foo\BarBundle\Entity;

interface UserInterface
{
    public function getId();

    public function getEmail();

    // other getters
}

Then, in each entity depending on the user, define the relationship, e.g.:

namespace Foo\BarBundle\Entity;

use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;

/**
 * @ORM\Entity
 */
class Something
{
    /**
     * @ORM\ManyToOne(targetEntity="UserInterface")
     * @Assert\NotNull
     */
    protected $User;

    // add other fields as required
}

Now you need to register the User entity as an implementation of the UserInterfaces:

namespace Foo\UserBundle\Entity;

use Foo\BarBundle\Entity\UserInterface as BarUserInterface;
use Foo\FoobarBundle\Entity\UserInterface as FoobarUserInterface;

/**
 * @ORM\Entity
 */
class User implements BarUserInterface, FoobarUserInterface
{
    // implement the demanded methods
}

Then add the following to app/config/config.yml:

doctrine:
    orm:
        resolve_target_entities:
            Foo\BarBundle\Entity\UserInterface: Foo\UserBundle\Entity\User
            Foo\FooarBundle\Entity\UserInterface: Foo\UserBundle\Entity\User

(Heads up: there will usually already be a doctrine.orm node which you'll have to extend.)


This is not a perfect solution, because you cannot really say which fields the user entity should have. On the other hand, it's strictly OOP, as you don't have to know about internals of the User implementation – you just need it to return the right values.

1
John Cartwright On

Creating multiple definitions of the account is the wrong way to do it, unless you want to create 3 seperate user tables (even then it's better not to do it this way).

Really you want your other entities to map to the your user entity in the account bundle.

I.e.,

class Hospital {
   /**
    * @ORM\ManyToMany(targetEntity="Zoo\AccountBundle\Entity\User")
    */
   protected $users; 

Now, there is no need to create the inverse relationship. In fact, this is a bad practice since you have a bi-directional dependency. Users don't know about hospitals, but hospital knows about it's users. Now, any bundle can map to the user entity and reuse it.