Skip to content

Commit

Permalink
Merge branch '1.8' into 1.9
Browse files Browse the repository at this point in the history
* 1.8:
  Add precisions about annotations autoloading
  Add a test and fix the behavior of issue #141
  fix ignored annotations with namespace alias
  Refer to annotation parser with "it"
  Use more precise language
  Use a more appopriate verb
  Improve style
  add missing word
  grammar
  syntax
  Makes sure function is recognized as such
  Use lowercase
  a vs an
  punctuation
  Improve formatting
  Wrap lines at 72 chars
  • Loading branch information
alcaeus committed Apr 1, 2020
2 parents dc73c46 + 53c09e9 commit 3b95eb9
Show file tree
Hide file tree
Showing 7 changed files with 304 additions and 123 deletions.
189 changes: 118 additions & 71 deletions docs/en/annotations.rst
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
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.
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:

Expand Down Expand Up @@ -39,21 +40,41 @@ Take a look at the following code snippet:
In this snippet you can see a variety of different docblock annotations:

- Documentation annotations such as ``@var`` and ``@author``. These annotations are on a blacklist 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 blacklisted. 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:
- Documentation annotations such as ``@var`` and ``@author``. These
annotations are on a blacklist 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 blacklisted. 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
Expand All @@ -67,26 +88,33 @@ To anticipate the configuration section, making the above PHP class work with Do
$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 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.
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 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``.
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:
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.
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.

You can use a file caching reader, but please note it is deprecated to
do so:
Expand All @@ -102,12 +130,15 @@ do so:
$debug = true
);
If you set the debug flag to true the cache reader will check for changes in the original files, which
is very important during development. If you don't set it to true you have to delete the directory to clear the cache.
This gives faster performance, however should only be used in production, because of its inconvenience
during development.
If you set the ``debug`` flag to ``true`` the cache reader will check
for changes in the original files, which is very important during
development. If you don't set it to ``true`` you have to delete the
directory to clear the cache. This gives faster performance, however
should only be used in production, because of its inconvenience during
development.

You can also use one of the ``Doctrine\Common\Cache\Cache`` cache implementations to cache the annotations:
You can also use one of the ``Doctrine\Common\Cache\Cache`` cache
implementations to cache the annotations:

.. code-block:: php
Expand All @@ -121,21 +152,23 @@ You can also use one of the ``Doctrine\Common\Cache\Cache`` cache implementation
$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.
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
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.
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:
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
Expand All @@ -146,31 +179,41 @@ to be indexed by their class name you can wrap the reader in an IndexedReader:
.. 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 an numerical
or indexed format.
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.
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.
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:

Expand All @@ -183,7 +226,7 @@ A sample loader callback could look like:
$file = str_replace("\\", DIRECTORY_SEPARATOR, $class) . ".php";
if (file_exists("/my/base/path/" . $file)) {
// file exists makes sure that the loader fails silently
// file_exists() makes sure that the loader fails silently
require "/my/base/path/" . $file;
}
});
Expand All @@ -195,13 +238,15 @@ A sample loader callback could look like:
Ignoring missing exceptions
~~~~~~~~~~~~~~~~~~~~~~~~~~~

By default an exception is thrown from the AnnotationReader if an annotation was found that:
By default an exception is thrown from the ``AnnotationReader`` if an
annotation was found that:

- Is not part of the blacklist of ignored "documentation annotations".
- Was not imported through a use statement
- Is not a fully qualified class that exists
- is not part of the blacklist 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:
You can disable this behavior for specific names if your docblocks do
not follow strict requirements:

.. code-block:: php
Expand All @@ -211,12 +256,14 @@ You can disable this behavior for specific names if your docblocks do not follow
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 you can validate the correct
usage of annotations and throw exceptions if you misspelled an annotation. This mechanism is enabled by default.
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:
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
Expand Down

0 comments on commit 3b95eb9

Please sign in to comment.