Tom Butler's programming blog

PHP: Annotations are an Abomination

Update 16/11/2017

Clarified note regarding Symfony.

Update 17/06/2017

I disliked the wording in parts of this article so I've restructured parts of it.

Annotations in php

There are increasingly more (mostly Symfony based) PHP projects out there that are making use of annotations for configuration (including dependency injection and routing). However, how anyone can think that teaching people this is acceptable or a good practice, is frankly, ridiculous. Those people developing such high profile frameworks should know better.

Take Symfony's routing class example (

use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;

PostController extends Controller
     * @Route("/")
public function indexAction()
// ...

I'm sure it works perfectly well. But so do global variables and singletons. It doesn't make them right and it certainly doesn't make them the best tool for the job.

There are many reasons using annotations is a bad idea. I'm going to use this routing class as an example, but all these issues are with annotations in general, not just Symfony's routing class.

Yes, I know in Symfony this approach is optional but the problems I highlight here are issues with annotations in principle, not only with this specific example. If you use annotations, you fall into the problems I present here. And for what? Just so you can store metadata in the same file as your code.

Preface: Annotations are the OOP equivalent of HTML's style attribute.

public function 
listAction() {

is the equivalent of

<div style="width: 120px;">

Just as the style attribute couples display information with the markup, annotations couple application configuration with application logic.

By using a CSS stylesheet instead of the style attribute, it's easy to substitute the CSS and apply different CSS styles to the pages. The CSS code can be organised in its own file and the same markup can even be used where no CSS is required.

Annotations do the same thing: They move application configuration, which is otherwise unrelated to the logic in the class (A class has no concept of a "route", only methods and properties) which is then used to configure another entirely unrelated class (in this case the router) somewhere else in the project. The class contains data/configuration that is used solely by another, entirely unrelated, class and a different tier of the architecture.

With either external CSS or external configuration, it's possible to easily replace the CSS/configuration. The entire look of the website or configuration of the router can be changed without needing to amend the HTML markup or the class itself.

While reading through the rest of this article, keep that in mind. More importantly though, I want to make it clear that:

The single benefit of annotations is the ability to edit application logic and application configuration in the same file

What can be achieved with annotations can also be achieved using a data structure hardcoded elsewhere in the application or loaded from a JSON/XML/Whatever configuration file.

Annotations are a form of convenience for the developer, and in some ways that I'll outline actually make configuration more difficult to change. As you're reading through the negatives introduced by annotations, keep weighing them up against this singular check in the pros column.

1) Changing comments should not break things

Every novice programmer is taught that comments are never executed. Making them integral for required functionality is ludicrous. If the comments get removed/altered then it becomes very difficult to work out why the application suddenly doesn't work.

Breaking this very well understood and fundamental language feature is absurd.

That said, annotations for IDE hinting are perfectly acceptable because the script still executes successfully whether they're there or not.

Yes, this is a minor thing, I'm starting small

2) Increased difficulty in development and debugging

A developer unfamiliar with the annotation meta-language will find it difficult to program in. There's no IDE hints, there's no IDE syntax checking, the syntax is unfamiliar to them. What's worse, because configuration is spread across multiple files, they can't even easily copy/paste examples from what's already there. Doing any debugging becomes impossible. You can't echo, var_dump() or debug_print_backtrace() in an annotation making it very difficult to debug.

When using Annotations, suddenly configuration is spread across multiple files. To use Symfony routing as an example, there's no way to get an overview of all the routes used by the application. When creating a new route, it's difficult to quickly see if it's already in use. It's impossible to see how the overall application is configured, which, in turn, means making any large scale structural changes means finding and altering every file rather than a single, central configuration. For instance, what if I wanted to restructure all the urls so that everything that was in /users/.../.../ becomes /customers/.../... if the configuration of those is spread amongst a dozen files that becomes a dozen files to locate then edit.

Again, this is something that can potentially be overcome but it does hint at a separation of concerns issue..

3) Not SOLID

In OOP we strive for SOLID code. The wikipedia page has a good explanation of SOLID principles for anyone unaware of this term.

Annotations break two of these.

3a) Single Responsibility principle

The S in solid.

the single responsibility principle states that every class should have a single responsibility, and that responsibility should be entirely encapsulated by the class. All its services should be narrowly aligned with that responsibility.

Annotations break this because when Annotations are used, the class provides both the behaviours of an object and the configuration for an entirely different part of the system (e.g. providing configuration for a router or a dependency injection container).

3b) Liskov substitution principle

The L in SOLID

It states that, in a computer program, if S is a subtype of T, then objects of type T may be replaced with objects of type S (i.e., objects of type S may substitute objects of type T) without altering any of the desirable properties of that program (correctness, task performed, etc.).

Annotations unquestionably break this because if a subclass does not provide identical annotations, then the subclass cannot be used in place of the parent class without altering the behaviour of the code. When using annotations, interfaces become irrelevant. We can't provide an interface and a implementation separately. If one implementation provides annotations and another doesn't we'll get a completely different result. Annotations effectively become part of the interface.

4) Separation of concerns/Encapsulation

The biggest issue is one of OOP theory. In OOP we strive for Separation of Concerns whereby one part of the system can have its implementation entirely changed without having knock-on effects anywhere else. To use the Symfony routing example, the routing table structure, internal terms/names and the entire routing mechanism should be able to be changed without affecting any existing controller code.

Annotations make this impossible. Using them means that if the syntax of the routing annotations is changed, every single class using them needs to change as well. With separated metadata, the metadata format can be altered entirely with ease and without needing amends to the code itself. E.g. replacing an XML routing table with a JSON routing table would be relatively painless. However, replacing annotation based routing with JSON routing becomes an ordeal.

This creates horrible backwards compatibility issues. A controller written for Symfony2.0 might not work in 2.1 because the syntax for annotation configuration has changed. This is ridiculous on every front. The configuration format should be able to change without breaking anything and will almost inevitably in future, support new features. Using annotation based configuration, the framework developers essentially shoot themselves in the foot because they can't change the syntax without breaking everyone's controllers! The application developers would then need to go through each of their controllers and adjust the syntax accordingly. With an XML, JSON or PHP based routing table, it's a change in one file to bring all outdated modules into a newer version of the framework.

Encapsulation is broken because the controller code knows about the router. By providing @Inject, the class knows about the dependency injection container. It provides configuration for a part of the application it should not even be aware exists.

Further to that, they break polymorphism entirely. Let's say I have a controller which uses annotations for its route. In OOP, I should be able to substitute this class with a child class or any other class which has the same interface. Annotations make this impossible because the annotations have become part of the class API and must be supplied alongside the class. In OOP we strive for polymorphism. I can interchange instances of different classes provided they have the same API. However, annotations break this. If I substitute a controller for a class which has an identical interface, it simply will not work unless it also supplies the annotations. For developers, this is frustrating. A developer should be able to see a method which type hints a particular interface or class name and be able to pass an object which is an instance of that class or implements the interface. With annotations, the interpreter will allow the class to be passed, but, once the client code starts reading the annotations, it will essentially see the substituted object as having an incomplete API and break because the annotations don't exist.

Ironically, in this scenario, the initial purpose of annotations which was to allow for better documented code has been entirely undermined. Because annotations are now part of the class API, type hinting and other self-documenting inbuilt language features are useless!

In short, annotations break the idea of separation of concerns by mixing metadata with the code it's related to. Why should a controller care what its route is? Why would a controller need to understand the concept of a "route" in the first place? It plainly breaks encapsulation by exposing external implementation to irrelevant classes. Objects should not have control over how they may be used externally and only be concerned with their own responsibility.

The most laughable use of annotations for configuration is @Inject which allows the annotation to configure the Dependency Injection Container. The Dependency Injection Container is there for the purpose of separation of concerns; client code should not be concerned with where its dependencies come from or the state of them. Using @Inject removes this by placing this responsibility back in the client code-- exactly the opposite of what was trying to be achieved by choosing to use a Dependency Injection Container in the first place! By using @InjectParams in Symfony you forfeit the benefit of the Dependency Injection Container, a method will now always be called with a specific parameter (specified by @InjectParms) and cannot easily be injected with a subclass or other implementation which uses the same interface. This removes any flexibility and is almost as bad as ServiceLocator::Locate('Somedependency') yet adds two layers of complexity!

Yes, the @Inject rules can be overridden. So to can the style attribute with !important in a CSS file. By overriding the annotation at a higher level you have essentially implemented the equivalent of !important for your configuration. Most of the arguments against !important also apply to your fancy "ignore the annotations and use this configuration instead" hack.

5) Tight coupling

The knock on effect of ignoring separation of concerns is tight coupling.

Even though it shouldn't, Symfony very tightly couples controllers to the framework anyway. Because of this, the above example doesn't cause any additional problems related to coupling which is probably why Symfony users are happy to overlook it; the controllers need the framework stack in order to work at all. But in the real world, where we use loose coupling, this could well present very large and very real problems.

You may argue "But the annotations don't couple the component to the framework. I can move the code elsewhere and it will work in isolation". This is half true. It will indeed work in isolation. But what if every framework used annotations for configuration? Now your annotations cause an issue if you try to use a Symfony class in Zend or Zend class in Symfony. The annotations won't be understood correctly and will likely break things. As it stands, it's only through obscurity that they don't cause issues with portability.

If annotations became commonplace, we'd get to a point where they required CSS style vendor prefixes.

A better example of this is Symfony's @Inject. It's highly likely that any IoC Container using annotations will use this label. Do you feel like going through every class in the system and changing the format of @Inject because you're using a new branch of the IoC container or another, newer, IoC Container? These annotations do indeed couple your code to an implementation and the rest of the framework stack. It's only because (thankfully) annotations do not have widespread use that they don't cause portability issues.

@--Symfony-Inject("security.context"required false)


If annotations were avoided in the first places you could just configure the DIC externally and never worry about supporting different implementations in your classes.

6) Littered code and portability

Let's say you replace Symfony's router with a different one. Not an unreasonable request-- Symfony build their components with modularity in mind, don't they? However, because of the existing annotations littered throughout your code, you can't use a different router that uses annotations because of name clashes. Perhaps the convenient annotation called @Route wasn't such a great idea after all? You can't even easily port existing routes to the new router because you don't have a complete list of them in a centralised location. You have to go through every single controller action one by one updating the @Route syntax to the new router implementation. Ouch.

Imagine you copy some classes between projects. The first project used @Inject to inject dependencies, the second one doesn't. Because it's time consuming you leave the @Inject annotations in the classes. They're harmless comments, right? Of course, anyone else looking at your code sees the @Inject annotation and assumes that's how it works and changes the @Inject rules and wonders why it doesn't alter the behaviour. Not a huge problem in itself, but think about it: would you publish a class you intended other developers to use in their own projects with annotation based configuration? No, you wouldn't. You wouldn't because you wouldn't want to lock people into a specific implementation or force people to use your preferred dependency injection container.. The same goes for your own code. Why lock yourself into an implementation? In 5 years time you may have use for this class, if it's using @Inject and you're no longer using the same Dependency Injection Container you'll wish you hadn't.

Even ignoring futureproofing, imagine you have two Symfony projects both using its annotation based router and you want to move some code between the two projects. This makes you ask a lot of questions: Are the routes I've used in my controller already in use on that project? Is the new project using a different annotation-based router that might clash? To answer these, you'll need to do a lot of digging around in the 2nd project. If you'd used a centralised routing table, these issues are easily resolved and none of the copied classes need ever be modified or even examined.

7) Flexibility

Probably the most obvious problem people will hit is that using annotations in this way immediately limits flexibility. The self-configuring class is stuck with that configuration forever. It's impossible to initiate two instances of the class with different configurations. Using the @Inject example, unless you avoid using the injection container or have some horrible workaround, the DIC must create an instance of your class with the configuration designated in @Inject. As soon as you want to wire the class differently, you have a problem.

Essentially, the class has become static. It's impossible to reuse it with another configuration. If I want to use the same controller for a different route, using annotations makes this very difficult without some ugly hacks such as controller action chaining or unneeded inheritance. And if you can achieve this reusability with a workaround, how do you make it clear what's happened to another developer looking at the code?

How should annotations work with polymorphism? If you extend a class should its annotations be extended as well? What if, in the router example, you want the route to dynamically point you to a relevant subclass? The power of polymorphism is completely lost in this case because routes have become hardcoded! Hardcoding is always a bad idea!

In effect, annotations force your classes to be static. How can you have two instances of the same class with different configurations? This is a core concept in OOP but annotations break this because they configure objects at a class/static level (Configuration will apply to every object that is an instance of the class) rather than an object level where each object can have its own configuration.

8) They break Version Control

For the sake of argument let's say you copy a "products" module from an existing project to another one. It provides functionality for searching for products, listing the results and displaying information about a single product. It's not unreasonable to suggests that this is a common feature for multiple sites. On the 2nd project, you don't want the url to be /products/ because you want something a lot more specific. Your client's site sells cars so you want the url to be /cars/ instead of /products/. If you'd used annotations you'd have to go through and alter each route. A minor inconvenience and only marginally more difficult than altering routes in a separate, centralised routing table.

However, introduce Version Control into this scenario and you've created a problem for yourself. Your second site is now using a new branch of the code and only because the routes have changed! You can't fix a bug in the products module and push it to each of your client sites that are using it because they're on different branches, solely because you have hardcoded routes into the controllers using annotations. Whoops! From a business perspective this is terrible, had you kept to a single branch you could very easily sell new features that were developed to each of your clients and deploy them at the press of a button; by pushing the new version to their site. But by using annotations and creating a separate branch you make this far more difficult for yourself.

This is a perfect example of why mixing application metadata into the application code is a very bad idea. It prevents you from sharing the application code between projects which are (almost inevitably!) configured differently.


Let's keep in mind that the sole benefit to using annotations for configuration is that you can store application configuration and application logic in the same file. That is the only reason to use annotations over an alternative approach. Is this minimal convenience ever worth it? I'd argue no.

Using annotations for configuration is horrible. It goes against common sense, basic programming principles, prevents debugging and breaks two fundamental OOP principles: Encapsulation and Polymorphism as well as making version control and sharing code between projects far more challenging.

Annotations for this purpose go against everything we strive for as programmers. It both annoys and worries me that very high profile projects are making use of them in this manner.

They are a very useful tool for documenting code. However, they should not be abused to configure an application. Each of the eight points I made above is enough on its own to warrant avoiding using annotations for storing application metadata. The fact there are eight real world problems (likely more, I probably missed some obvious ones!) introduced by their use makes them totally unfit for any purpose. This is one of the few cases where I'd even go as far as saying: They should never be used. With a lot of bad practices, there are certain scenarios where they can add some benefit, usually because they're a shortcut and make development time significantly faster (even if they introduce problems as the project grows) or avoid a lot of complexity so make sense for small projects. However, annotations add complexity and don't offer enough of a benefit at all to warrant any use. The single benefit is that it allows programmers to edit two sets of data (application logic and application configuration) within a single file. Compared with the problems they cause this is not enough to ever encourage their use.

Annotations - ProsAnnotations - Cons
Edit application configuration and application logic in the same fileCounterintuitive - changing comments affects application flow
Cannot be debugged easily (cannot var_dump an annotation)
Breaks the Single Responsibility Principle
Breaks the Liskov Substitution Principle
Breaks Encapsulation and Separation of Concerns
Introduces coupling between unrelated components (e.g. every class and a dependency injection container or controllers and a router)
Makes code less portable
Makes it more difficult to instantiate the same class with multiple configurations.
Makes version control more difficult as different configurations require different branches

In my opinion, this is one of those fad-of-the-month trends, like singletons, which have a sudden surge in popularity because people are playing with new language features but after a few years the downsides will have become painfully apparent to people who've used them in the real world and we'll see an influx of "is evil" and "are bad practice" articles related to them.