Переопределить ассоциативное сопоставление свойства объекта в Doctrine 2.6

Предпосылки:

  • PHP 7.1.8
  • Symfony 3.3.9
  • Doctrine 2.6.x-dev

Интересно, возможно ли переопределить атрибут inversedBy сопоставления ассоциаций свойств, взятый из признака.

Интерфейс, который я использую в качестве заполнителя конкретного пользователя:

ReusableBundle\ModelEntrantInterface.php

interface EntrantInterface
{
    public function getEmail();

    public function getFirstName();

    public function getLastName();
}
  • Следующая архитектура работает очень хорошо (нужно создать объект User, который реализует EntrantInterface и все другие объекты, которые получены из этих абстрактных классов в AppBundle):

ReusableBundle\Entity\Entry.php

/**
 * @ORM\MappedSuperclass
 */
abstract class Entry
{
    /**
     * @var EntrantInterface
     *
     * @ORM\ManyToOne(targetEntity="ReusableBundle\Model\EntrantInterface", inversedBy="entries")
     * @ORM\JoinColumn(name="user_id")
     */
    protected $user;

    // getters/setters...
}

ReusableBundle\Entity\Timestamp.php

/**
 * @ORM\MappedSuperclass
 */
abstract class Timestamp
{
    /**
     * @var EntrantInterface
     *
     * @ORM\ManyToOne(targetEntity="ReusableBundle\Model\EntrantInterface", inversedBy="timestamps")
     * @ORM\JoinColumn(name="user_id")
     */
    protected $user;

    // getters/setters...
}

И еще пару объектов с аналогичной структурой, которые используют EntranInterface

  1. И это то, чего я хочу достичь - UserAwareTrait для повторного использования для нескольких объектов:

ReusableBundle\Entity\Черты характера \UserAwareTrait.php

trait UserAwareTrait
{
    /**
     * @var EntrantInterface
     *
     * @ORM\ManyToOne(targetEntity="ReusableBundle\Model\EntrantInterface")
     * @ORM\JoinColumn(name="user_id")
     */
    protected $user;

    // getter/setter...
}

В Doctrine 2.6, если бы я использовал суперкласс и хотел переопределить его свойство, я бы сделал это:

/**
 * @ORM\MappedSuperclass
 * @ORM\AssociationOverrides({
 *     @ORM\AssociationOverride({name="property", inversedBy="entities"})
 * })
 */
abstract class Entity extends SuperEntity
{
    // code...
}

Но если я хочу, чтобы Entity использовал UserAwareTrait и переопределил ассоциативное сопоставление свойства...

/**
 * @ORM\MappedSuperclass
 * @ORM\AssociationOverrides({
 *     @ORM\AssociationOverride({name="user", inversedBy="entries"})
 * })
 */
abstract class Entry
{
    use UserAwareTrait;
    // code...
}

... и запустите php bin/console doctrine:schema:validate Я вижу эту ошибку в консоли:

[Учение\ORM\Mapping\MappingException]
Недопустимое переопределение поля с именем 'user' для класса 'ReusableBundle\Entity\Entry'.

Есть ли способ обхода, который я мог бы выполнить для достижения желаемого результата?

  • Использовать свойство для хранения общих свойств

  • Отменить сопоставление ассоциаций или (возможно) сопоставление атрибутов в классе, который использует этот признак

Ответ 1

TL; DR Вы должны изменить модификатор доступа с protected на private. Не забывайте, что вы не сможете напрямую манипулировать частной собственностью в подклассе и вам понадобится getter.

Исключение появляется из-за ошибки (я считаю, или причуды поведения) в AnnotationDriver.

foreach ($class->getProperties() as $property) {
    if ($metadata->isMappedSuperclass && ! $property->isPrivate()
        ||
        ...) {
        continue;
    }

Он пропускает все неличные свойства для MappedSuperclass, позволяя им составлять метаданные при разборе подкласса. Но когда дело доходит до того, что драйвер пытается сделать это на уровне MappedSuperclass, он не помнит, что свойство было пропущено, не удалось найти его в метаданных и создать исключение.

Я подробно объяснил вопрос. Вы также можете найти ссылку на блок-тесты, которые подчеркивают случай.

Ответ 2

Вам нужно попробовать это в своем собственном коде, чтобы увидеть, но возможно может.

В качестве эксперимента я переопределил признак в классе, а затем проверил его с помощью class_uses() http://php.net/manual/en/function.class-uses.php

<?php

trait CanWhatever
{
    public function doStuff()
    {
        return 'result!';
    }
}

class X
{
    use CanWhatever;

    public function doStuff()
    {
        return 'overridden!';
    }
}

$x = new X();
echo $x->doStuff();
echo "\n\$x has ";
echo (class_uses($x, 'CanWhatever')) ? 'the trait' : 'no trait';

Выводится:

overridden! 
$x has the trait

Что вы можете увидеть здесь https://3v4l.org/Vin2H

Тем не менее, доктрина Аннотации могут по-прежнему собирать DocBlock из свойства, а не переопределенного метода, поэтому я не могу дать вам окончательный ответ. Вам просто нужно попробовать и посмотреть!

Ответ 3

У меня была аналогичная проблема и решить ее, переопределив ее свойство:

use UserAwareTrait;
/**
 * @var EntrantInterface
 * @ORM\ManyToOne(targetEntity="ReusableBundle\Model\EntrantInterface"inversedBy="entries")
 */
protected $user;