fridde
fridde

Reputation: 522

Symfony Doctrine: Adding new new Annotation namespaces to an existing reader

I have implemented my own Doctrine\Common\EventSubscriber that subscribes to "onFlush" events and compares the EntityChangeSet to a hard coded list of class properties (like User->Name and Event->Date) and logs the change if necessary.

As property names of the other classes might change, I'd much rather prefer to annotate these properties with a custom made @Loggable.

I have built the Annotation class "Loggable", added use MyNamespace\Annotations\Loggable; to the User and Event classes and have a method in my EventSubscriber that creates a Doctrine\Common\Annotations\AnnotationReader and uses its getPropertyAnnotation($property, 'Loggable') to check for a non-null value.

Now to the problem

As one could expect (or not), this particular reader throws

[Semantical Error] The annotation "@Id" in property MyNamespace\Entities\User::$id was never imported.

and is not aware of any other ORM-annotations that the reader in my EntityManager knows about.

Do I actually have to add use Doctrine\ORM\Mapping as ORM; to every Entity class and prefix every ORM-annotation with ORM\ just to please this newly created reader or is there a way to reuse the reader in my EntityManager (a Doctrine\Common\Annotations\SimpleAnnotationReader by default, if I understood correctly?)

I did my research and read through most of the answers related to Doctrine and Annotations, but I seem to be missing some conceptual understanding. Maybe someone can point me in the right direction?

Upvotes: 0

Views: 1473

Answers (2)

fridde
fridde

Reputation: 522

Thanks to @Frank Gamess' explanations I finally managed to wade through the jungle of Doctrine's codebase and find a messy, but working solution, that doesn't involve explicit namespaces for every file.

Instead of using the simpler Setup::createAnnotationMetadataConfiguration() when creating my EntityManager, I went a bit more verbose and performed most of the steps of the configuration myself:

// preloading all of Doctrine's Annotation classes
$rc = new \ReflectionClass(Configuration::class);
$dir = dirname($rc->getFileName());

AnnotationRegistry::registerFile($dir . '/Mapping/Driver/DoctrineAnnotations.php');

// preloading my own classes
$dir = $dir ?? self::BASE_DIR . '/src/Annotations';  // BASE_DIR is my project root directory
// CustomAnnotations.php is built the same way as DoctrineAnnotations.php;
AnnotationRegistry::registerFile($dir . '/CustomAnnotations.php');

$reader = new SimpleAnnotationReader();
$reader->addNamespace('Doctrine\ORM\Mapping');
$reader->addNamespace('MyProject\Annotations');

$mapping_driver = new AnnotationDriver($reader, [self::BASE_DIR . '/src/Entities']);

$config = Setup::createConfiguration();
$config->setMetadataDriverImpl($mapping_driver);
$config->setAutoGenerateProxyClasses(true);

$EM = EntityManager::create($db_params, $config, new EventManager());

// adding the objects to my ORM-objects for later use
$this->annotation_reader = $reader;
$this->EM = $EM;

Now I can use my annotations without any use-statements or FQCN. Maybe not cleaner, but less repetitive.

As this works, I might be able to go on to more urgent things, but if anyone can point out possibilities for refactoring, I'm all ears!

Upvotes: 0

fgamess
fgamess

Reputation: 1324

Basically, the AnnotationReader class use getClassAnnotations and getClassAnnotation (which use getClassAnnotations in fact).

getClassAnnotations:

public function getClassAnnotations(ReflectionClass $class)
{
    $this->parser->setTarget(Target::TARGET_CLASS);
    $this->parser->setImports($this->getClassImports($class));
    $this->parser->setIgnoredAnnotationNames($this->getIgnoredAnnotationNames($class));
    $this->parser->setIgnoredAnnotationNamespaces(self::$globalIgnoredNamespaces);

    return $this->parser->parse($class->getDocComment(), 'class ' . $class->getName());
}

getClassAnnotation:

public function getClassAnnotation(ReflectionClass $class, $annotationName)
{
    $annotations = $this->getClassAnnotations($class);

    foreach ($annotations as $annotation) {
        if ($annotation instanceof $annotationName) {
            return $annotation;
        }
    }

    return null;
}

Just take a look at this line:

$this->parser->setImports($this->getClassImports($class));

The annotation reader collects the use statements in order to detect any other annotation that you use. So, about your question

Do I actually have to add use Doctrine\ORM\Mapping as ORM; to every Entity class and prefix every ORM-annotation with ORM\ [...]?

I should answer: yes, you do.

Upvotes: 2

Related Questions