10

Prerequisites:

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

I wonder if it's possible to override an inversedBy attribute of a property association mapping that's taken from a trait.

An interface that I use as a concrete user entity placeholder:

ReusableBundle\ModelEntrantInterface.php

interface EntrantInterface
{
    public function getEmail();

    public function getFirstName();

    public function getLastName();
}
  1. The following architecture works just fine (need to create User entity that implements EntrantInterface and all other entities that are derived from these abstract classes in 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...
}

And couple more entities with similar structure that utilize EntranInterface

  1. And this is what I want to achieve - UserAwareTrait to be reusable across several entities:

ReusableBundle\Entity\Traits\UserAwareTrait.php

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

    // getter/setter...
}

In Doctrine 2.6 if I would use super class and wanted to override its property I'd do this:

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

But if I want that Entity to use UserAwareTrait and override association mapping of a property...

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

... and run php bin/console doctrine:schema:validate I see this error in the console:

[Doctrine\ORM\Mapping\MappingException]
Invalid field override named 'user' for class 'ReusableBundle\Entity\Entry'.

Is there a workaround that I could follow to achieve the desired result?

  1. Use trait to store shared properties

  2. Override assotiation mapping or (possibly) attributes mapping in the class that uses that trait

genesst
  • 1,333
  • 1
  • 10
  • 39
  • Well I have a similar implementation but in my case the class that uses the trait is not a `MappedSuperclass`. Did you tried this with deleting the `@ORM\MappedSuperclass` line over your `abstract class Entry` ? And more important: You try to override the a relation without specifing it (in this case `ManyToOne`. Try: `@ORM\AssociationOverrides({ @ORM\AssociationOverride(name="user", manyToMany=@ORM\ManyToOne(targetEntity="ReusableBundle\Model\EntrantInterface", inversedBy="entities")) })`. – goulashsoup Sep 26 '17 at 12:13
  • Also in your last code block it is `inversedBy="entries"` which is the same as the original and in your second last it is `inversedBy="entities"`... – goulashsoup Sep 26 '17 at 12:15

3 Answers3

2

TL;DR You should change the access modificator from protected to private. Don't forget that you will not be able to directly manipulate the private property in a subclass and will need a getter.

The exception appears due to the bug (I believe, or a quirk of the behavior) in the AnnotationDriver.

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

It skips all non-private properties for MappedSuperclass letting them to compose metadata on the subclass parsing. But when it comes to overriding the driver tries to do it at a MappedSuperclass level, it doesn't remember that the property was skipped, fails to find it in the metadata and raise an exception.

I made a detailed explanation at the issue. You can find there also the link to the unit tests that highlight the case.

origaminal
  • 2,065
  • 1
  • 11
  • 19
0

You'll have to try this in your own code to see, but it could be possible.

As an experiment, I overridden a trait in a class, then checked for the trait using 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';

This outputs:

overridden! 
$x has the trait

Which you can see here https://3v4l.org/Vin2H

However, Doctrine Annotations may still pick up the DocBlock from the trait proper rather than the overridden method, which is why I can't give you a definitive answer. You just need to try it and see!

delboy1978uk
  • 12,118
  • 2
  • 21
  • 39
  • Thanks for the answer, this is not what I need. I don’t want to override a method (I would just define it in each class then and annotate individually, and this exactly what I wish to avoid to do). And there’s nothing to override in the body of methods, those just getter and setter. – genesst Sep 25 '17 at 19:48
0

I had a similiar problem and solve it by override the property it self:

use UserAwareTrait;
/**
 * @var EntrantInterface
 * @ORM\ManyToOne(targetEntity="ReusableBundle\Model\EntrantInterface"inversedBy="entries")
 */
protected $user;
Gladhon
  • 211
  • 2
  • 5
  • 1
    Please read carefully, I want to reuse the trait in multiple entities, therefore there’s no chance to override it in trait, inversedBy must have different values for each entity – genesst Sep 29 '17 at 20:47
  • 1
    Yes, my answer means to override the trait property in each entity, to set a proper inversed by – Gladhon Sep 30 '17 at 21:21