1Handling Annotations 2==================== 3 4There are several different approaches to handling annotations in PHP. Doctrine Annotations 5maps docblock annotations to PHP classes. Because not all docblock annotations are used 6for metadata purposes a filter is applied to ignore or skip classes that are not Doctrine annotations. 7 8Take a look at the following code snippet: 9 10.. code-block:: php 11 12 namespace MyProject\Entities; 13 14 use Doctrine\ORM\Mapping AS ORM; 15 use Symfony\Component\Validation\Constraints AS Assert; 16 17 /** 18 * @author Benjamin Eberlei 19 * @ORM\Entity 20 * @MyProject\Annotations\Foobarable 21 */ 22 class User 23 { 24 /** 25 * @ORM\Id @ORM\Column @ORM\GeneratedValue 26 * @dummy 27 * @var int 28 */ 29 private $id; 30 31 /** 32 * @ORM\Column(type="string") 33 * @Assert\NotEmpty 34 * @Assert\Email 35 * @var string 36 */ 37 private $email; 38 } 39 40In this snippet you can see a variety of different docblock annotations: 41 42- 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. 43- 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``. 44- 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. 45- The fully qualified annotation ``@MyProject\Annotations\Foobarable``. This is transformed directly into the given class name. 46 47How 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 48the 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 49of ``class_exists($name, $autoload)`` to false. To work flawlessly the AnnotationReader requires silent autoloaders which many autoloaders are not. Silent autoloading is NOT 50part of the `PSR-0 specification <https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md>`_ for autoloading. 51 52This is why Doctrine Annotations uses its own autoloading mechanism through a global registry. If you are wondering about the annotation registry being global, 53there is no other way to solve the architectural problems of autoloading annotation classes in a straightforward fashion. Additionally if you think about PHP 54autoloading then you recognize it is a global as well. 55 56To anticipate the configuration section, making the above PHP class work with Doctrine Annotations requires this setup: 57 58.. code-block:: php 59 60 use Doctrine\Common\Annotations\AnnotationReader; 61 use Doctrine\Common\Annotations\AnnotationRegistry; 62 63 AnnotationRegistry::registerFile("/path/to/doctrine/lib/Doctrine/ORM/Mapping/Driver/DoctrineAnnotations.php"); 64 AnnotationRegistry::registerAutoloadNamespace("Symfony\Component\Validator\Constraint", "/path/to/symfony/src"); 65 AnnotationRegistry::registerAutoloadNamespace("MyProject\Annotations", "/path/to/myproject/src"); 66 67 $reader = new AnnotationReader(); 68 AnnotationReader::addGlobalIgnoredName('dummy'); 69 70The second block with the annotation registry calls registers all the three different annotation namespaces that are used. 71Doctrine saves all its annotations in a single file, that is why ``AnnotationRegistry#registerFile`` is used in contrast to 72``AnnotationRegistry#registerAutoloadNamespace`` which creates a PSR-0 compatible loading mechanism for class to file names. 73 74In the third block, we create the actual AnnotationReader instance. Note that we also add "dummy" to the global list of annotations 75for which we do not throw exceptions. Setting this is necessary in our example case, otherwise ``@dummy`` would trigger an exception to 76be thrown during the parsing of the docblock of ``MyProject\Entities\User#id``. 77 78Setup and Configuration 79----------------------- 80 81To use the annotations library is simple, you just need to create a new ``AnnotationReader`` instance: 82 83.. code-block:: php 84 85 $reader = new \Doctrine\Common\Annotations\AnnotationReader(); 86 87This creates a simple annotation reader with no caching other than in memory (in php arrays). 88Since parsing docblocks can be expensive you should cache this process by using 89a caching reader. 90 91You can use a file caching reader: 92 93.. code-block:: php 94 95 use Doctrine\Common\Annotations\FileCacheReader; 96 use Doctrine\Common\Annotations\AnnotationReader; 97 98 $reader = new FileCacheReader( 99 new AnnotationReader(), 100 "/path/to/cache", 101 $debug = true 102 ); 103 104If you set the debug flag to true the cache reader will check for changes in the original files, which 105is very important during development. If you don't set it to true you have to delete the directory to clear the cache. 106This gives faster performance, however should only be used in production, because of its inconvenience 107during development. 108 109You can also use one of the ``Doctrine\Common\Cache\Cache`` cache implementations to cache the annotations: 110 111.. code-block:: php 112 113 use Doctrine\Common\Annotations\AnnotationReader; 114 use Doctrine\Common\Annotations\CachedReader; 115 use Doctrine\Common\Cache\ApcCache; 116 117 $reader = new CachedReader( 118 new AnnotationReader(), 119 new ApcCache(), 120 $debug = true 121 ); 122 123The debug flag is used here as well to invalidate the cache files when the PHP class with annotations changed 124and should be used during development. 125 126.. warning :: 127 128 The AnnotationReader works and caches under the 129 assumption that all annotations of a doc-block are processed at 130 once. That means that annotation classes that do not exist and 131 aren't loaded and cannot be autoloaded (using the AnnotationRegistry) would never be visible and not 132 accessible if a cache is used unless the cache is cleared and the 133 annotations requested again, this time with all annotations 134 defined. 135 136By default the annotation reader returns a list of annotations with numeric indexes. If you want your annotations 137to be indexed by their class name you can wrap the reader in an IndexedReader: 138 139.. code-block:: php 140 141 use Doctrine\Common\Annotations\AnnotationReader; 142 use Doctrine\Common\Annotations\IndexedReader; 143 144 $reader = new IndexedReader(new AnnotationReader()); 145 146.. warning:: 147 148 You should never wrap the indexed reader inside a cached reader only the other way around. This way you can re-use 149 the cache with indexed or numeric keys, otherwise your code may experience failures due to caching in an numerical 150 or indexed format. 151 152Registering Annotations 153~~~~~~~~~~~~~~~~~~~~~~~ 154 155As explained in the Introduction Doctrine Annotations uses its own autoloading mechanism to determine if a 156given annotation has a corresponding PHP class that can be autoloaded. For Annotation Autoloading you have 157to configure the ``Doctrine\Common\Annotations\AnnotationRegistry``. There are three different mechanisms 158to configure annotation autoloading: 159 160- Calling ``AnnotationRegistry#registerFile($file)`` to register a file that contains one or more Annotation classes. 161- Calling ``AnnotationRegistry#registerNamespace($namespace, $dirs = null)`` to register that the given namespace 162 contains annotations and that their base directory is located at the given $dirs or in the include path if NULL is passed. 163 The given directories should *NOT* be the directory where classes of the namespace are in, but the base directory 164 of the root namespace. The AnnotationRegistry uses a namespace to directory separator approach to resolve the correct path. 165- Calling ``AnnotationRegistry#registerLoader($callable)`` to register an autoloader callback. The callback accepts the 166 class as first and only parameter and has to return true if the corresponding file was found and included. 167 168.. note:: 169 170 Loaders have to fail silently, if a class is not found even if it matches for example the namespace prefix of that loader. 171 Never is a loader to throw a warning or exception if the loading failed otherwise parsing doc block annotations will become 172 a huge pain. 173 174A sample loader callback could look like: 175 176.. code-block:: php 177 178 use Doctrine\Common\Annotations\AnnotationRegistry; 179 use Symfony\Component\ClassLoader\UniversalClassLoader; 180 181 AnnotationRegistry::registerLoader(function($class) { 182 $file = str_replace("\\", DIRECTORY_SEPARATOR, $class) . ".php"; 183 184 if (file_exists("/my/base/path/" . $file)) { 185 // file exists makes sure that the loader fails silently 186 require "/my/base/path/" . $file; 187 } 188 }); 189 190 $loader = new UniversalClassLoader(); 191 AnnotationRegistry::registerLoader(array($loader, "loadClass")); 192 193 194Ignoring missing exceptions 195~~~~~~~~~~~~~~~~~~~~~~~~~~~ 196 197By default an exception is thrown from the AnnotationReader if an annotation was found that: 198 199- Is not part of the blacklist of ignored "documentation annotations". 200- Was not imported through a use statement 201- Is not a fully qualified class that exists 202 203You can disable this behavior for specific names if your docblocks do not follow strict requirements: 204 205.. code-block:: php 206 207 $reader = new \Doctrine\Common\Annotations\AnnotationReader(); 208 AnnotationReader::addGlobalIgnoredName('foo'); 209 210PHP Imports 211~~~~~~~~~~~ 212 213By default the annotation reader parses the use-statement of a php file to gain access to the import rules 214and register them for the annotation processing. Only if you are using PHP Imports you can validate the correct 215usage of annotations and throw exceptions if you misspelled an annotation. This mechanism is enabled by default. 216 217To ease the upgrade path, we still allow you to disable this mechanism. Note however that we will remove this 218in future versions: 219 220.. code-block:: php 221 222 $reader = new \Doctrine\Common\Annotations\AnnotationReader(); 223 $reader->setEnabledPhpImports(false); 224