Bringing annotations to Yii

I wrote an annotation framework for PHP - I’ve been refining and testing over the past few months, and I’m pondering the next logical step for integration with Yii.

The main thing I need to address, is a practical approach for the syntax.

The problem is, the active record implementation in Yii uses virtual properties for persistence. Meaning, when you look at the source code of a CActiveRecord class, you see something like this:




class User extends GActiveRecord

{

  /**

   * @var integer $id

   * @var string $email

   * @var string $firstname

   * @var string $lastname


...



In other words, the persistent properties aren’t actually declared as PHP code - they’re evaluated at run-time, and don’t actually exist as PHP class members.

The trouble with that (in relation to annotations in PHP) is, most annotation frameworks rely on reflection to find the annotation mark-up.

In my own solution, I opted for a simple parser that actually scans the PHP code for the annotations, as this gives me more freedom in terms of syntax - I make minimal use of reflection, only as a means of finding the source code file associated with a class being inspected for annotations.

So that’s the good news, but one complication remains: how do you annotate properties that do not exist?

For a plain old PHP object, my annotation library uses the following syntax:




class User

{

  #[PrimaryKey()]

  public $id;


  #[Length(255)]

  #[Type('email')]

  public $email;

  

  ...

}



Unlike most other annotation layers for PHP, my implementation does not use PHP-DOC syntax for the annotations - I opted for a syntax that that is shorter and more similar to existing PHP syntax.

I would like to stand by that choice. I really don’t like the idea of mixing documentation syntax with functionality syntax.

However, there is of course the IDE aspect to consider - and all modern PHP IDEs rely on PHP-DOC syntax for declarations like those seen in CActiveRecord classes to inform them about existing virtual properties.

In other words, there’s really no way around them.

And since declaring the property names that way is already a well-established convention, I need to make a difficult choice.

  1. Get rid of my existing syntax and change it to something PHP-DOC compatible. In which case my project becomes a lot more similar to other existing annotation libraries for PHP.

  2. Extend my syntax to allow annotation of virtual members. It could look something like this:




class User extends GActiveRecord

{

  /**

   * [PrimaryKey()]

   * @var integer $id

   *

   * [Length(255)]

   * [Type('email')]

   * @var string $email


...



The two syntaxes aren’t difficult to mix as such. I wonder how existing PHP-DOC parsers will interpret my annotations though?

My main concern with the second approach, blending with PHP-DOC syntax, is whether this will escalate - will there be other types of PHP-DOC annotations I’ll need to take into account besides @var?

There is another, perhaps more serious problem, with the first approach though - how do you apply annotations to members (properties) which themselves only exist in the form of an @var annotation?

I poked around to see how other annotation frameworks address this issue - Addendum, for one, does not seem to address it. Some frameworks support nested annotations, but that in my opinion adds another degree of complexity and messy syntax - and PHP-DOC syntax does not include nested annotations in the first place, so the syntax would be a superset of PHP-DOC under any circumstances.

Looking for any kind of ideas and input here.

I would like to take this library and actually apply it in practice. My first goal is to write a CActiveRecord extension that implements a number of the configuration callbacks: attributeNames(), attributeLabels(), tableName(), primaryKey(), rules(), etc. - with annotations to handle all of the standard CActiveRecord configuration and eliminate the redundant methods and repeated property names as array keys in each of those.

Once that is done, I would release this as an extension.

But first I need to make some hard decisions here, and after months of pondering and going back and forth on the subject, I need help! :slight_smile:

Thanks for reading this lengthy post!

Yii does support explicitly declaring those persistent properties as class members. You mainly need to modify the model generation code so that all of those properties are generated for you to simplify your task. However, this means the users need to synchronize the code manually if they make changes to the underlying table structure.

If you use any validation rules, labels, etc. you need to synchronize your model classes anyway - and if you use @var annotations, you need to update those, too.

My goal with the annotations is to eliminate multiple references to the same property name.

Haven’t used Yii on a project in a while now, so going slightly off-topic - does this mean it’s also possible to use getters/setters for property names now? And how about declaring persistent properties as protected/private, is that supported?

As description for the @var or @property declared above.

@property, @method, @return.

As I know, it was always supported for Yii models.

No. public only.

btw., in Yii persistent properties are not declared. Since ActiveRecord is used it is getting schema info automatically and there is no need to explicitly declare anything except maybe some validation rules. @property declarations generated by Gii are just for IDEs code completion.

Not sure, if that’s what you mean, but for AR you can’t use getter/setter for DB attributes. Check the source of __get(). It will always first check for a db column with that name. If the name is found in $this->_attributes it will not call parent implementation CComponent::__get() where the getter would be called.

http://www.yiiframework.com/doc/api/1.1/CActiveRecord#__get-detail

http://www.yiiframework.com/doc/api/1.1/CComponent#__get-detail

Yeah, that’s how I recalled it.

Thanks for clearing that up.

Oh, so I need to support annotations for @property, not for @var, which is just for descriptions - correct? (PHP-DOC documentation is rather messy…)

My own library supports annotations on classes, methods and properties. From what I’ve seen of practical applications for annotations, annotating arguments has a pretty limited use - mainly IOC containers. Annotating return values is almost unheard of, from what I’ve seen.

I’m willing to reconsider that position of course, if I hear a convincing argument why they’re needed.

Well, that’s the AR pattern, and it’s always been one of the most frequently heard arguments in debates about active record vs decoupled object/relational mappers.

But as you just pointed out, there is always a need to declare things, if just for documentation - after all, what good is a property if no one knows it’s there? So at the very minimum, you have to write an annotation to document it’s existence.

In practice, for most models, you have to describe each property in many other ways: how to validate it, how to label inputs, whether it’s safe for massive assignment and perhaps in which scenario, etc.

I prefer code that looks and functions like code. When there’s a code option and a documentation option, I’ll always take the code option. Hence, I would much prefer to see “public $name”, which is recognized by PHP (and programmers) and works with other PHP-features like has_property() and property reflection, as opposed to “@property $name” in a comment-block, of which PHP is entirely unaware.

Now, you could use the same argument against annotations in PHP - the difference is, annotations are not available in PHP, and properties are :wink: … if we want annotations now, we’ll have to get our hands dirty. (and the contributed PHP annotation extension does NOT look promising…)

As I know, @var is valid too but is generally used for inline variables, not class members. As for @return, I’ve just telling which annotations are used commonly.

Well, if your annotations approach will be really dry and performance hit will be insignificant, I think it will be a good optional addition to AR.

Sure, it’s not meant as a replacement. For most tasks, this will probably be faster, easier and more legible - that’s my hope, anyway. For certain specialized tasks, say, if the description or input on a form is generated dynamically, static metadata won’t work.

(although I suspect this is already impossible with AR in Yii, as the methods that provide various property metadata are invoked only once per request, for a single static instance of any given AR model object? if I recall, at least the validation rules are only constructed once, as an optimization, not for every instance - meaning the validation rules and other property metadata do not actually provide variability to begin with…?)

Do you know if PHP-DOC parsers will ignore PHP-DOC-like annotations they don’t recognize?

So for example, could we change the syntax to something like this:




class User extends GActiveRecord

{

  /**

   * @PrimaryKey()

   * @property integer $id

   *

   * @Length(255)

   * @Type('email')

   * @property string $email


...



I guess individual implementations of PHP-DOC parsers in documentation generators and IDEs might react differently to unknown tags like these?

Depends on the parser.

btw., just found this one: http://www.doctrine-project.org/projects/common/2.0/docs/reference/annotations/en#annotations

Yep, I believe this is what the new version of Symfony uses?

I guess if they can get away with this sort of extended PHP-DOC syntax, so can I? :wink:

Yes, Doctrine is now the main Symfony ORM.

Sure you can ;)

I wonder how it would react to multi-line annotations though. I can’t imagine that’s going to go well, since PHP-DOC does not have any existing multi-line annotations or any syntax expecting that… :-/

Well, you can try this yourself:

PhpStorm: http://confluence.jetbrains.net/display/WI/Web+IDE+EAP

NetBeans: http://netbeans.org/downloads/

If anyone’s still listening on this thread, here’s an update.

My annotation library is still in development - after months of thought and discussion with other developers, I finally have satisfying solutions to the remaining conceptual problems, some of which are implemented, and some of which will require another couple of weekends of hard work.

Annotations seem to be making their way into many other PHP frameworks by now. Are annotations still not on the chart for Yii 2.0? Just wondering.

If Yii were to support annotations, I would be glad to contribute this library when it’s complete.

Right now annotations aren’t planned for Yii2 but since Yii2 is too far from public release everything can change.

makes sense: annotations for routes

Just a quick note to let you know, I have been working hard on this project - here’s what I’ve been up to in the last couple of months:

  • Refactored the codebase to use namespaces, and the library itself now supports namespaced annotations. (to me, personally, I find this feature has very little practical use, but a couple of important people in the community were adamant about this)

  • Added a demo-script showing (with step-by-step instructions) how to consume source code annotations and use them to drive user interface, type-conversion, validation, etc.

  • The AnnotationManager now has a registry, allowing you to decouple the implementation of an annotation from the application of an annotation to a source-code element.

  • Documentation is nearly complete, although still with a few things missing here and there.

The Wiki is a good place to start.

A few of the standard annotations are in place too - enough to drive the demo script.

One problem remains and needs to be addressed before version 1.0 of my annotation engine - and before I start integrating this with Yii, which is my eventual goal. I was wondering if any of you might have some ideas?

Take the following example:




class Foo

{

  private $values = array(

    'bar' => 'hello',

    // ...

  );


  public function __get($name)

  {

    return $this->values[$name];

  }

}



How do you annotate the virtual member $bar ?

I thought I had addressed this problem, and the current implementation does provide a working solution, but I’ve decided I’m still not satisfied with this. It’s currently solved as follows:




/**

 * @required

 * @property $bar

 *

 * @table('name'=>'items')

 */

class Test

{

  // ...

}



In this example, the @required annotation is applied to the virtual member $bar, because the @property annotation implements a "delegation" interface… annotations can delegate annotations to a virtual member property/method in this way.

Because the @required annotation precedes the @property annotation, it gets delegated to the virtual $bar member.

The extra space means nothing, it’s just there for clarity - any annotations following the @property annotation go where they normally would, in this case to the Test class.

This feature works and does solve the problem, but there is no visible syntax when you apply this feature, and this is where I see a problem.

I think I should add some kind of syntax that makes it clear which annotations get delegated - I have a feeling too many people are otherwise going to have to learn the hard way why annotations seem to "disappear" when using @var and @property etc…

I thought it might look like this:




/**

 * @table('name'=>'items')

 *

 * @property $bar

 * @@required

 */

class Test

{

  // ...

}



It looks a bit odd, I guess - but it’s at least obvious that something is different here :wink:

Perhaps better would be:




/**

 * @table('name'=>'items')

 *

 * @property $bar {

 *   @required

 * }

 */

class Test

{

  // ...

}



Looks a bit odd too, I guess - and could collide (at least visually) with curly braces, if some annotation takes an anonymous function (closure) as argument.

Any thoughts? ideas?