Somebody sent me a rant about Doctrine's Annotation parser being comment based. I found it frustrating to see that nobody understood the reasons behind some of the decisions here, so I thought I'd write them out.
I'll admit, I was a little baffled myself at the concept at first at the concept of comment based annotations. Or annotations at all for that matter. It seems like, at it's absolute best, a workaround. But after some more experience with them and thought on the subject, the four main points the posting in question states aren't actually good points. In fact, removing them from comments introduces a lot of further problems, and I'm beginning to question if it would even be reasonable to implement as a construct in PHP. I think there is a common fundamental misunderstanding of how annotations work and what they are. I want to give some background on why this is the case before addressing these.
First, lets talk about the origin of annotations. Annotations were originally a feature introduced in Java. These annotations were introduced to provide class metadata for the compiler. A simple example to look at is the
@Override annotation available in Java. This annotation enforces that a method has to match the parameter signature of a parent class. That way, if a parent class method's parameters change, you know that a methods overriding it will need to change as well. This action is something that is processed at compile time, as opposed to the application runtime. The compiler, when done with these annotations strips them out when building the compiled class files. But there is some data you may want to keep around for runtime. This is typically useful for Routers, Dependency injection, and ORMs. So then, another annotation was introduced. The
@Retention Annotation allows you to retain a retention through different parts of the application compilation. Mainly, it will allow you to prevent the compiler from stripping it out at runtime.
You'll notice that Java annotations look almost identical to the ones being used in PHP. That's no coincidence. The current popular annotation parser is the Doctrine annotations parser. Doctrine, if you are not aware, is heavily based on Hibernate An ORM for java that uses annotations heavily. But this is not the first place I had seen annotations. PHPUnit used a different syntax and parser for them as well, and PHPUnit is essentially a port of Junit which also uses them in a similar fashion.
So what do we need these annotations for, anyway? Like I mentioned earlier, Annotations provide a way to give class metadata in a clean way, from the class, and without coupling our class's functionality to that structure. So in the doctrine entity example, it allows us to describe what the Doctrine ORM can do with this object. I emphasized the word describe here because it's important. Annotations don't do anything. They're simply informational. This is mostly true in java as well. Annotations can't possess any functionality themselves. This means that we can use our Entity object as a plain old PHP object and not worry about doctrine unless we want to.
So, now we're going to diverge a bit from the standard annotation. There's a key difference here in that PHP, unlike Java, does not compile. Except this isn't exactly true anymore. Due to the high disk usage of a large enterprise architected application, many PHP frameworks have begun to provide a compiler of their own. Typically we've been calling this a cache, but it is at its core a compiler. Symfony actually calls them compiler passes. What these will typically do is gather intensive operations and pre-coputes them. Things like routers, dependency injection, and ORM's typically fall into this list. You'll notice that these are the same items I mentioned as things that are kept at runtime for Java. This simply can happen at a different step in the process. We get that functionality because unlike Java, everything is interpreted. So frameworks get to choose what gets pre-compiled, and what doesn't. It turns out this is incredibly useful. The Symfony framework has managed to create a framework that is both interpreted and compiled using this. It does this by determining it's environment and whether there is an available cache for that environment. Based on that it decides whether to use a compiled cache, or a JIT-like compiler.
So why doesn't PHP implement an annotation construct? It turns out there's a somewhat good reason for this. And that is the fact that there isn't a standard way to compile these files. Since this is left up to the framework, PHP can't reasonably determine when to enforce those constructs and when to ignore them. And It has no place to strip out compile-time annotations. So it would have to ignore them always in order to reasonably function. Kind of like what happens when they're put in comments. This isn't to say you couldn't implement it, just that there are some challenges and implications with doing so. Comments actually provide a somewhat, if I dare say, elegant way to solve this problem, since they are already meta data for the class.
So now that we're done with that let me address the original posting's concerns.
1) Some php bytecode compilers (for example, facebook's hiphop) will strip your comments. STRIPPING COMMENTS SHOULD NEVER BREAK YOUR APP.
This is kind of a similar problem to the reason PHP can't implement implement them well. What's happening with hiphop is you're compiling the application before the annotations are compiled. Bad. You added another step to the compile phase that should have been run last, but is instead being run first. There are more than a few ways to fix this problem, and I'm not going to go into them all here.
2) No IDE hints, no IDE syntax checking because, you know, THEY ARE COMMENTS. If you think I should download a specific IDE plugin to work with Doctrine and another one to work with Symfony and yet another one to work in your specific project with your custom annotations, you can go F@#$ yourself.
This is an IDE problem, and yes, it's a pain. but has little to do with the issue at hand. The existence of those plugins shows that there is no reason the construct can't be parsed and formatted in a comment, IDE's just haven't implemented it. Get mad at your IDE, not the construct at hand.
3) Coupling, separation of concerns, encapsulation: having @Route("/something") in a controller class is AWFUL. The controllers don't do the routing per se and yet contain routing information. If I want to check which action specifically is called for route X, I'd have to check all my controllers' methods for this specific route, because they are scattered. It also creates a tight coupling, my router now depends on controller metadata to know what to do with the route.
Like I mentioned before, the annotations provide information not functionality. The difference between coupling and availability is in what you have to do. In the
@Route example, you could get rid of the Symfony Router and the controller class would still be functional. This is not a coupling.
And as for the location of the annotations being on the controller being confusing locationally as opposed to one routing file, I would argue that it is an equally confusing to look at a controller and not knowing what route it is connected to. With the additional downside of having a large, overbearing routing configuration file. So I'd personally say the annotations win out there, but it may depend on your use case.
4) Dude, what does "@ManyToMany" do? Oh, ok, and what does "@param" do...? HOW THE HELL AM I SUPPOSED TO TELL SOMEONE "oh, this part over here is metadata, but this one is just a comment"? How do I distinguish between the two of them? Yes, Doctrine has a set of well defined annotations, but I can create any annotations I want and they are INDISTINGUISHABLE from DocComments or from other comment tags.
The reason you're having a hard time explaining this is because it isn't there. What if I told you there was not difference? It's no coincidence either that these share a similar syntax with the Annotation syntax. Let's look back at Java one more time. Java has a similar construct for their docblocks. These are used for a java documentation parser called JavaDoc. These
@return are inherited from java, where we needed more meta data around these class constructs. Except that JavaDoc was around a long time before the Annotation construct, so they put it in comments and parsed it from there. Sound familiar? There are a number of reasons why these are left in the comments even in java, the primary of which is that Javadoc uses the entire comment to generate documentation, so it fits in there better. PHP uses a similar tool, PHPDoc, which does the same thing. These
@param comments are annotations. These annotations are used in the exact same way, only a different compiler. Similar to the way that PHPUnit also has a different annotation compiler.