Home > Net >  How to inherit custom Doctrine class annotation in Symfony?
How to inherit custom Doctrine class annotation in Symfony?

Time:09-28

I have created a custom @TimestampAware annotation which can be used to automatically update a timestamp property when persisting or updating my entities (see code below).

When using this annotation directly on an entity class everything works fine. However, when using the annotation on a base class, it is not recognized on the inherited sub-classes.

/**
 * @TimestampAware
 */
class SomeEntity { ... }  // works fine

 /**
 * @TimestampAware
 */
class BaseEntity { ... }

class SubEntity extends BaseEntity { ... } // Annotation is not recognized

Is it the intended behavior of Doctrine annotations and the Annotation Reader class to only look for annotation directly on the current class and no include its parent classes? Or is there something wrong with my implementation?

My annotation:

use Doctrine\Common\Annotations\Annotation;
use Doctrine\Common\Annotations\Reader;

/**
 * @Annotation
 * @Target("CLASS")
 */
final class TimestampAware { }

The annotation listener:

use Doctrine\Common\EventSubscriber;

class TimestampAwareSubscriber implements EventSubscriber {
    protected $reader;
    protected $logger;

    public function __construct(Reader $reader, LoggerInterface $logger) {
        $this->reader = $reader;
        $this->logger = $logger;
    }

    public function getSubscribedEvents() {
        return [
            Events::prePersist,
            Events::preUpdate,
        ];
    }

    public function prePersist(LifecycleEventArgs $args) {
        $this->onPersistOrUpdate($args);
    }    

    public function preUpdate(LifecycleEventArgs $args) {
        $this->onPersistOrUpdate($args);
    }    

    protected function onPersistOrUpdate(LifecycleEventArgs $args) {
        $this->logger->info("Reader: ".get_class($this->reader));
 
        $entity = $args->getEntity();
        $reflection = new \ReflectionClass($entity);
    
        $timestampAware = $this->reader->getClassAnnotation(
            $reflection,
            TimestampAware::class
        );
    
        if (!$timestampAware) {
            return;
        }

        // update timestamp...
    }
}

CodePudding user response:

The Annotation Reader inspects only the relevant class, it does not read the annotations of the parent class. This can easily be checked:

use Doctrine\Common\Annotations\AnnotationReader;
use Doctrine\Common\Annotations\AnnotationRegistry;

AnnotationRegistry::registerLoader('class_exists');

/**
 * @Annotation
 * @Target("CLASS")
 */
class Annotated {}

/**
 * @Annotated
 **/
class ParentFoo {}

class ChildFoo extends ParentFoo {}

$reader = new AnnotationReader();

$parentAnnotated = $reader->getClassAnnotation(
    new ReflectionClass(ParentFoo::class),
    Annotated::class
);

var_dump($parentAnnotated);
// Outputs `object(Annotated)#10 (0) {}`

$childAnnotated = $reader->getClassAnnotation(
    new ReflectionClass(ChildFoo::class),
    Annotated::class
);

var_dump($childAnnotated);

// outputs `null`

If you want to check parent classes, you'll have to do it yourself. ReflectionClass provides the getParentClass() method which you could do to check the class hierarchy.

Note: I found this package that claims to extend annotations so that they are usable with inheritance directly. Haven't checked if it's any good.

CodePudding user response:

In addition to the great answer by @yivi I would like to share the code I used to solve the problem. Maybe this helps others how encounter the same problem:

protected function onPersistOrUpdate(LifecycleEventArgs $args) {
    $this->logger->info("Reader: ".get_class($this->reader));

    $entity = $args->getEntity();
    $reflection = new \ReflectionClass($entity);

    $timestampAware = $this->reader->getClassAnnotation(
        $reflection,
        TimestampAware::class
    );
    
    while (!$timestampAware && $reflection = $reflection->getParentClass()) {
        $timestampAware = $this->reader->getClassAnnotation(
            $reflection,
            TimestampLogAware::class
        );
    }

    if (!$timestampAware) {
        return;
    }

    // update timestamp...
}
  • Related