Yii Framework Forum: Source Code Annotations (Attributes) - Yii Framework Forum

Jump to content

Page 1 of 1
  • You cannot start a new topic
  • This topic is locked

Source Code Annotations (Attributes) Yet another idea for future versions... Rate Topic: -----

#1 User is offline   mindplay 

  • Advanced Member
  • PipPipPip
  • Yii
  • Group: Members
  • Posts: 397
  • Joined: 03-September 09
  • Location:New York

Posted 27 July 2010 - 03:35 PM

Once again, I'm experimenting with the translation of some of the principles from my work with ASP.NET MVC 2 and C# to PHP.

Attributes are an awesome language feature - basically, you can annotate your source code (classes, methods, properties, etc.) using custom Attributes, and inspect them at runtime.

This is used for things like validation and adding filters to actions.

To do something equivalent in PHP, we have to invent a syntax for it. Some of you may cringe at a the thought of this already, but bear with me ;-)

As an example, you might use an attribute to specify that a particular action requires a POST - which might look like this:

<?php

class MyController extends Controller
{
  #[HttpPost()]
  public function actionTest()
  {
    // ...
  }
}


Apart from the pound sign (#) this is identical to the syntax used in C#.

This is not a complete implementation by any means, but here's a basic implementation in PHP:

This code sample has been superseded by a full library currently in development - see below

<?php

/**
 * This interface must be implemented by Attributes
 */
interface IAttribute {
  public function initAttribute($properties);
}

/**
 * This class implements run-time Attribute inspection
 */
abstract class Attributes
{
  /**
   * This method is the public entry-point for Attribute inspection.
   *
   * @param Reflector a ReflectionClass, ReflectionMethod or ReflectionProperty instance
   * @return array an array of Attribute instances - or an empty array, if no Attributes were found
   */
  public static function of(Reflector $r)
  {
    if ($r instanceof ReflectionMethod)
      return self::ofMethod($r);
    else if ($r instanceof ReflectionProperty)
      return self::ofProperty($r);
    else if ($r instanceof ReflectionClass)
      return self::ofClass($r);
    else
      throw new Exception("Attributes::of() : Unsupported Reflector");
  }
  
  /**
   * Inspects class Attributes
   */
  protected static function ofClass(ReflectionClass $r)
  {
    return self::loadAttributes($r->getFileName(), $r->getStartLine());
  }
  
  /**
   * Inspects Method Attributes
   */
  protected static function ofMethod(ReflectionMethod $r)
  {
    return self::loadAttributes($r->getFileName(), $r->getStartLine());
  }
  
  /**
   * Inspects Property Attributes
   */
  protected static function ofProperty(ReflectionProperty $r)
  {
    return self::loadAttributes($r->getDeclaringClass()->getFileName(), method_exists($r,'getStartLine') ? $r->getStartLine() : self::findStartLine($r));
  }
  
  /**
   * Helper method, replaces the missing ReflectionProperty::getStartLine() method
   */
  protected static function findStartLine(ReflectionProperty $r)
  {
    $c = $r->getDeclaringClass();
    $code = explode("\n", file_get_contents($c->getFileName()));
    $start = $c->getStartLine();
    $length = $c->getEndLine()-$start;
    foreach (array_slice($code,$start,$length) as $i=>$line)
      if (preg_match('/(?:public|private|protected|var)\s+\$'.preg_quote($r->getName()).'/', $line))
        return $i+$start+1;
  }
  
  /**
   * Create and initialize Attributes from source code
   */
  protected static function loadAttributes($path, $linenum)
  {
    $attributes = array();

    $code = explode("\n", file_get_contents($path));
    
    $linenum -= 1;
    while (($linenum-->=0) && ($attribute = self::parseAttribute($code[$linenum])))
      $attributes[] = $attribute;
    
    return array_reverse($attributes);
  }
  
  /**
   * Parse an Attribute from a line of source code
   */
  protected static function parseAttribute($code)
  {
    if (preg_match('/\s*\#\[(\w+)\((.*)\)\]/', $code, $matches))
    {
      $params = eval('return array('.$matches[2].');');
      return self::createAttribute($matches[1], $params);
    }
    else
      return false;
  }
  
  /**
   * Create and initialize an Attribute
   */
  protected static function createAttribute($name, $params)
  {
    $class = $name.'Attribute';
    $attribute = new $class;
    
    $attribute->initAttribute($params);
    
    return $attribute;
  }
}

/**
 * Sample Attribute
 */
class NoteAttribute implements IAttribute
{
  public $note;
  
  public function initAttribute($params)
  {
    if (count($params)!=1)
      throw new Exception("NoteAttribute::init() : The Note Attribute requires exactly 1 parameter");
    
    $this->note = $params[0];
  }
}

/**
 * A sample class with Note Attributes applied to the source code:
 */

#[Note("Applied to the Test class")]
class Test
{
  #[Note("Applied to a property")]
  public $hello='World';
  
  #[Note("First Note Applied to the run() method")]
  #[Note("And a second Note")]
  public function run()
  {
    var_dump(array(
      'class' => Attributes::of(new ReflectionClass(__CLASS__)),
      'method' => Attributes::of(new ReflectionMethod(__CLASS__, 'run')),
      'property' => Attributes::of(new ReflectionProperty(__CLASS__, 'hello')),
    ));
  }
}

// Perform a test:

header('Content-type: text/plain');

$test = new Test;
$test->run();



Unfortunately, two missing features are currently forcing me to comment out the portion of this code that would implement support for property annotations.

Until this is added, property annotations would only be possible by means of a very ugly/bulky work-around.


Edit: I added a simple fix for the missing ReflectionProperty::getStartLine() method - if they ever fix it, it will default to using the real method, in the mean-time, it'll use this simple work-around.

So this isn't by any means a recommendation to implement this feature - more like an experiment for discussion, since PHP currently doesn't fully provide support for completing this implementation...

Feedback/ideas/flames welcome :-)

This post has been edited by mindplay: 07 September 2010 - 08:13 AM

0

#2 User is offline   Y!! 

  • Advanced Member
  • Yii
  • Group: Yii Dev Team
  • Posts: 978
  • Joined: 18-June 09

Posted 27 July 2010 - 03:51 PM

This is interesting. Do you think there is a way to implement a good method overloading with reflection?
0

#3 User is offline   samdark 

  • Having fun
  • Yii
  • Group: Yii Dev Team
  • Posts: 4,553
  • Joined: 17-January 09
  • Location:Russia

Posted 28 July 2010 - 04:05 AM

It's possible to read PhpDoc for methods / properties with getDocComment() and then parse it for annotations. There are some ready to use implementations: http://code.google.com/p/addendum/

Also there is an implementation in Recess framework http://www.recessfra...els-at-a-glance

Overall I don't really like this idea. In C# and Java annotations are language level constructs and have both good runtime and IDE syntax/error checking. Also these are adding more magic.
0

#4 User is offline   mindplay 

  • Advanced Member
  • PipPipPip
  • Yii
  • Group: Members
  • Posts: 397
  • Joined: 03-September 09
  • Location:New York

Posted 28 July 2010 - 06:56 AM

View Postsamdark, on 28 July 2010 - 04:05 AM, said:

It's possible to read PhpDoc for methods / properties with getDocComment() and then parse it for annotations. There are some ready to use implementations: http://code.google.com/p/addendum/

Also there is an implementation in Recess framework http://www.recessfra...els-at-a-glance

Overall I don't really like this idea. In C# and Java annotations are language level constructs and have both good runtime and IDE syntax/error checking. Also these are adding more magic.


I'm aware that some people would feel that way ;-)

The PhpDoc implementation is very simplistic, since it doesn't even name the annotations - I think the real power of C# annotations, is that they are actually classes.
0

#5 User is offline   mindplay 

  • Advanced Member
  • PipPipPip
  • Yii
  • Group: Members
  • Posts: 397
  • Joined: 03-September 09
  • Location:New York

Posted 28 July 2010 - 08:09 AM

I just took a look at the Recess implementation, and I can't say I like that either - it's too remote from PHP syntax.

In my example, everything between the parantheses is inserted into an array() statement, so you can not only name your attributes, you can also name their properties, using PHP syntax - for example:

<?php

class Test
{
  [SomeAttribute('a'=>123, 'b'=>456)]
  public $whatever;
}


Since this pattern is actually a method for annotation of code, I also think the idea of integrating with the Reflection API, an integral part of the PHP language, makes more sense than relying on something like PhpDoc, an extraneous stand-alone specification - which also happens to require a big, bulky parser.

Of course, in the end, you could argue against attributes because they don't really provide any functionality that doesn't already exist - for example, you could "annotate" your code with "attributes" by implementing an interface defining a call-back method, like, say:

class Test implements Attributes
{
  public $hello='World';
  
  public function run()
  {
    var_dump(array(
      'class' => Attributes::of(new ReflectionClass(__CLASS__)),
      'method' => Attributes::of(new ReflectionMethod(__CLASS__, 'run')),
      //'property' => Attributes::of(new ReflectionProperty(__CLASS__, 'hello')),
    ));
  }

  public function getAttributes($type, $name)
  {
    static $attributes = array(
      'class' => array(
        new NoteAttribute("Applied to the Test Class"),
      ),
      'property'=>array(
        'hello'=>array(
          new NoteAttribute("Applied to the property"),
        ),
      ),
      'method'=>array(
        'run'=>array(
          new NoteAttribute("First Note Applied to the run() method"),
          new NoteAttribute("And a second Note"),
        ),
      ),
    );
    return $type=='class' ? $attributes[$type] : $attributes[$type][$name];
  }
}


This achieves the same thing, it just isn't very elegant, since your attributes are removed from the code they apply to, and you're forced to specify the name of every property and method again.

There's also a performance consideration - since source code annotations are typically only needed for specific tasks, why add the overhead of having PHP load and parse all that code? Comments add no overhead.

This may be outweighed by the fact that this implementation needs to use eval() for the attribute values - but for something like schema annotations, which would be loaded once in a blue moon (e.g. a deploy script), this might be a real benefit...
0

#6 User is offline   mindplay 

  • Advanced Member
  • PipPipPip
  • Yii
  • Group: Members
  • Posts: 397
  • Joined: 03-September 09
  • Location:New York

Posted 28 July 2010 - 08:14 AM

By the way,

View Postsamdark, on 28 July 2010 - 04:05 AM, said:

Also these are adding more magic.


I don't like "magic" either - but magic to me implies something happens "magically", automatically, without your writing code to invoke a particular function.

Source code annotations are not magic by my definition, since your code has to explicitly ask for them - they are not somehow magically constructed or run without your intervention.
0

#7 User is offline   mindplay 

  • Advanced Member
  • PipPipPip
  • Yii
  • Group: Members
  • Posts: 397
  • Joined: 03-September 09
  • Location:New York

Posted 11 August 2010 - 07:38 AM

In case anyone is interested, I added a work-around for the missing property annotations - code updated above.
0

#8 User is offline   mindplay 

  • Advanced Member
  • PipPipPip
  • Yii
  • Group: Members
  • Posts: 397
  • Joined: 03-September 09
  • Location:New York

Posted 11 August 2010 - 07:40 AM

View PostY!!, on 27 July 2010 - 03:51 PM, said:

This is interesting. Do you think there is a way to implement a good method overloading with reflection?


I think that's a topic for a different discussion? But no, I don't think reflection can help here. There is a library called RunKit in PHP, but it has to be compiled in, and it's not included in the standard distributions - it will do overloading and many other runtime hacks. It's been in the making for years though, like so many cool PHP features - I wonder if it will ever officially see the light of day...
0

#9 User is offline   Y!! 

  • Advanced Member
  • Yii
  • Group: Yii Dev Team
  • Posts: 978
  • Joined: 18-June 09

Posted 11 August 2010 - 10:17 AM

View Postmindplay, on 11 August 2010 - 07:40 AM, said:

I think that's a topic for a different discussion? But no, I don't think reflection can help here. There is a library called RunKit in PHP, but it has to be compiled in, and it's not included in the standard distributions - it will do overloading and many other runtime hacks. It's been in the making for years though, like so many cool PHP features - I wonder if it will ever officially see the light of day...


Yes it's a different thing, sorry for hijacking. I will take a look at runkit, thanks.
0

#10 User is offline   mindplay 

  • Advanced Member
  • PipPipPip
  • Yii
  • Group: Members
  • Posts: 397
  • Joined: 03-September 09
  • Location:New York

Posted 07 September 2010 - 08:08 AM

Early days (pre-alpha) for this library still, but I worked hard on my annotations library this weekend, and it is now functional!

You can browse the code, or check out a copy, from this repository:

http://svn.mindplay....otations/trunk/

I still need to write unit tests and add more error checking, but the library is basically functional.

It also needs documentation (although the source code is well-documented), and the standard library of annotations (DataAnnotations.php, DisplayAnnotations.php and ValidationAnnotations.php) is currently all stubs.

I welcome you to review these stubs at this point, and submit ideas/requests for missing useful annotations to include in the standard library. I'm including three categories of standard annations: data annotations to describe data-relationships in models, display annotations to describe formatting and form generation, and validation annotations to describe simple validations, validation methods, and type checking.

Of course you're also welcome to review the overall architecture of the library itself, but please hold bug reports for now - I am aware of several minor issues that still need to be resolved, and as said, I still need to write unit tests to assist with the final round of debugging.

This is work in progress. But I made good progress this weekend :-)
1

#11 User is offline   mindplay 

  • Advanced Member
  • PipPipPip
  • Yii
  • Group: Members
  • Posts: 397
  • Joined: 03-September 09
  • Location:New York

Posted 07 September 2010 - 08:59 AM

By the way, I contacted the author of the native PHP extension for annotations, and after receiving his reply, I decided to redouble my efforts to complete my own implementation.

For the record, here are the comments I submitted:

Quote

Hello,

I saw your RFC here:

http://wiki.php.net/rfc/annotations

I'm not sure how one is expected to actually comment, since this wiki page appears to be read-only. But anyhow...

I've been thinking about annotations for PHP for a while, and have been working on an implementation (in PHP) myself.

Here are my thoughts on your implementation:

The syntax is great - short and to the point. The extensions to the Reflection API look good. This is definitely off to a good start.

Some things I would add or change:

Make the annotation base class optional - there's no reason why something this simple should require a base class, so please consider adding an interface, so that existing codebases can integrate annotations more seamlessly, without breaking existing inheritance hierarchies.

Establish a naming convention for annotation classes - I would suggest an optional "Annotation" class-name suffix. This helps prevent class-name collisions - so that you can have a class named Author and an annotation class named AuthorAnnotation in the same namespace (or in framework not using namespaces). When the attribute [Author(name="Rasmus")] is applied, reflection should look for AuthorAnnotation first, and if not found, Author second.

The getAnnotations() methods index the annotations by class-name, which prevents you from having more than one annotation of the same type - this limitation is unnecessary. When inspecting all annotations, they should be returned in the order they were defined, as an indexed array, not as a hash. When you need a specific type of annotations, you should use getAnnotations($name=null) - or if you're looking for a single annotation of a specific type, getAnnotation($name) should be used, so the name-index becomes redundant.

A UsageAnnotation should be available to mark up the annotations themselves and define how they can/should be used - this needs to include a set of flags indicating where the attribute can be applied to a (file?), class, method or property. Also a flag indicating whether multiple attributes of this type are allowed on the same statement. And a flag indicating whether this attribute can be inherited by child classes.

Because PHP is not a compiled language, we can't feasibly load annotation classes and validate constraints at load-time, due to the overhead of loading all (potentially unused) annotation classes - but when attributes are constructed via the extended reflection API, exceptions should be thrown when an applied annotation violates a constraint, so that programmers can learn how to use them correctly.

Not sure if your implementation takes inheritance into account? But this is very important - calling getAttributes('HttpAnnotation') must include 'HttpGetAnnotation' and 'HttpPostAnnotation' objects. This enables frameworks to provide base classes for annotations that include things like custom filters, selectors, validators, etc.

Last but not least, we need to provide a standard library of annotations. In my opinion, this is absolutely crucial - without a standard library of annotations, every framework and library are going to invent their own annotations for common data-annotations like Required, Length, DataType, RegexPattern, etc. which to some extend defeats the point of having annotations in the first place.

What's truly beautiful about annotations in C#, for example, is the fact that every library (whether it's an MVC framework, a View engine or an Object/Relational Mapper) uses the defined standard library of annotations to the extend that it's possible - which means that developers only have to learn one set of standard annotations. And more importantly, core annotations will be compatible between libraries, no matter which combination of packages you're working with - if you don't have a standard library of annotations, you're going to quickly end up with models that require multiple similar annotations to mark up the same property with the same basic information, for example [ORM\Length(max=50)] and [ViewEngine\Length(max=50)].

I would love to see annotations make it into PHP, and your library is a great start - but I'm afraid if these issues are not addressed, it's going to do more harm than good. I can see this turning into another one of those PHP features that are too simple for real-world applications. PHP has too many of these half baked features, and this one is going to lead to the usual situation, where every framework and library invents their own layers upon layers of incompatible custom extensions on top of it, rendering the whole thing much less useful than it could (should) have been.

I am hoping to see a more complete version of this feature make it into PHP someday, but I think it's premature for current implementation to go public.

As said, I have been working on an annotations library for PHP myself, written in PHP - currently my annotation interface and base class looks like this:

/**
 * This interface is mandatory for all Attributes.
 */
interface IAttribute {
  public function initAttribute($properties);
}

/**
 * A general-purpose base class for attributes.
 *
 * Deriving from this base class is optional.
 */
abstract class Attribute implements IAttribute
{
  public function __get($name)
  {
    throw new Exception(get_class($this)."::\${$name} is not a valid property name");
  }
  
  public function __set($name, $value)
  {
    throw new Exception(get_class($this)."::\${$name} is not a valid property name");
  }
  
  public function initAttribute($properties)
  {
    foreach ($properties as $name => $value)
      $this->$name = $value;
  }
}



The base class is used for standard attributes, and it guarantees that only properly defined properties can be accessed on annotation objects.

In my implementation, a usage attribute defines how attributes can be applied:

/**
 * This Attribute is mandatory for IAttribute classes.
 */
class UsageAttribute extends Attribute
{
  public $class = false;
  public $property = false;
  public $method = false;
  
  public $multiple = false;
  
  public $inherited = true;
}


And a custom attribute looks like this:

/**
 * Sample Attribute
 */
#[Usage('class'=>true, 'property'=>true, 'method'=>true)]
class NoteAttribute implements IAttribute
{
  public $note;
  
  public function initAttribute($params)
  {
    if (count($params)!=1)
      throw new Exception("NoteAttribute::init() : The Note Attribute requires exactly 1 parameter");
    
    $this->note = $params[0];
  }
}


A class using this attribute looks like this:

/**
 * A sample class with NoteAttributes applied to the source code:
 *
 * [Note(
 *   "Applied to the Test class (a)"
 * )]
 * 
 * [Note("And another one for good measure (B)")]
 */
class Test extends TestBase
{
  #[Note("Applied to a property")]
  public $hello='World';
  
  // [Note("First Note Applied to the run() method")]
  // [Note("And a second Note")]
  public function run()
  {
    var_dump(array(
      'class' => Attributes::ofClass(__CLASS__),
      'method' => Attributes::ofMethod(__CLASS__, 'run'),
      'property' => Attributes::ofProperty(__CLASS__, 'hello'),
    ));
  }
}


As you can see, I use an interface (initAttribute) for the initialization - I would encourage you to take this approach under consideration, leaving the __construct() method untouched and available to be used more freely when architecting custom libraries of annotations. The constructor is likely to collide with pretty much any pre-existing code, whereas a custom method-name is much less likely to do so. This way, there is also no risk of providing a constructor signature that is incompatible with the constructor signature of pre-existing code.

I chose to use standard PHP array syntax for the annotation property values - so that, for named properties, my syntax could look like this:

[Length('max'=>50)]


I would like you to take this under consideration - for one, the syntax is closer to existing PHP syntax, so it should look more familiar to PHP developers.

For another, the initialization method (in your implementation as well as mine) expects precisely that: an array - as long as what you're building with this syntax is in fact just an array, I think it makes sense to use standard array syntax.

This permits us to write annotations using constants or class-constants as property values, e.g. [DataType(DataTypes::DATETIME)]. Given that such values are permitted inside arrays, this could probably also simplify the parser syntax definition, since I imagine you'd be able to simply tell your parser extension to expect standard array syntax inside the parantheses (?)

This also gives us more flexibility in terms of mixing named annotation properties with anonymous annotation properties, so we can leave it up to the class to decide how to handle them.

For example, the LengthAnnotation could accept either [Length('max'=>50, 'min'=>10)] or simply [Length(50)] - so the initializer method would receive either array(0=>50) or array('max'=>50, 'min'=>10) and given that a length specification is 90% of cases is used to indicate a maximum length, this would be the default.

I would be happy to help in any way I can - although I'm not much use in terms of C myself, I do have a relevant education in systems architecture and 13+ years of professional experience as a web developer, and I'm good at thinking "out of the box" - so I hope I may be able to contribute some ideas and experience to help further your efforts.

I also would love to help spec (or prototype) a standard library of annotations, which I had planned for my own annotations library anyway - I do not want to release the library itself without a standard supply of annotations, for the reasons described above.

I know this is a lot of comments, and I hope you're not overwhelmed by this. But frankly I'm pretty psyched about the fact that you're doing this, and since the idea of annotations is catching on fast, I am somewhat worried about the upcoming custom implementation and annotations library in the coming version of Symfony. While I'm sure I am every bit as impatient as the Symfony team to start using this feature in PHP code myself, I think it's a terrible idea for something like this to be tailored to a specific framework - I fear it's going to start a terrible trend, where every framework has it's own incompatible syntax custom library of annotations...

I was hoping to nip this trend in the butt by providing a framework-independent open-source annotations library, but obviously a native PHP implementation would be a far better approach - a custom library is not likely to see any IDE support at all.

I appreciate your initiative, and I have great hopes that annotations could take the PHP language to the next level, so please let me know if there's anything at all I can do to help you make this happen! :-)

Thanks,
Rasmus Schultz


For the sake of privacy, I'm not going to post his reply - but the author pretty much rejected every single comment I posted above. That's not to say he doesn't have his own reasons for thinking these features are unimportant or impractical - I just don't agree with any of them.

To me, these are all real and important concerns - and even with IDE support for an official annotation feature in PHP, I don't think this extension is going to be (even remotely) what I had hoped for.

So I'm redoubling my efforts to complete my own implementation - and while it may never see IDE support, perhaps it may serve as evidence to demonstrate the importance of the concerns listed above.

Note that the concerns listed in my e-mail are (loosely) based on what I referenced (or inferred) from the C# annotations feature set, using these features, and seeing how other third-party libraries leverage these features to greatly simplify many aspects of development and maintenance.

C# has the best support for annotations I've seen, and I don't want to put up with less. Although my library is not a port of C# annotations by any means, I believe I've referenced the parts that are relevant to PHP - PHP being a dynamic language of a very different nature.

I would hate to see PHP distributed with another premature language feature - by the time everyone realizes why this feature doesn't cut it, it'll be too late, due to the usual backwards compatibility concerns. Abstraction layers, class-extensions and "semi-official" libraries of annotations (PEAR etc.) will start to emerge, and the whole thing ends up being much less useful (and fun!) than it could have been.

As much as I like PHP, I don't want to see another addition to a list of shortcomings that it's "too late" to expand upon - with an open-source codebase as huge as PHP's, breaking backwards compatibility is pretty much out of the question. It almost never happens. Once a feature is in the official distribution, it's pretty much set in stone. This feature is too important to end up on the "too late" list.
0

#12 User is offline   samdark 

  • Having fun
  • Yii
  • Group: Yii Dev Team
  • Posts: 4,553
  • Joined: 17-January 09
  • Location:Russia

Posted 08 September 2010 - 05:41 AM

Here is the discussion you can be interested in:
http://news.php.net/...internals/49580
0

#13 User is offline   mindplay 

  • Advanced Member
  • PipPipPip
  • Yii
  • Group: Members
  • Posts: 397
  • Joined: 03-September 09
  • Location:New York

Posted 09 September 2010 - 09:20 PM

Thanks, samdark - I'm keeping taps on this extension. I have contacted the reviewer at Zend and made him aware of the problems with this extension, and it seems he is already aware of some of the same issues.

The standard annotations library for my own implementation is still all stubs, but the class library itself is now fairly complete, debugged and tested, with a simple unit test to demonstrate that it works as intended.

http://svn.mindplay....otations/trunk/

I welcome any feedback and comments at this point - feel free to take it for a spin.

As said, if anyone is interested in reviewing the proposed standard annotations library, that would be helpful too.
0

#14 User is offline   mindplay 

  • Advanced Member
  • PipPipPip
  • Yii
  • Group: Members
  • Posts: 397
  • Joined: 03-September 09
  • Location:New York

Posted 20 January 2011 - 06:28 PM

Huh?
0

#15 User is offline   samdark 

  • Having fun
  • Yii
  • Group: Yii Dev Team
  • Posts: 4,553
  • Joined: 17-January 09
  • Location:Russia

Posted 20 January 2011 - 08:05 PM

mindplay
Another spam wave ;)
0

#16 User is offline   mindplay 

  • Advanced Member
  • PipPipPip
  • Yii
  • Group: Members
  • Posts: 397
  • Joined: 03-September 09
  • Location:New York

Posted 04 May 2011 - 08:13 AM

This project now has an official home here:

http://code.google.c...hp-annotations/

I released the first feature-complete and stable version of the annotation framework itself, last night - please see the page for more information.

Note that there's still a lot of work to do - mainly documentation, examples, the standard library of annotations, and eventually integration with other frameworks like Yii.

But it's definitely a milestone! :-)
0

Share this topic:


Page 1 of 1
  • You cannot start a new topic
  • This topic is locked

1 User(s) are reading this topic
0 members, 1 guests, 0 anonymous users