Doctrine ORM: use interface as relation for differ

2019-07-20 04:43发布

问题:

How to use interface in many-to-many relation in doctrine?

In my app there is 3 entities: User, Car and Driver. User can add cars and drivers as favorites. So i made this structure (simplified):

User, who has favorite feature:

namespace Acme\AppBundle\Entities;

use Acme\AppBundle\Interfaces\HasFavorites;

/** @ORM\Entity */
class User implements HasFavorites
{
    /** @ORM\ManyToMany(targetEntity="Acme\AppBundle\Entities\Favorite") */
    protected $favorites;

    public function getFavorites() : ArrayCollection
    {
        return $this->favorites;
    }

    public function addFavorite(Favorite $favorite)
    {
        $this->favorites->add($favorite);
    }
}

Favorite object model:

namespace Acme\AppBundle\Entities;

use Acme\AppBundle\Interfaces\Favoritable;

/** @ORM\Entity */
class Favorite
{
    /** @ORM\ManyToOne(targetEntity="Acme\AppBundle\Entities\User") */
    private $owner;

    /** @ORM\ManyToOne(targetEntity="Acme\AppBundle\Interfaces\Favoritable") */
    private $target;

    public function __construct(User $owner, Favoritable $target)
    {
        $this->owner  = $owner;
        $this->target = $target;
    }

    public function getOwner() : User
    {
        return $this->owner;
    }

    public function getTarget() : Favoritable
    {
        return $this->target;
    }
}

Car and Driver – entities that can be added to favorites:

namespace Acme\AppBundle\Entities;

use Acme\AppBundle\Interfaces\Favoritable;

/** @ORM\Entity */
class Car implements Favoritable { /* ... */ }

/** @ORM\Entity */
class Driver implements Favoritable { /* ... */ }

But when i update my schema with command ./bin/console doctrine:schema:update --force, i'll get error

[Doctrine\Common\Persistence\Mapping\MappingException]
Class 'Acme\AppBundle\Interfaces\Favoritable' does not exist

This code in my tests also working ok (if i don't work with database) so namespaces and file paths are correct:

$user = $this->getMockUser();
$car  = $this->getMockCar();
$fav  = new Favorite($user, $car);
$user->addFavorite($fav);
static::assertCount(1, $user->getFavorites());
static::assertEquals($user, $fav->getUser());

How to do this relation? What i've found yet in search that is only situation when Car/Driver are mostly the same by logic.

What i need in database is just something like this (desired) but it's not so important:

+ ––––––––––––– +   + ––––––––––––– +   + –––––––––––––––––––––––––––––––––– +
|     users     |   |      cars     |   |            favorites               |
+ –– + –––––––– +   + –– + –––––––– +   + –––––––– + ––––––––– + ––––––––––– +
| id |   name   |   | id |   name   |   | owner_id | target_id | target_type |
+ –– + –––––––– +   + –– + –––––––– +   + –––––––– + ––––––––– + ––––––––––– +
| 42 | John Doe |   | 17 | BMW      |   |       42 |        17 | car         |
+ –– + –––––––– +   + –– + –––––––– +   + –––––––– + ––––––––– + ––––––––––– +

回答1:

Looks like You are missing JoinColumns statement in the Annotation after:

** @ORM\ManyToOne(targetEntity="Acme\AppBundle\Entities\User") */

 * @ORM\JoinColumns({
 *  @ORM\JoinColumn(name="owner_id", referencedColumnName="id")
 * })


回答2:

Not sure how you are generating your schema, but i would imagine that the interface is not in the scope.

Add the command you are using to generate so this can be reproduced.

Update Unless Favoritable is a database table this will not work
@ORM\ManyToOne(targetEntity="Acme\AppBundle\Interfaces\Favoritable")

I would suggest mapping the Car and Driver Entities in User and then implement a getFavorites from that will return the Cars and/or Drivers.

This should get you going

/** @ORM\ManyToOne(targetEntity="Acme\AppBundle\Entities\Car" mappedBy="favorites" ????) */ private $favoriteCars;

http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/association-mapping.html



回答3:

With Doctrine's ResolveTargetEntityListener you can annotate a relationship to target interface the way you did, and then resolve it to a specific entity.

More in Doctrine docs: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/cookbook/resolve-target-entity-listener.html

Or here, if you're using Symfony framework: http://symfony.com/doc/current/doctrine/resolve_target_entity.html

HOWEVER, you can't achieve what you want with this - one interface being resolved into multiple entities implementing this interface using a "discriminator". That's not possible AFAIK. You can only resolve one interface to one entity for use within one table.