123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252 |
- Handling Annotations
- ====================
- There are several different approaches to handling annotations in PHP.
- Doctrine Annotations maps docblock annotations to PHP classes. Because
- not all docblock annotations are used for metadata purposes a filter is
- applied to ignore or skip classes that are not Doctrine annotations.
- Take a look at the following code snippet:
- .. code-block:: php
- namespace MyProject\Entities;
- use Doctrine\ORM\Mapping AS ORM;
- use Symfony\Component\Validator\Constraints AS Assert;
- /**
- * @author Benjamin Eberlei
- * @ORM\Entity
- * @MyProject\Annotations\Foobarable
- */
- class User
- {
- /**
- * @ORM\Id @ORM\Column @ORM\GeneratedValue
- * @dummy
- * @var int
- */
- private $id;
- /**
- * @ORM\Column(type="string")
- * @Assert\NotEmpty
- * @Assert\Email
- * @var string
- */
- private $email;
- }
- In this snippet you can see a variety of different docblock annotations:
- - Documentation annotations such as ``@var`` and ``@author``. These
- annotations are ignored and never considered for throwing an
- exception due to wrongly used annotations.
- - Annotations imported through use statements. The statement ``use
- Doctrine\ORM\Mapping AS ORM`` makes all classes under that namespace
- available as ``@ORM\ClassName``. Same goes for the import of
- ``@Assert``.
- - The ``@dummy`` annotation. It is not a documentation annotation and
- not ignored. For Doctrine Annotations it is not entirely clear how
- to handle this annotation. Depending on the configuration an exception
- (unknown annotation) will be thrown when parsing this annotation.
- - The fully qualified annotation ``@MyProject\Annotations\Foobarable``.
- This is transformed directly into the given class name.
- How are these annotations loaded? From looking at the code you could
- guess that the ORM Mapping, Assert Validation and the fully qualified
- annotation can just be loaded using
- the defined PHP autoloaders. This is not the case however: For error
- handling reasons every check for class existence inside the
- ``AnnotationReader`` sets the second parameter $autoload
- of ``class_exists($name, $autoload)`` to false. To work flawlessly the
- ``AnnotationReader`` requires silent autoloaders which many autoloaders are
- not. Silent autoloading is NOT part of the `PSR-0 specification
- <https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md>`_
- for autoloading.
- This is why Doctrine Annotations uses its own autoloading mechanism
- through a global registry. If you are wondering about the annotation
- registry being global, there is no other way to solve the architectural
- problems of autoloading annotation classes in a straightforward fashion.
- Additionally if you think about PHP autoloading then you recognize it is
- a global as well.
- To anticipate the configuration section, making the above PHP class work
- with Doctrine Annotations requires this setup:
- .. code-block:: php
- use Doctrine\Common\Annotations\AnnotationReader;
- use Doctrine\Common\Annotations\AnnotationRegistry;
- AnnotationRegistry::registerFile("/path/to/doctrine/lib/Doctrine/ORM/Mapping/Driver/DoctrineAnnotations.php");
- AnnotationRegistry::registerAutoloadNamespace("Symfony\Component\Validator\Constraint", "/path/to/symfony/src");
- AnnotationRegistry::registerAutoloadNamespace("MyProject\Annotations", "/path/to/myproject/src");
- $reader = new AnnotationReader();
- AnnotationReader::addGlobalIgnoredName('dummy');
- The second block with the annotation registry calls registers all the
- three different annotation namespaces that are used.
- Doctrine Annotations saves all its annotations in a single file, that is
- why ``AnnotationRegistry#registerFile`` is used in contrast to
- ``AnnotationRegistry#registerAutoloadNamespace`` which creates a PSR-0
- compatible loading mechanism for class to file names.
- In the third block, we create the actual ``AnnotationReader`` instance.
- Note that we also add ``dummy`` to the global list of ignored
- annotations for which we do not throw exceptions. Setting this is
- necessary in our example case, otherwise ``@dummy`` would trigger an
- exception to be thrown during the parsing of the docblock of
- ``MyProject\Entities\User#id``.
- Setup and Configuration
- -----------------------
- To use the annotations library is simple, you just need to create a new
- ``AnnotationReader`` instance:
- .. code-block:: php
- $reader = new \Doctrine\Common\Annotations\AnnotationReader();
- This creates a simple annotation reader with no caching other than in
- memory (in php arrays). Since parsing docblocks can be expensive you
- should cache this process by using a caching reader.
- To cache annotations, you can create a ``Doctrine\Common\Annotations\PsrCachedReader``.
- This reader decorates the original reader and stores all annotations in a PSR-6
- cache:
- .. code-block:: php
- use Doctrine\Common\Annotations\AnnotationReader;
- use Doctrine\Common\Annotations\PsrCachedReader;
- $cache = ... // instantiate a PSR-6 Cache pool
- $reader = new PsrCachedReader(
- new AnnotationReader(),
- $cache,
- $debug = true
- );
- The ``debug`` flag is used here as well to invalidate the cache files
- when the PHP class with annotations changed and should be used during
- development.
- .. warning ::
- The ``AnnotationReader`` works and caches under the
- assumption that all annotations of a doc-block are processed at
- once. That means that annotation classes that do not exist and
- aren't loaded and cannot be autoloaded (using the
- AnnotationRegistry) would never be visible and not accessible if a
- cache is used unless the cache is cleared and the annotations
- requested again, this time with all annotations defined.
- By default the annotation reader returns a list of annotations with
- numeric indexes. If you want your annotations to be indexed by their
- class name you can wrap the reader in an ``IndexedReader``:
- .. code-block:: php
- use Doctrine\Common\Annotations\AnnotationReader;
- use Doctrine\Common\Annotations\IndexedReader;
- $reader = new IndexedReader(new AnnotationReader());
- .. warning::
- You should never wrap the indexed reader inside a cached reader,
- only the other way around. This way you can re-use the cache with
- indexed or numeric keys, otherwise your code may experience failures
- due to caching in a numerical or indexed format.
- Registering Annotations
- ~~~~~~~~~~~~~~~~~~~~~~~
- As explained in the introduction, Doctrine Annotations uses its own
- autoloading mechanism to determine if a given annotation has a
- corresponding PHP class that can be autoloaded. For annotation
- autoloading you have to configure the
- ``Doctrine\Common\Annotations\AnnotationRegistry``. There are three
- different mechanisms to configure annotation autoloading:
- - Calling ``AnnotationRegistry#registerFile($file)`` to register a file
- that contains one or more annotation classes.
- - Calling ``AnnotationRegistry#registerNamespace($namespace, $dirs =
- null)`` to register that the given namespace contains annotations and
- that their base directory is located at the given $dirs or in the
- include path if ``NULL`` is passed. The given directories should *NOT*
- be the directory where classes of the namespace are in, but the base
- directory of the root namespace. The AnnotationRegistry uses a
- namespace to directory separator approach to resolve the correct path.
- - Calling ``AnnotationRegistry#registerLoader($callable)`` to register
- an autoloader callback. The callback accepts the class as first and
- only parameter and has to return ``true`` if the corresponding file
- was found and included.
- .. note::
- Loaders have to fail silently, if a class is not found even if it
- matches for example the namespace prefix of that loader. Never is a
- loader to throw a warning or exception if the loading failed
- otherwise parsing doc block annotations will become a huge pain.
- A sample loader callback could look like:
- .. code-block:: php
- use Doctrine\Common\Annotations\AnnotationRegistry;
- use Symfony\Component\ClassLoader\UniversalClassLoader;
- AnnotationRegistry::registerLoader(function($class) {
- $file = str_replace("\\", DIRECTORY_SEPARATOR, $class) . ".php";
- if (file_exists("/my/base/path/" . $file)) {
- // file_exists() makes sure that the loader fails silently
- require "/my/base/path/" . $file;
- }
- });
- $loader = new UniversalClassLoader();
- AnnotationRegistry::registerLoader(array($loader, "loadClass"));
- Ignoring missing exceptions
- ~~~~~~~~~~~~~~~~~~~~~~~~~~~
- By default an exception is thrown from the ``AnnotationReader`` if an
- annotation was found that:
- - is not part of the list of ignored "documentation annotations";
- - was not imported through a use statement;
- - is not a fully qualified class that exists.
- You can disable this behavior for specific names if your docblocks do
- not follow strict requirements:
- .. code-block:: php
- $reader = new \Doctrine\Common\Annotations\AnnotationReader();
- AnnotationReader::addGlobalIgnoredName('foo');
- PHP Imports
- ~~~~~~~~~~~
- By default the annotation reader parses the use-statement of a php file
- to gain access to the import rules and register them for the annotation
- processing. Only if you are using PHP Imports can you validate the
- correct usage of annotations and throw exceptions if you misspelled an
- annotation. This mechanism is enabled by default.
- To ease the upgrade path, we still allow you to disable this mechanism.
- Note however that we will remove this in future versions:
- .. code-block:: php
- $reader = new \Doctrine\Common\Annotations\AnnotationReader();
- $reader->setEnabledPhpImports(false);
|