When developing enterprise software one should always keep an eye on writing code that is easily maintainable, testable and extendable. Design patterns already propose a way to implement a loosely coupled architecture. With annotations you can take a step forward to make your code look even more expressive, focusing on the primary problem instead of writing a lot of boilerplate code. A single comment line can save you many more lines of code but as it is written in the project’s domain language it will be much more meaningful to the developer.
What are annotations?
Annotations are meta information describing classes, properties, methods or functions. They are always put in comments above the structure that they describe. The most common annotations every PHP developer should know are the ones used to document classes and their members. Annotation identifiers always start with an @ sign, there’s no other formatting rules.
Example: A class and a method documented with the annotations used by phpDocumentor
Many of you will know that phpDocumentor and modern IDEs use annotations to determine method parameter types (@param), return values (@return) and so on.
But there are many more use cases than that: validating entities before saving them to a database or another storage, setting meta information on how to persist data or implementing ACL on classes and methods.
Many more examples can be found in PHP libraries such as PHPUnit, which uses annotations to group tests, define dependencies between single tests and even to create tests directly in the code that will be tested.
Example: Using the @assert annotation to define tests directly in the code
Entities used by the Doctrine2 Object Relational Mapper are completely decoupled from the framework by using annotations. The information about how to persist an entity is set via annotations, allowing entities to be plain PHP classes without a need to extend a base entity class from the ORM.
Example: Defining entity persist information to use a plain PHP class with the Doctrine2 ORM
There is a variety of PHP annotation reader libraries out there, but I am going to use the Doctrine reader in this example. It is used by Doctrine2 ORM, FLOW3 and others to read annotations for a given Reflection object (class/method/function etc.). The Doctrine Common package includes the reader and the class loader for it, so there’s no need to download the whole ORM package from the Doctrine website.
We start by setting up the class loader for the Common package:
Now that the autoloader is able to load the Doctrine classes, it’s time to define the annotations that we want to use. For the Doctrine annotation reader, annotations must be defined as classes (you should put them in a namespace), where the class name is the name of the annotation. Public properties will be set as properties in the annotation.
What happens when the reader scans for annotations is that it will instantiate the classes and set the properties in there.
So in this example a valid annotation would look like:
To evaluate the annotations you need to obtain an instance of the reader and a reflection object, but first things first. The easiest way to get a reader is to instanciate the AnnotationReader class:
$reader = new \Doctrine\Common\Annotations\AnnotationReader();
But that way wouldn’t be the best during development and especially not in production. Reflection is always very expensive so the results should be cached in either a file or (most likely in production) APC. The Doctrine Common package already contains classes and adapters to implement this behaviour. Using a file cache, the creation of the reader object looks like:
Now we need a class to test our new annotation. Let’s say we have a user object and want the user’s name to be required and the type should always be a string.
Of course, the example above could also be rewritten to be used with classes, methods and functions. In many use cases it could also be very beneficial to implement a fallback from method to class annotations.
That’s it and you should now be able to use custom annotations in your application. I would love to know about your own use cases, feel free to add them to this article using the comment form.