On 04/22/2016 12:52 AM, Larry Garfield wrote:
On 4/21/16 4:13 PM, Dmitry Stogov wrote:
Hi,


I would like to present an RFC proposing support for native annotation.

The naming, syntax and behavior are mostly influenced by HHVM Hack, but not exactly the same.

The most interesting difference is an ability to use arbitrary PHP expressions as attribute values.

These expressions are not evaluated, but stored as Abstract Syntax Trees, and later may be accessed (node by node) in PHP extensions, preprocessors and PHP scripts their selves. I think this ability may be useful for "Design By Contract", other formal verification systems, Aspect Oriented Programming, etc


https://wiki.php.net/rfc/attributes


Note that this approach is going to be native, in contrast to doc-comment approach that uses not well defined syntax, and even not parsed by PHP itself.


Additional ideas, endorsement and criticism are welcome.


Thanks. Dmitry.

Thanks, Dmitry! In concept I am in favor of syntax-native annotations, although I have some concerns with the specifics of the proposal. Thoughts in no particular order:

First, for the getAttributes() reflection method, please oh please don't return array-or-false. That's horrible. Just return an empty array if there aren't any, as that makes getAttributes() entirely type safe and saves all callers from a mandatory if-check. (See http://www.garfieldtech.com/blog/empty-return-values for more information.)
Makes sense. I may change this.

The reflection section further indicates that the type of the result is variable, which means I cannot know in advance if I'm going to get back a scalar or an array. If we go with this free-form approach, I'd honestly prefer to always get back an array, even for single value, so that I can always know the type I'm dealing with. (Since I cannot enforce a given attribute to be single-value.)

I'm not sure yet. both decisions may make sense. If I expect just a single value, I'll have to check the number of elements (or just ignore values above the first).

For the expression example:

<<test($a  +  $b   >  0)>>
function  foo($a,  $b)  {
}


It is not at all clear to me what scope the annotation's $a and $b exist in. Are the they same $a and $b as in the function signature? If so, what happens if I reflect the function before ever calling it?
This is just an AST. It may contain any valid PHP expression syntax, but variable, functions and constants don't have to be valid.

How can I evaluate test?
I hope this functionality will be provided by php-ast extension. Currently, it is not a problem to reconstruct PHP source from AST and then use regular eval().
In general, we may find a more efficient way.

Or are they inherited from the global scope at the time of declaration? (That scares me a great deal.) I don't know what to make of that at all.
AST is going to be mainly used by extension and pre-processors (like AOT and DBC), but in general, they also may be used directly in scripts.

<<test($a  +  $b   >  0)>>
function  foo($a,  $b)  {
ast_eval(RefelectionFunction(__FUNCTION__)->getAttributes()["test"]);
}

DB
In the "Attribute syntax" section, the text says the tokens are the left and right double-angle character, as used for quotations in some European languages. The rest of the text says it's two left/right carrot characters, as seen above the comma and period on US keyboards. I'm assuming the former is just a typo/auto-correct bug.

yeah, computers think they are too smart :)

If I read correctly, the following two would be semantically identical:

<<One, Two>>
function foo() {}

<<One>>
<<Two>>
function foo() {}

right

Is there a reason you chose the name "attribute" rather than "annotations", which seems at least in PHP to be the more common term for this type of declaration?

I took the name from HHVM. Personally, I don't care about naming at all.


It appears that the annotations themselves are entirely free-form.
no. they are parsed according to PHP expression syntax rules. syntax mistakes in attributes are going to be caught at compile time.

At the risk of expanding the typing debate, this concerns me as then all we're adding is a new way to parse undocumented, undefined anonymous structs. How can I say what annotations mean what for my ORM, or routing system, or whatever? We're back to, essentially, out-of-band documentation of big anonymous structs (aka associative arrays).

A more robust alternative would be something along the same lines that Doctrine uses: Make annotations actual classes. To wit:


<<AThing>>
<<AnotherThing('stuff')>>
<<MoreThing(1, 2, 3)>>
function foo($a, $b) { }

Where AThing, AnotherThing, and MoreThing are defined classes, and subject to namespaces and use statements. Then what gets returned from getAttributes() is an array consisting of an instance of AThing, an instance of AnotherThing, and an instance of MoreThing. In this example we'd just call their constructors with the listed values and let them do as they will. Doctrine uses named properties in the annotation that maps to properties on the object, which is even more flexible and self-documenting although I don't know how feasible that is without opening up the named properties can of worms globally.
This is just a next level. Attributes are just a storage of meta-data. You may use them as you like :)

function getAttributesAsObjects($r) {
    $ret = array();
    $a = $r->getAttributes();
    foreach ($a as $name => $val) {
        $ret[] = new $name(...$val);
   }
   return $ret;
}

Either way, the advantage then is that I know what annotations are available, and the class itself serves as documentation for what it is, what it does, and what its options are. It also helps address collisions if two different libraries both want to use the same keyword; we already have a class name resolution mechanism that works and everyone is familiar with.

One concern is that not all classes necessarily make sense as an annotation; perhaps only classes with a certain interface can be used. Actually (thinking aloud here), that would be a possible solution to the named property issue. To wit:

<<AThing(a => 'a', b => 'b')>>
foo() {}

class AThing implements Attribute {
  public static function attributeCreate(array $params) {
    return new static($param['a'], $param['b']);
  }
}

$r  = new ReflectionFunction('foo');
$a = $r->getAttributes();

$a is now an array of one element, an instance of AThing, created with 'a' and 'b'. The specifics here are probably terrible, but the general idea of using classes to define annotations is, I think, a big step forward for documentation and avoiding multi-library collisions.

While I know some of the things Drupal 8 is using annotations for are arguably excessive (and I would agree with that argument in some cases), as is I fear the proposed system is too free-form and rudimentary for Drupal to switch to them.

We can't use classes and object in storage directly, because they may be defined in different PHP script, may be changed between requests, etc. but as I showed, it's very easy to construct corresponding objects on request.

Thanks for deep review and good suggestions.

Dmitry.

--
PHP Internals - PHP Runtime Development Mailing List
To unsubscribe, visit: http://www.php.net/unsub.php

Reply via email to