2

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...
    }
}
1
  • It's the use Doctrine\Common\Annotations\Reader which is injeced using autowire. When logging get_class($this->reader) it outputs Doctrine\Common\Annotations\PsrCachedReader Commented Sep 28, 2021 at 9:26

2 Answers 2

2

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

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.

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

Sign up to request clarification or add additional context in comments.

1 Comment

Thanks a lot for the good answer. This makes things much clearer. I have not checked the linked packed in detail, but it looks promising. However, in may case it was easier to implement the search in the parent classes myself. I will add an answer to share the code, maybe it helps other.
1

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...
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.