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