Переопределить сопоставление ассоциации свойства черты в Doctrine 2.6

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

  • PHP 7.1.8
  • Симфония 3.3.9
  • Доктрина 2.6.x-dev

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

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

ReusableBundle\ModelEntrantInterface.php

interface EntrantInterface
{
    public function getEmail();

    public function getFirstName();

    public function getLastName();
}
  1. Следующая архитектура работает просто отлично (необходимо создать сущность 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\Traits\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...
}

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

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

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

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

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

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

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


person genesst    schedule 21.09.2017    source источник
comment
Что ж, у меня есть похожая реализация, но в моем случае класс, который использует черту, не является MappedSuperclass. Вы пробовали это, удалив строку @ORM\MappedSuperclass над abstract class Entry? И что еще более важно: вы пытаетесь переопределить отношение без его указания (в данном случае ManyToOne. Попробуйте: @ORM\AssociationOverrides({ @ORM\AssociationOverride(name="user", manyToMany=@ORM\ManyToOne(targetEntity="ReusableBundle\Model\EntrantInterface", inversedBy="entities")) }).   -  person goulashsoup    schedule 26.09.2017
comment
Также в вашем последнем блоке кода это inversedBy="entries", который совпадает с оригиналом, а во втором последнем блоке это inversedBy="entities"...   -  person goulashsoup    schedule 26.09.2017


Ответы (3)


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

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

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

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

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

person origaminal    schedule 29.09.2017

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

В качестве эксперимента я переопределил трейт в классе, а затем проверил трейт, используя 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

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

person delboy1978uk    schedule 25.09.2017
comment
Спасибо за ответ, это не то, что мне нужно. Я не хочу переопределять метод (тогда я бы просто определил его в каждом классе и аннотировал отдельно, и именно этого я хочу избежать). И в теле методов нечего переопределять, это только геттеры и сеттеры. - person genesst; 25.09.2017

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

use UserAwareTrait;
/**
 * @var EntrantInterface
 * @ORM\ManyToOne(targetEntity="ReusableBundle\Model\EntrantInterface"inversedBy="entries")
 */
protected $user;
person Gladhon    schedule 29.09.2017
comment
Пожалуйста, прочитайте внимательно, я хочу повторно использовать трейт в нескольких сущностях, поэтому нет возможности переопределить его в трейте, inversedBy должен иметь разные значения для каждого объекта. - person genesst; 29.09.2017
comment
Да, мой ответ означает переопределить свойство типажей в каждой сущности, чтобы установить правильную инверсию - person Gladhon; 01.10.2017