Dear Internals,
I'm announcing a simplified RFC for annotations:
https://wiki.php.net/rfc/simple-annotations
It's an alternative to the proposed Attributes RFC and the (2010)
Annotations RFC.
I'm attempting with this to describe a feature that is closer to the
language than what is proposed by the Attributes RFC, by permitting
the use of any valid PHP expression as an annotation value.
Where the Attributes RFC proposes new syntax for what is essentially
arrays, this proposal instead permits you to use actual arrays, e.g.
without inventing any new syntax. It also allows you to use values of
any other type, including objects. This makes the proposed feature
more immediately useful, with a minimal learning curve.
Compared with the old Annotations RFC (and Doctrine Annotations, etc.)
this proposal does not attempt to define or enforce any rules about
what annotations are, permitted usage, inheritance rules, etc. -
instead it adds a very simple language feature upon which the
community may choose to build (and evolve) facilities that implement
additional rules and logic.
My hope is that, if we can agree on something very small and simple,
we can defer the more opinionated discussions about rules and logic to
the community.
In my opinion, language features should be simple, and consistent with
the language - I believe the way to do that, is to avoid discussions
about how such as facility should be used, and instead focus on how it
should work. There is a lot of opinion built into the old Annotations
RFC, and into Doctrine - features that attempt to dictate how the
feature should be used. I don't believe the language should dictate
what is or isn't correct or appropriate use.
Please review and comment.
Thanks,
Rasmus Schultz
Dear Internals,
I'm announcing a simplified RFC for annotations:
https://wiki.php.net/rfc/simple-annotations
It's an alternative to the proposed Attributes RFC and the (2010)
Annotations RFC.I'm attempting with this to describe a feature that is closer to the
language than what is proposed by the Attributes RFC, by permitting
the use of any valid PHP expression as an annotation value.Where the Attributes RFC proposes new syntax for what is essentially
arrays, this proposal instead permits you to use actual arrays, e.g.
without inventing any new syntax. It also allows you to use values of
any other type, including objects. This makes the proposed feature
more immediately useful, with a minimal learning curve.Compared with the old Annotations RFC (and Doctrine Annotations, etc.)
this proposal does not attempt to define or enforce any rules about
what annotations are, permitted usage, inheritance rules, etc. -
instead it adds a very simple language feature upon which the
community may choose to build (and evolve) facilities that implement
additional rules and logic.My hope is that, if we can agree on something very small and simple,
we can defer the more opinionated discussions about rules and logic to
the community.In my opinion, language features should be simple, and consistent with
the language - I believe the way to do that, is to avoid discussions
about how such as facility should be used, and instead focus on how it
should work. There is a lot of opinion built into the old Annotations
RFC, and into Doctrine - features that attempt to dictate how the
feature should be used. I don't believe the language should dictate
what is or isn't correct or appropriate use.
I think I like this, however a couple of simple questions:
-
Any chance for (optionally) naming annotations? It would be nice to be
able to do: ->getAnnotation('table') and not have to handle unnecessary
annotations -
I like the idea of Hacks memoize for example, how do you see those being
implemented? Or don't you? -
When are annotations executed? On ->getAnnotations()? Every time?
Thanks,
Dear Internals,
I'm announcing a simplified RFC for annotations:
https://wiki.php.net/rfc/simple-annotations
It's an alternative to the proposed Attributes RFC and the (2010)
Annotations RFC.I'm attempting with this to describe a feature that is closer to the
language than what is proposed by the Attributes RFC, by permitting
the use of any valid PHP expression as an annotation value.Where the Attributes RFC proposes new syntax for what is essentially
arrays, this proposal instead permits you to use actual arrays, e.g.
without inventing any new syntax. It also allows you to use values of
any other type, including objects. This makes the proposed feature
more immediately useful, with a minimal learning curve.Compared with the old Annotations RFC (and Doctrine Annotations, etc.)
this proposal does not attempt to define or enforce any rules about
what annotations are, permitted usage, inheritance rules, etc. -
instead it adds a very simple language feature upon which the
community may choose to build (and evolve) facilities that implement
additional rules and logic.My hope is that, if we can agree on something very small and simple,
we can defer the more opinionated discussions about rules and logic to
the community.In my opinion, language features should be simple, and consistent with
the language - I believe the way to do that, is to avoid discussions
about how such as facility should be used, and instead focus on how it
should work. There is a lot of opinion built into the old Annotations
RFC, and into Doctrine - features that attempt to dictate how the
feature should be used. I don't believe the language should dictate
what is or isn't correct or appropriate use.I think I like this, however a couple of simple questions:
Any chance for (optionally) naming annotations? It would be nice to be
able to do: ->getAnnotation('table') and not have to handle unnecessary
annotations
not sure you need that, you can do this instead:
->getAnnotations(Table::class)
however it won't throw an error if you have more than one "Table::class
annotation" whereas I supposed you would expect so with a named annotationI like the idea of Hacks memoize for example, how do you see those being
implemented? Or don't you?
if Rasmus goal is to that "we can agree on something very small and
simple" I guess it would best be left for another rfcWhen are annotations executed? On ->getAnnotations()? Every time?
given they are context free, I suppose they would be executed on the
first call only
but what about this kind of annotation classes? :
class Counter {
private static $i = 0;
private $n;
function __construct() {
$this->n = self::$i++;
}
function get() { return $this->n; }
}
both the getAnnotations() calls order and whether or not annotations are
executed every time would make a difference
Thanks,
--
Mathieu Rochette
Love the simplicity of the RFC, but I can already see people doing terrible
things with it:
<< new DateTimeImmutable() >>
<< log(get_instance('logger')) >>
And other global-state related stuff.
TBH, I'd rather just allow a constant array (with constant expressions
only), and that would be good enough :-)
Cheers,
Marco Pivetta
On Fri, May 13, 2016 at 2:11 PM, Rasmus Schultz rasmus@mindplay.dk
wrote:Dear Internals,
I'm announcing a simplified RFC for annotations:
https://wiki.php.net/rfc/simple-annotations
It's an alternative to the proposed Attributes RFC and the (2010)
Annotations RFC.I'm attempting with this to describe a feature that is closer to the
language than what is proposed by the Attributes RFC, by permitting
the use of any valid PHP expression as an annotation value.Where the Attributes RFC proposes new syntax for what is essentially
arrays, this proposal instead permits you to use actual arrays, e.g.
without inventing any new syntax. It also allows you to use values of
any other type, including objects. This makes the proposed feature
more immediately useful, with a minimal learning curve.Compared with the old Annotations RFC (and Doctrine Annotations, etc.)
this proposal does not attempt to define or enforce any rules about
what annotations are, permitted usage, inheritance rules, etc. -
instead it adds a very simple language feature upon which the
community may choose to build (and evolve) facilities that implement
additional rules and logic.My hope is that, if we can agree on something very small and simple,
we can defer the more opinionated discussions about rules and logic to
the community.In my opinion, language features should be simple, and consistent with
the language - I believe the way to do that, is to avoid discussions
about how such as facility should be used, and instead focus on how it
should work. There is a lot of opinion built into the old Annotations
RFC, and into Doctrine - features that attempt to dictate how the
feature should be used. I don't believe the language should dictate
what is or isn't correct or appropriate use.I think I like this, however a couple of simple questions:
Any chance for (optionally) naming annotations? It would be nice to
be
able to do: ->getAnnotation('table') and not have to handle unnecessary
annotations
not sure you need that, you can do this instead:
->getAnnotations(Table::class)
however it won't throw an error if you have more than one "Table::class
annotation" whereas I supposed you would expect so with a named annotationI like the idea of Hacks memoize for example, how do you see those
being
implemented? Or don't you?
if Rasmus goal is to that "we can agree on something very small and
simple" I guess it would best be left for another rfcWhen are annotations executed? On ->getAnnotations()? Every time?
given they are context free, I suppose they would be executed on the
first call onlybut what about this kind of annotation classes? :
class Counter {
private static $i = 0;
private $n;
function __construct() {
$this->n = self::$i++;
}
function get() { return $this->n; }
}both the getAnnotations() calls order and whether or not annotations are
executed every time would make a differenceThanks,
--
Mathieu Rochette
- I like the idea of Hacks memoize for example, how do you see those being
implemented? Or don't you?
I wrote it in the /attribute grammar/ thread and I write it here again.
Stuff like memoize should be implemented as keywords as part of the
language and not via annotations.
memoized function f() {}
Same goes for deprecated:
deprecated memoized function f() {}
Or other special effects:
deprecated inlined memoized function f() {}
These things are not meta-data for userland, they are meta-data for PHP
and should be part of, well, PHP.
--
Richard "Fleshgrinder" Fussenegger
Dear Internals,
I'm announcing a simplified RFC for annotations:
https://wiki.php.net/rfc/simple-annotations
It's an alternative to the proposed Attributes RFC and the (2010)
Annotations RFC.I'm attempting with this to describe a feature that is closer to the
language than what is proposed by the Attributes RFC, by permitting
the use of any valid PHP expression as an annotation value.Where the Attributes RFC proposes new syntax for what is essentially
arrays, this proposal instead permits you to use actual arrays, e.g.
without inventing any new syntax. It also allows you to use values of
any other type, including objects. This makes the proposed feature
more immediately useful, with a minimal learning curve.
The attributes RFC actually does not specific syntax or arrays, but either
a scalar (number, string) or an ast\node.
A proposal for arrays would actually be "closer to the language" as you
describe it.
Compared with the old Annotations RFC (and Doctrine Annotations, etc.)
this proposal does not attempt to define or enforce any rules about
what annotations are, permitted usage, inheritance rules, etc. -
instead it adds a very simple language feature upon which the
community may choose to build (and evolve) facilities that implement
additional rules and logic.My hope is that, if we can agree on something very small and simple,
we can defer the more opinionated discussions about rules and logic to
the community.In my opinion, language features should be simple, and consistent with
the language - I believe the way to do that, is to avoid discussions
about how such as facility should be used, and instead focus on how it
should work. There is a lot of opinion built into the old Annotations
RFC, and into Doctrine - features that attempt to dictate how the
feature should be used. I don't believe the language should dictate
what is or isn't correct or appropriate use.Please review and comment.
I see you took my question about context from the other thread and defined
annotations to be context less.
You should add to the RFC examples of what is happening, if you do access
context, for example access to undefined variable notices:
<<$x>>
class Foo {}
$refl = new ReflectionClass('Foo');
var_dump($refl->getAttributes(Foo::class));
// PHP Notice: Undefined variable: x in Command line code on line 1
Still this is imho a weak point of your RFC. You already stated that a
"simplified annotation" the way you see it is the expression wrapped in a
closure:
function () {
return php-expression;
}
So if an annotation is a function/closure that belongs to its target
(class, property, function, method, ...) then why wouldn't there be a way
to specify arguments?
The RFC should give an answer to that. The statement you add doesn't make
sense without examples. How does dependency injection even work, when you
don't have a context?
"Annotations that do require context should explicitly ask for that context
- for example, you could use an anonymous function, a callable, or an
anonymous class, to provide context via dependency injection."
greetings
Benjamin
Thanks,
Rasmus Schultz
Thank you for your comments!
Benjamin,
I have removed the comment about annotation expressions executing in
an empty closure, since this example was clearly confusing, and I have
tried to clarify the fact that annotation expressions execute in an
empty scope. To be clear, annotation expressions are not closures,
and they do not accept arguments - there is no reason to complicate
the concept of annotations by introducing an implied scope, when you
can introduce one explicitly, for example:
class Validation {
private $validate;
public function __construct(callable $validate) {
$this->validate = $validate;
}
public function validate($instance) {
return call_user_func($this->validate, $instance);
}
}
<< new Validation(function (User $user) { return false !==
filter_var($user->email, FILTER_VALIDATE_EMAIL); }) >>
class User {
public $email;
}
Of course, you could just as well do this with an interface - as it
the case with most examples I can think of.
If you can think of a meaningful annotation that actually requires
context and can't be implemented in a better/safer/cleaner way with
abstraction, please post? I'd like to add an example, but really have
never seen one that can't be solved better without annotations.
Marco,
Love the simplicity of the RFC, but I can already see people doing terrible things with it
Every language lets you do terrible and great things - I don't believe
in putting up roadblocks and complicating the language that prevent
people from learning.
TBH, I'd rather just allow a constant array (with constant expressions only), and that would be good enough :-)
I don't agree with the idea of crippling a language feature to prevent
beginners from making mistakes - when doing so would also prevent
static analysis and block experienced developers from doing useful
things with objects.
Davey and Mathieu,
Annotations can be "named" by using classes. Per the RFC, defining or
enforcing a rule about allowing only one instance of the same type, is
not a feature.
Hack's memoize is an annotation for the language interpreter itself -
that's beyond the scope of this RFC, but could be implemented in the
future.
Your third question is answered by the RFC here:
Annotation expressions are not evaluated until reflection is invoked, and are evaluated only once and internally memoized upon the first call to getAnnotations().
Thank you for your input so far!
The RFC is 0.2 with the last updates based on your feedback - I would
like to add a suitable case example for annotations that require
context before I'd say this RFC is 1.0.
If anyone can point at a real case example using Doctrine Annotations
(or anything else) please do!
Thanks,
Rasmus
Dear Internals,
I'm announcing a simplified RFC for annotations:
https://wiki.php.net/rfc/simple-annotations
It's an alternative to the proposed Attributes RFC and the (2010)
Annotations RFC.I'm attempting with this to describe a feature that is closer to the
language than what is proposed by the Attributes RFC, by permitting
the use of any valid PHP expression as an annotation value.Where the Attributes RFC proposes new syntax for what is essentially
arrays, this proposal instead permits you to use actual arrays, e.g.
without inventing any new syntax. It also allows you to use values of
any other type, including objects. This makes the proposed feature
more immediately useful, with a minimal learning curve.The attributes RFC actually does not specific syntax or arrays, but either a
scalar (number, string) or an ast\node.A proposal for arrays would actually be "closer to the language" as you
describe it.Compared with the old Annotations RFC (and Doctrine Annotations, etc.)
this proposal does not attempt to define or enforce any rules about
what annotations are, permitted usage, inheritance rules, etc. -
instead it adds a very simple language feature upon which the
community may choose to build (and evolve) facilities that implement
additional rules and logic.My hope is that, if we can agree on something very small and simple,
we can defer the more opinionated discussions about rules and logic to
the community.In my opinion, language features should be simple, and consistent with
the language - I believe the way to do that, is to avoid discussions
about how such as facility should be used, and instead focus on how it
should work. There is a lot of opinion built into the old Annotations
RFC, and into Doctrine - features that attempt to dictate how the
feature should be used. I don't believe the language should dictate
what is or isn't correct or appropriate use.Please review and comment.
I see you took my question about context from the other thread and defined
annotations to be context less.
You should add to the RFC examples of what is happening, if you do access
context, for example access to undefined variable notices:<<$x>>
class Foo {}$refl = new ReflectionClass('Foo');
var_dump($refl->getAttributes(Foo::class));
// PHP Notice: Undefined variable: x in Command line code on line 1Still this is imho a weak point of your RFC. You already stated that a
"simplified annotation" the way you see it is the expression wrapped in a
closure:function () {
return php-expression;
}So if an annotation is a function/closure that belongs to its target (class,
property, function, method, ...) then why wouldn't there be a way to specify
arguments?
The RFC should give an answer to that. The statement you add doesn't make
sense without examples. How does dependency injection even work, when you
don't have a context?"Annotations that do require context should explicitly ask for that context
- for example, you could use an anonymous function, a callable, or an
anonymous class, to provide context via dependency injection."greetings
BenjaminThanks,
Rasmus Schultz
Hack's memoize is an annotation for the language interpreter itself -
that's beyond the scope of this RFC, but could be implemented in the
future.
Please can you add something to the RFC that reserves the possibility
to do this, so that it can be introduced in a BC compatible way in the
future?
Hand-waving away problems and saying that they aren't in scope is a
sure-fire way to build deficiencies into a design.
This is not a smaller problem in userland code, as it is easy for
people to migrate from one library to another, but is almost
impossible to overcome in core PHP, where any BC-breaking change
causes lots of difficultly for end-users.
cheers
Dan
Thank you for your comments!
Benjamin,
I have removed the comment about annotation expressions executing in
an empty closure, since this example was clearly confusing, and I have
tried to clarify the fact that annotation expressions execute in an
empty scope. To be clear, annotation expressions are not closures,
and they do not accept arguments - there is no reason to complicate
the concept of annotations by introducing an implied scope, when you
can introduce one explicitly, for example:class Validation {
private $validate;public function __construct(callable $validate) { $this->validate = $validate; } public function validate($instance) { return call_user_func($this->validate, $instance); }
}
<< new Validation(function (User $user) { return false !==
filter_var($user->email, FILTER_VALIDATE_EMAIL); }) >>
class User {
public $email;
}Of course, you could just as well do this with an interface - as it
the case with most examples I can think of.If you can think of a meaningful annotation that actually requires
context and can't be implemented in a better/safer/cleaner way with
abstraction, please post? I'd like to add an example, but really have
never seen one that can't be solved better without annotations.
Every proposal before was about annotation/attributes as metadata for code.
Your proposal makes them executable functions instead (nameless, attached
to code blocks).
I think this is just way out of scope of what the 80% want to achieve with
this feature.
Marco,
Love the simplicity of the RFC, but I can already see people doing
terrible things with itEvery language lets you do terrible and great things - I don't believe
in putting up roadblocks and complicating the language that prevent
people from learning.TBH, I'd rather just allow a constant array (with constant expressions
only), and that would be good enough :-)I don't agree with the idea of crippling a language feature to prevent
beginners from making mistakes - when doing so would also prevent
static analysis and block experienced developers from doing useful
things with objects.Davey and Mathieu,
Annotations can be "named" by using classes. Per the RFC, defining or
enforcing a rule about allowing only one instance of the same type, is
not a feature.Hack's memoize is an annotation for the language interpreter itself -
that's beyond the scope of this RFC, but could be implemented in the
future.Your third question is answered by the RFC here:
Annotation expressions are not evaluated until reflection is invoked,
and are evaluated only once and internally memoized upon the first call to
getAnnotations().Thank you for your input so far!
The RFC is 0.2 with the last updates based on your feedback - I would
like to add a suitable case example for annotations that require
context before I'd say this RFC is 1.0.If anyone can point at a real case example using Doctrine Annotations
(or anything else) please do!Thanks,
RasmusOn Sat, May 14, 2016 at 4:38 AM, Benjamin Eberlei kontakt@beberlei.de
wrote:On Fri, May 13, 2016 at 2:11 PM, Rasmus Schultz rasmus@mindplay.dk
wrote:Dear Internals,
I'm announcing a simplified RFC for annotations:
https://wiki.php.net/rfc/simple-annotations
It's an alternative to the proposed Attributes RFC and the (2010)
Annotations RFC.I'm attempting with this to describe a feature that is closer to the
language than what is proposed by the Attributes RFC, by permitting
the use of any valid PHP expression as an annotation value.Where the Attributes RFC proposes new syntax for what is essentially
arrays, this proposal instead permits you to use actual arrays, e.g.
without inventing any new syntax. It also allows you to use values of
any other type, including objects. This makes the proposed feature
more immediately useful, with a minimal learning curve.The attributes RFC actually does not specific syntax or arrays, but
either a
scalar (number, string) or an ast\node.A proposal for arrays would actually be "closer to the language" as you
describe it.Compared with the old Annotations RFC (and Doctrine Annotations, etc.)
this proposal does not attempt to define or enforce any rules about
what annotations are, permitted usage, inheritance rules, etc. -
instead it adds a very simple language feature upon which the
community may choose to build (and evolve) facilities that implement
additional rules and logic.My hope is that, if we can agree on something very small and simple,
we can defer the more opinionated discussions about rules and logic to
the community.In my opinion, language features should be simple, and consistent with
the language - I believe the way to do that, is to avoid discussions
about how such as facility should be used, and instead focus on how it
should work. There is a lot of opinion built into the old Annotations
RFC, and into Doctrine - features that attempt to dictate how the
feature should be used. I don't believe the language should dictate
what is or isn't correct or appropriate use.Please review and comment.
I see you took my question about context from the other thread and
defined
annotations to be context less.
You should add to the RFC examples of what is happening, if you do access
context, for example access to undefined variable notices:<<$x>>
class Foo {}$refl = new ReflectionClass('Foo');
var_dump($refl->getAttributes(Foo::class));
// PHP Notice: Undefined variable: x in Command line code on line 1Still this is imho a weak point of your RFC. You already stated that a
"simplified annotation" the way you see it is the expression wrapped in a
closure:function () {
return php-expression;
}So if an annotation is a function/closure that belongs to its target
(class,
property, function, method, ...) then why wouldn't there be a way to
specify
arguments?
The RFC should give an answer to that. The statement you add doesn't make
sense without examples. How does dependency injection even work, when you
don't have a context?"Annotations that do require context should explicitly ask for that
context
- for example, you could use an anonymous function, a callable, or an
anonymous class, to provide context via dependency injection."greetings
BenjaminThanks,
Rasmus Schultz
Dan,
I've added a note about special annotations to the "future scope"
section, naming the memoization-annotation as an example.
Benjamin,
I don't think you mean "out of scope" of what 80% want, I think you
mean "beyond the scope"?
I think this will definitely cover what 80% want to achieve, and
possibly a lot more - possibly new and useful patterns that we can't
even predict.
Thank you for your comments!
Benjamin,
I have removed the comment about annotation expressions executing in
an empty closure, since this example was clearly confusing, and I have
tried to clarify the fact that annotation expressions execute in an
empty scope. To be clear, annotation expressions are not closures,
and they do not accept arguments - there is no reason to complicate
the concept of annotations by introducing an implied scope, when you
can introduce one explicitly, for example:class Validation {
private $validate;public function __construct(callable $validate) { $this->validate = $validate; } public function validate($instance) { return call_user_func($this->validate, $instance); }
}
<< new Validation(function (User $user) { return false !==
filter_var($user->email, FILTER_VALIDATE_EMAIL); }) >>
class User {
public $email;
}Of course, you could just as well do this with an interface - as it
the case with most examples I can think of.If you can think of a meaningful annotation that actually requires
context and can't be implemented in a better/safer/cleaner way with
abstraction, please post? I'd like to add an example, but really have
never seen one that can't be solved better without annotations.Every proposal before was about annotation/attributes as metadata for code.
Your proposal makes them executable functions instead (nameless, attached to
code blocks).I think this is just way out of scope of what the 80% want to achieve with
this feature.Marco,
Love the simplicity of the RFC, but I can already see people doing
terrible things with itEvery language lets you do terrible and great things - I don't believe
in putting up roadblocks and complicating the language that prevent
people from learning.TBH, I'd rather just allow a constant array (with constant expressions
only), and that would be good enough :-)I don't agree with the idea of crippling a language feature to prevent
beginners from making mistakes - when doing so would also prevent
static analysis and block experienced developers from doing useful
things with objects.Davey and Mathieu,
Annotations can be "named" by using classes. Per the RFC, defining or
enforcing a rule about allowing only one instance of the same type, is
not a feature.Hack's memoize is an annotation for the language interpreter itself -
that's beyond the scope of this RFC, but could be implemented in the
future.Your third question is answered by the RFC here:
Annotation expressions are not evaluated until reflection is invoked,
and are evaluated only once and internally memoized upon the first call to
getAnnotations().Thank you for your input so far!
The RFC is 0.2 with the last updates based on your feedback - I would
like to add a suitable case example for annotations that require
context before I'd say this RFC is 1.0.If anyone can point at a real case example using Doctrine Annotations
(or anything else) please do!Thanks,
RasmusOn Sat, May 14, 2016 at 4:38 AM, Benjamin Eberlei kontakt@beberlei.de
wrote:On Fri, May 13, 2016 at 2:11 PM, Rasmus Schultz rasmus@mindplay.dk
wrote:Dear Internals,
I'm announcing a simplified RFC for annotations:
https://wiki.php.net/rfc/simple-annotations
It's an alternative to the proposed Attributes RFC and the (2010)
Annotations RFC.I'm attempting with this to describe a feature that is closer to the
language than what is proposed by the Attributes RFC, by permitting
the use of any valid PHP expression as an annotation value.Where the Attributes RFC proposes new syntax for what is essentially
arrays, this proposal instead permits you to use actual arrays, e.g.
without inventing any new syntax. It also allows you to use values of
any other type, including objects. This makes the proposed feature
more immediately useful, with a minimal learning curve.The attributes RFC actually does not specific syntax or arrays, but
either a
scalar (number, string) or an ast\node.A proposal for arrays would actually be "closer to the language" as
you
describe it.Compared with the old Annotations RFC (and Doctrine Annotations, etc.)
this proposal does not attempt to define or enforce any rules about
what annotations are, permitted usage, inheritance rules, etc. -
instead it adds a very simple language feature upon which the
community may choose to build (and evolve) facilities that implement
additional rules and logic.My hope is that, if we can agree on something very small and simple,
we can defer the more opinionated discussions about rules and logic to
the community.In my opinion, language features should be simple, and consistent with
the language - I believe the way to do that, is to avoid discussions
about how such as facility should be used, and instead focus on how it
should work. There is a lot of opinion built into the old Annotations
RFC, and into Doctrine - features that attempt to dictate how the
feature should be used. I don't believe the language should dictate
what is or isn't correct or appropriate use.Please review and comment.
I see you took my question about context from the other thread and
defined
annotations to be context less.
You should add to the RFC examples of what is happening, if you do
access
context, for example access to undefined variable notices:<<$x>>
class Foo {}$refl = new ReflectionClass('Foo');
var_dump($refl->getAttributes(Foo::class));
// PHP Notice: Undefined variable: x in Command line code on line 1Still this is imho a weak point of your RFC. You already stated that a
"simplified annotation" the way you see it is the expression wrapped in
a
closure:function () {
return php-expression;
}So if an annotation is a function/closure that belongs to its target
(class,
property, function, method, ...) then why wouldn't there be a way to
specify
arguments?
The RFC should give an answer to that. The statement you add doesn't
make
sense without examples. How does dependency injection even work, when
you
don't have a context?"Annotations that do require context should explicitly ask for that
context
- for example, you could use an anonymous function, a callable, or an
anonymous class, to provide context via dependency injection."greetings
BenjaminThanks,
Rasmus Schultz
Dan,
I've added a note about special annotations to the "future scope"
section, naming the memoization-annotation as an example.
This doesn't really explain how such a feature would fit it into the
proposal. For instance, how can we avoid the syntax clashing with
existing userland issues? Given that annotations in this proposal don't
have names, we can't just reserve all names beginning "__" or "php".
Perhaps they would be pseudo-function calls, like <<__memoize()>>? How
would that (or any other undefined function) act if accessed on a
version that didn't have the feature?
I realise you don't want to define the details, but an idea of what an
annotation targeting the engine would look like would make the
future-proofing clearer.
Regards,
Rowan Collins
[IMSoP]
Rowan,
I'm afraid I don't follow. Why is it a problem to simply add a
function or class to the global namespace?
<< new Memoize() >>
or maybe a class with factory functions for built-in annotations:
<< Meta::memoize() >>
I tend to agree with Richard though, that system directives really
ought to be supported by the language with keywords rather than
annotations.
Richard,
Regarding your point of view:
The problem is that an annotation by definition is just meta-data. Your
proposal however might result in fatal errors from meta-data
I agree that annotations are just meta-data. Values.
I disagree that we can't permit the use of PHP expressions to generate
those values.
You can't eliminate problems such as missing classes - and you
shouldn't. With the examples in the Attributes RFC, as well as with
e.g. Doctrine Annotations, we have the exact same problems, they're
just one step removed from the language feature, which only adds
complexity and makes it harder to debug these things.
The only work-around to an issue such as a missing class, is to
complete ignore the annotation - which leads to undefined behavior and
scenarios that are even harder to debug.
The other big problem with this position, is that it goes more or less
directly against what the community wants - the majority of those who
use annotations in PHP today, use Doctrine Annotations, and they
expect annotations as classes.
Source code can contain errors. I don't think there's really any way
around that. Even if you wittled down metadata to the absolute minimum
- a string, such as what it is in Go code, that string is invariably
going to contain something that needs to be parsed or interpreted
somehow, somewhere, at some point, and it can lead to errors.
In my opinion, it's better to allow those errors to be caught early -
for example, being able to catch syntax errors in expressions already
at load-time. The attributes RFC is much worse in this regard - you
could be lugging around all kinds of dependency errors, missing
classes and whatnot, and you'd never know about it until the day you
hit one of those annotations. Now instead, you're just stuck having to
type out redundant unit-tests to prove that every annotation is valid,
when the language could have taken care of that in a much simpler way.
The other issue is dependencies. The Attributes RFC will lead to code
with all sorts of hidden dependencies - possibly dependencies that you
can't even know about, because you can't know how your metadata is
going to be interpreted precisely. You can't know which classes your
metadata might get mapped to or how. Simple Annotations take care of
that, and they force you to e.g. composer require
the package that
provides the annotation classes you're using - this is a good thing.
Hiding dependencies makes it really difficult for others to reason
about your code. The Attributes RFC will lead to a lot of hidden rules
- whereas Simple Annotations are simple and straight-forward, nothing
hidden or implicit, your dependencies visible and traceable, which
will lead to much better tooling. For example, an IDE or offline code
analysis tool would catch syntax errors and missing classes and warn
about those right away.
I'm sorry, but I think that the idea of meta-data that can't error
somehow, somewhere, at some point, is completely unrealistic.
And I'd much rather have a simple facility that enables those errors
to surface quickly.
Dan,
I've added a note about special annotations to the "future scope"
section, naming the memoization-annotation as an example.This doesn't really explain how such a feature would fit it into the
proposal. For instance, how can we avoid the syntax clashing with existing
userland issues? Given that annotations in this proposal don't have names,
we can't just reserve all names beginning "__" or "php".Perhaps they would be pseudo-function calls, like <<__memoize()>>? How would
that (or any other undefined function) act if accessed on a version that
didn't have the feature?I realise you don't want to define the details, but an idea of what an
annotation targeting the engine would look like would make the
future-proofing clearer.Regards,
Rowan Collins
[IMSoP]
I'm afraid I don't follow. Why is it a problem to simply add a
function or class to the global namespace?<< new Memoize() >>
or maybe a class with factory functions for built-in annotations:
<< Meta::memoize() >>
Well, those particular names look quite likely to collide with users' code, though I guess more obviously reserved names could be used.
More generally, though, neither feels a natural fit, because of the need to specify a value, rather than a name. On Reflection, what would an instance of class Memoize look like? Could you call "new Memoize" somewhere else?
If a memoize() function was executed, what value would it return? Since you can only see the evaluated value, how could you detect the annotation for debugging?
Indeed, if we're interested only in values, not where they come from, are the following all equivalent, and the mechanics totally unndetectable by Reflection?
<<null>>
<<(function(){return null;})()>>
function foo() { return null; }
<< foo() >>
If the function form doesn't receive any context, its only purpose would be to copy some runtime global state into the annotation value on first access, which seems like a strange thing to do.
I tend to agree with Richard though, that system directives really
ought to be supported by the language with keywords rather than
annotations.
That would be one way around it, indeed, so rather than "Future Scope",
you could label "compiler directive" type annotations as "deliberately
out of scope".
Regards,
Rowan Collins
[IMSoP]
I'm sorry, but I think that the idea of meta-data that can't error
somehow, somewhere, at some point, is completely unrealistic.And I'd much rather have a simple facility that enables those errors
to surface quickly.
It should error and it should error exactly at the point when you want
to use it and at no other point. Right now they will error anytime
anyone wants to do anything with any annotation.
I fully understand the urge to directly access objects and I actually
support it and I want them to error out at the point where you try to
instantiate them. However, not when I simply access annotations in
introspection.
According to your logic I have to load the whole dependency chain, even
thought I just want to generate my documentation that might have some
annotations in use. I also need to load all my dependencies even though
I wanted to leave the security dependency out because I wanted to easily
disable the security annotation system locally for development. I even
have to load the whole dependency chain, even though I just want to
introspect the single data structure at hand without invoking anything.
Even worse if I am using a package that has Doctrine annotations but I
use it without them: /fatal error/
Nice? No!
Solutions?
Make the /simple/ annotations /simple/. ;-)
Only allow scalar data to be defined and allow userland to register
themselves for specific annotations. I mentioned this before but somehow
everyone wants something more fancy and I have no karma (even though
requested but ignored) to write my own RFC. That being said, you have
the ability to write an RFC so please just take it. :-)
Some PHP code to illustrate what I was thinking of since I am better
with code than with words. :-P
https://gist.github.com/Fleshgrinder/d26cd4751827b8c10a0dc33da32b48c3
Reflection is the wrong tool for the job to build annotation systems.
Reflection should not be used in production it should be used to
introspect data structures and reason about them or other stuff during
unit tests.
However, reflection should provide the ability to read annotations, it
just does not do anything with them by default and outputs stupid
arrays. You will definitely understand what I mean it you follow the Gist.
I am sure there is room for improvement and that I overlooked something
in my Gist. But I think it is a starting point, some additional notes:
- I do not see any benefit in annotations for parameters.
- I do not see any benefit in annotations for Generators.
- I do not see any benefit for annotations in php-src.
My proposal would include to ban annotations from php-src and make it a
pure userland functionality. This has many advantages:
- No name collisions.
- Clear policy for the future.
- php-src features are never optional.
- php-src does not provide meta-data.
Let me know what you think or if you have questions in regards to the
Gist that are unclear. (Or maybe you found mistakes.)
--
Richard "Fleshgrinder" Fussenegger
Richard,
I believe I have already stated my position on all of these concerns
in my last email.
Short recap:
There's no such thing as optional dependencies - if you depend on a
Doctrine ORM annotation, you depend on that package.
The code should (must) error the moment you try to instantiate those
annotations - whether you intend to use them or not is besides the
question - a missing class is an error, in annotations as it is
anywhere else in your code.
Silently ignoring missing annotations solves nothing - it just leads
to errors that are even harder to find and debug.
You seem awfully concerned about name collisions? Most packages use a
vendor\package namespace convention now - PHP has always been adding
things to the global namespace, and it's never been a really
substantial problem. I'm not that concerned.
The one new thing you brought up here, is the issue of potentially
loading/instantiating a bunch of annotations that go unused. From a
dependency point of view, I maintain that that's correct and it should
error out on missing classes.
From a performance point of view, you may have a valid concern, and
this had occurred to me before - but I decided to leave it alone, for
a couple of reasons. The most important reason being, look at how
annotations get used in practice: controllers get things like route
and filter annotations, all of which are usually needed at the same
time; entities get ORM annotations, all of which are usually needed at
the same time; form models get validation and input-type annotations,
all of which are usually needed at the same time. And so on.
The fact of the matter is that classes tend to have a single
responsibility, and this usually means that applicable annotations
tend to belong to a specific domain, and are needed at the same time.
In other words, this problem is pretty theoretical - in reality, it's
a very small problem, which, if somebody was very concerned about,
could absolutely be solved in userland, by adding a cache layer. Which
projects like Doctrine Annotations would likely do anyway, in order to
cache the result of mode complex annotation logic to control
inheritance, cardinality, applicability, etc. - all the complex stuff
that this RFC stays away from.
The extreme case of what you're proposing, is what Go does - an
annotation is simply a string. No chance of any error, anywhere, ever,
right? Wrong. People put JSON data in those strings for example, and
parsing that data leads to run-time errors instead. That's the extreme
example, but the problem with what you're proposing is precisely the
same: you allow something like nested array data structures, those are
still going to get interpreted and mapped to objects at run-time, and
it leads to run-time errors instead.
I maintain that the majority use-case is object annotations - and that
deferring the construction of those objects doesn't solve anything.
You're just deferring or hiding the problem, adding complexity, and
mental as well as practical debugging effort - but the problem doesn't
go away.
I am and will always be in favor of language design that takes the
most direct route to satisfy a requirement. In this case, the
requirement is annotations as objects - and the most natural and
direct route is the "new" keyword, static method calls, or whatever
way you normally create objects; inventing a more indirect way to do
that just adds complexity.
I'm sorry, but I think that the idea of meta-data that can't error
somehow, somewhere, at some point, is completely unrealistic.And I'd much rather have a simple facility that enables those errors
to surface quickly.It should error and it should error exactly at the point when you want
to use it and at no other point. Right now they will error anytime
anyone wants to do anything with any annotation.I fully understand the urge to directly access objects and I actually
support it and I want them to error out at the point where you try to
instantiate them. However, not when I simply access annotations in
introspection.According to your logic I have to load the whole dependency chain, even
thought I just want to generate my documentation that might have some
annotations in use. I also need to load all my dependencies even though
I wanted to leave the security dependency out because I wanted to easily
disable the security annotation system locally for development. I even
have to load the whole dependency chain, even though I just want to
introspect the single data structure at hand without invoking anything.Even worse if I am using a package that has Doctrine annotations but I
use it without them: /fatal error/Nice? No!
Solutions?
Make the /simple/ annotations /simple/. ;-)
Only allow scalar data to be defined and allow userland to register
themselves for specific annotations. I mentioned this before but somehow
everyone wants something more fancy and I have no karma (even though
requested but ignored) to write my own RFC. That being said, you have
the ability to write an RFC so please just take it. :-)Some PHP code to illustrate what I was thinking of since I am better
with code than with words. :-Phttps://gist.github.com/Fleshgrinder/d26cd4751827b8c10a0dc33da32b48c3
Reflection is the wrong tool for the job to build annotation systems.
Reflection should not be used in production it should be used to
introspect data structures and reason about them or other stuff during
unit tests.However, reflection should provide the ability to read annotations, it
just does not do anything with them by default and outputs stupid
arrays. You will definitely understand what I mean it you follow the Gist.I am sure there is room for improvement and that I overlooked something
in my Gist. But I think it is a starting point, some additional notes:
- I do not see any benefit in annotations for parameters.
- I do not see any benefit in annotations for Generators.
- I do not see any benefit for annotations in php-src.
My proposal would include to ban annotations from php-src and make it a
pure userland functionality. This has many advantages:
- No name collisions.
- Clear policy for the future.
- php-src features are never optional.
- php-src does not provide meta-data.
Let me know what you think or if you have questions in regards to the
Gist that are unclear. (Or maybe you found mistakes.)--
Richard "Fleshgrinder" Fussenegger
I'd like to finish this RFC, but I have two remaining issues.
The syntax is a minor issue - since any valid PHP expression can be
used, the bit-shift ambiguity is technically an issue, however
marginal.
A lot of people commented on the syntax when I posted the RFC on
reddit - they don't like it.
Anyone have any ideas for an alternative syntax? It needs to be
delimited, e.g. needs to use opening and closing delimiters or a
recognizable opening delimiter... Here's some ideas:
@{ new Table("user") }
@[ new Table("user") ]
{{ new Table("user") }}
+{ new Table("user") }
I don't like any of these really, but the bit-shift operator isn't
going when the stuff inside is an expression which could include
bit-shift (is it?)
The other issue is the dependency (context) injection example - no one
seems to be able to cite an actual use-case, and if that's the case, I
should probably just remove it from the RFC entirely?
@Larry can you think of a case example in the myriad annotations
you've seen in Drupal code? :-)
Anyone else using Doctrine Annotations actually seen anyone making use
of a closure in an annotation?
Richard,
I believe I have already stated my position on all of these concerns
in my last email.Short recap:
There's no such thing as optional dependencies - if you depend on a
Doctrine ORM annotation, you depend on that package.The code should (must) error the moment you try to instantiate those
annotations - whether you intend to use them or not is besides the
question - a missing class is an error, in annotations as it is
anywhere else in your code.Silently ignoring missing annotations solves nothing - it just leads
to errors that are even harder to find and debug.You seem awfully concerned about name collisions? Most packages use a
vendor\package namespace convention now - PHP has always been adding
things to the global namespace, and it's never been a really
substantial problem. I'm not that concerned.The one new thing you brought up here, is the issue of potentially
loading/instantiating a bunch of annotations that go unused. From a
dependency point of view, I maintain that that's correct and it should
error out on missing classes.From a performance point of view, you may have a valid concern, and
this had occurred to me before - but I decided to leave it alone, for
a couple of reasons. The most important reason being, look at how
annotations get used in practice: controllers get things like route
and filter annotations, all of which are usually needed at the same
time; entities get ORM annotations, all of which are usually needed at
the same time; form models get validation and input-type annotations,
all of which are usually needed at the same time. And so on.The fact of the matter is that classes tend to have a single
responsibility, and this usually means that applicable annotations
tend to belong to a specific domain, and are needed at the same time.In other words, this problem is pretty theoretical - in reality, it's
a very small problem, which, if somebody was very concerned about,
could absolutely be solved in userland, by adding a cache layer. Which
projects like Doctrine Annotations would likely do anyway, in order to
cache the result of mode complex annotation logic to control
inheritance, cardinality, applicability, etc. - all the complex stuff
that this RFC stays away from.The extreme case of what you're proposing, is what Go does - an
annotation is simply a string. No chance of any error, anywhere, ever,
right? Wrong. People put JSON data in those strings for example, and
parsing that data leads to run-time errors instead. That's the extreme
example, but the problem with what you're proposing is precisely the
same: you allow something like nested array data structures, those are
still going to get interpreted and mapped to objects at run-time, and
it leads to run-time errors instead.I maintain that the majority use-case is object annotations - and that
deferring the construction of those objects doesn't solve anything.You're just deferring or hiding the problem, adding complexity, and
mental as well as practical debugging effort - but the problem doesn't
go away.I am and will always be in favor of language design that takes the
most direct route to satisfy a requirement. In this case, the
requirement is annotations as objects - and the most natural and
direct route is the "new" keyword, static method calls, or whatever
way you normally create objects; inventing a more indirect way to do
that just adds complexity.I'm sorry, but I think that the idea of meta-data that can't error
somehow, somewhere, at some point, is completely unrealistic.And I'd much rather have a simple facility that enables those errors
to surface quickly.It should error and it should error exactly at the point when you want
to use it and at no other point. Right now they will error anytime
anyone wants to do anything with any annotation.I fully understand the urge to directly access objects and I actually
support it and I want them to error out at the point where you try to
instantiate them. However, not when I simply access annotations in
introspection.According to your logic I have to load the whole dependency chain, even
thought I just want to generate my documentation that might have some
annotations in use. I also need to load all my dependencies even though
I wanted to leave the security dependency out because I wanted to easily
disable the security annotation system locally for development. I even
have to load the whole dependency chain, even though I just want to
introspect the single data structure at hand without invoking anything.Even worse if I am using a package that has Doctrine annotations but I
use it without them: /fatal error/Nice? No!
Solutions?
Make the /simple/ annotations /simple/. ;-)
Only allow scalar data to be defined and allow userland to register
themselves for specific annotations. I mentioned this before but somehow
everyone wants something more fancy and I have no karma (even though
requested but ignored) to write my own RFC. That being said, you have
the ability to write an RFC so please just take it. :-)Some PHP code to illustrate what I was thinking of since I am better
with code than with words. :-Phttps://gist.github.com/Fleshgrinder/d26cd4751827b8c10a0dc33da32b48c3
Reflection is the wrong tool for the job to build annotation systems.
Reflection should not be used in production it should be used to
introspect data structures and reason about them or other stuff during
unit tests.However, reflection should provide the ability to read annotations, it
just does not do anything with them by default and outputs stupid
arrays. You will definitely understand what I mean it you follow the Gist.I am sure there is room for improvement and that I overlooked something
in my Gist. But I think it is a starting point, some additional notes:
- I do not see any benefit in annotations for parameters.
- I do not see any benefit in annotations for Generators.
- I do not see any benefit for annotations in php-src.
My proposal would include to ban annotations from php-src and make it a
pure userland functionality. This has many advantages:
- No name collisions.
- Clear policy for the future.
- php-src features are never optional.
- php-src does not provide meta-data.
Let me know what you think or if you have questions in regards to the
Gist that are unclear. (Or maybe you found mistakes.)--
Richard "Fleshgrinder" Fussenegger
The other issue is the dependency (context) injection example - no one
seems to be able to cite an actual use-case, and if that's the case, I
should probably just remove it from the RFC entirely?@Larry can you think of a case example in the myriad annotations
you've seen in Drupal code? :-)Anyone else using Doctrine Annotations actually seen anyone making use
of a closure in an annotation?
Possibly nobody's responded to your call for an example because they're
not sure what it is you're asking for an example of. I don't really
understand what closures have to do with annotations, or how that
relates to capturing context.
An example from the other thread of a context-bound annotation would be
implementing validation like this:
<<ValidateRange($percentage, 0, 100)>>
function foo(int $percentage)
This would appear to be unsupported as such by your proposal, so you
would instead just have to use strings to represent how it needed to be
accessed:
<<new ValidateRangeAnnotation('$percentage', 0, 100)>>
and then have extra machinery in the reflection logic to extract the
parameters and pass them to the object, e.g.
$annotation->validate($reflected_parameters)
This might be the more sensible approach to such an annotation anyway,
but without any access to variables, I don't really understand the value
of evaluating an expression for the annotation, just to get a constant
value that is cached by the Reflection infrastructure.
If the expression would have to be made up entirely of constants anyway,
might the same "constant expressions" used in class const definitions be
a better fit than "any valid PHP expression" - plus a specific exception
for creating objects out of those constant expressions.
Regards,
Rowan Collins
[IMSoP]
@Rasmus: This approach is too broad, allowing situations like Marco pointed
out. I'd have to vote -1 on it too if you move forward, specially if you
consider things like << "How do I grab this?" >> and other weirdness.
@Rowan: Annotations should be immutable by nature. Still, I would love if
you can detail (in the context of your example) what should be the output
in the following examples:
<<ValidateRange($percentage, 0, 100)>>
function foo(int $percentage) {
$reflection = new \ReflectionFunction(FUNCTION);
var_dump($reflection->getAnnotations()); // #2
}
$reflection = new \ReflectionFunction("foo");
var_dump($reflection->getAnnotations()); // #1
foo(10);
When executing the code, what should I expect from #1 and #2? I can foresee
that #1 should crash (?) or should it return an ast\node or what?
It all sounds reasonable on #2 what everyone is heavily forcing/suggesting
because of DbC, but it only makes sense in the context of a function call
(or a bound object in case of OO members). In any other case its outcome is
unpredictable.
There're 2 potential approaches I can foresee with future for Annotations:
1- This one I see as a simple approach. Take the idea of Dmitry and extend
to support scalars and arrays (only, no objects, no AST\Nodes). Example: <<
Entity(["name" => "User"]) >>
Strings could be easily converted into AST\Nodes internally and DbC would
be possible. Context is inferred at libraries' desire.
Solution here would not have a 1-1 dependency with classes, simplifying the
implementation for meta parameters such as "override", "inherit",
"memoize", "jit", etc.
2- Take an OO approach and correlate 1-1 class. But before that I'd tackle
object initialization first. We'd require to make this to work:
new Foo() {
"property" => "value"
}
After that, we could get back to Annotations and support a subset of PHP
grammar, similar to this:
<< Foo { "property" => "value" } >>
Why I suggest this approach, some may ask? Simple young padawans... We're
already seeing people discuss about final/read-only properties, where they
could only be set at constructor time. We could easily expose this
possibility through scopes. The {} block would be post constructor, but
still in the initialization context, allowing sets to happen for read-only
properties. It's a smart solution against adding a long list of constructor
arguments (and handling default values for each one) and also a nice
alternative to "named parameters".
However, this approach would still require a 1-1 dependency with classes,
which would make "override", "inherit", "jit" a PITA, and likely empower a
more robust annotation solution (like defining which class can be an
annotation and what cannot, or specify annotation target, like class,
method, property, etc).
PS: I hate to write long emails... =(
Cheers,
On Tue, May 17, 2016 at 11:28 AM, Rowan Collins rowan.collins@gmail.com
wrote:
The other issue is the dependency (context) injection example - no one
seems to be able to cite an actual use-case, and if that's the case, I
should probably just remove it from the RFC entirely?@Larry can you think of a case example in the myriad annotations
you've seen in Drupal code? :-)Anyone else using Doctrine Annotations actually seen anyone making use
of a closure in an annotation?Possibly nobody's responded to your call for an example because they're
not sure what it is you're asking for an example of. I don't really
understand what closures have to do with annotations, or how that relates
to capturing context.An example from the other thread of a context-bound annotation would be
implementing validation like this:<<ValidateRange($percentage, 0, 100)>>
function foo(int $percentage)This would appear to be unsupported as such by your proposal, so you would
instead just have to use strings to represent how it needed to be accessed:<<new ValidateRangeAnnotation('$percentage', 0, 100)>>
and then have extra machinery in the reflection logic to extract the
parameters and pass them to the object, e.g.
$annotation->validate($reflected_parameters)This might be the more sensible approach to such an annotation anyway, but
without any access to variables, I don't really understand the value of
evaluating an expression for the annotation, just to get a constant value
that is cached by the Reflection infrastructure.If the expression would have to be made up entirely of constants anyway,
might the same "constant expressions" used in class const definitions be a
better fit than "any valid PHP expression" - plus a specific exception for
creating objects out of those constant expressions.Regards,
Rowan Collins
[IMSoP]--
--
Guilherme Blanco
Lead Architect at E-Block
It all sounds reasonable on #2 what everyone is heavily
forcing/suggesting because of DbC, but it only makes sense in the
context of a function call (or a bound object in case of OO members). In
any other case its outcome is unpredictable.
Yeah, you're right, access to an actual parameter value or runtime
instance requires logic somewhere else to provide the context, and to
run the check on every execution of a function requires some magic to
inject it into the call stack, or the function body, so my example
doesn't really make any sense.
Regards,
Rowan Collins
[IMSoP]
I don't really understand what closures have to do with annotations, or how that relates to capturing context.
The point is that, rather than trying to capture context, you can just
as well ask for what you need.
I'm going to use a very contrived example, because I really can't
think of any real case that requires context:
class User
{
<< new Validation(function ($value) { return false !==
filter_var($value, FILTER_VALIDATE_EMAIL); }) >>
public $email;
}
Like I said, extremely contrived - in practice, you wouldn't need to
attach functionality in that way.
An example from the other thread of a context-bound annotation would be implementing validation like this
You're not annotating the function - this is just a round-about way of
annotating the argument.
You would do that directly, like this:
function foo(
<< ValidateRange(0, 100) >>
int $percentage
) { ... }
If the expression would have to be made up entirely of constants anyway, might the same "constant expressions" used in class const definitions be a better fit than "any valid PHP expression" - plus a specific exception for creating objects out of those constant expressions.
Probably not - what happens with what is today "nested annotations"
then? Or will you make an exception for those too?
The problem is, you're just reinventing a subset of the programming
language, and I'm sure you can keep expanding on that indefinitely.
What for? Just use the facilities already defined by the language.
This fear of any feature that lets you do "evil" is incomprehensible
to me. Most features of almost any programming language can be used
for "evil". IMO, the real question is whether a feature accomplishes
what you want. If you insist on something that also prevents things
you don't want, you're bound to end up with something a lot more
complex that fits into the language a lot less naturally...
The other issue is the dependency (context) injection example - no one
seems to be able to cite an actual use-case, and if that's the case, I
should probably just remove it from the RFC entirely?@Larry can you think of a case example in the myriad annotations
you've seen in Drupal code? :-)Anyone else using Doctrine Annotations actually seen anyone making use
of a closure in an annotation?Possibly nobody's responded to your call for an example because they're not
sure what it is you're asking for an example of. I don't really understand
what closures have to do with annotations, or how that relates to capturing
context.An example from the other thread of a context-bound annotation would be
implementing validation like this:<<ValidateRange($percentage, 0, 100)>>
function foo(int $percentage)This would appear to be unsupported as such by your proposal, so you would
instead just have to use strings to represent how it needed to be accessed:<<new ValidateRangeAnnotation('$percentage', 0, 100)>>
and then have extra machinery in the reflection logic to extract the
parameters and pass them to the object, e.g.
$annotation->validate($reflected_parameters)This might be the more sensible approach to such an annotation anyway, but
without any access to variables, I don't really understand the value of
evaluating an expression for the annotation, just to get a constant value
that is cached by the Reflection infrastructure.If the expression would have to be made up entirely of constants anyway,
might the same "constant expressions" used in class const definitions be a
better fit than "any valid PHP expression" - plus a specific exception for
creating objects out of those constant expressions.Regards,
Rowan Collins
[IMSoP]
You're not annotating the function - this is just a round-about way of
annotating the argument.You would do that directly, like this:
function foo(
<< ValidateRange(0, 100) >>
int $percentage
) { ... }
What is this good for?
When is /ValidateRange/ being evaluated?
On every call? How?
I really do not get it, sorry.
--
Richard "Fleshgrinder" Fussenegger
I don't really understand what closures have to do with annotations, or how that relates to capturing context.
The point is that, rather than trying to capture context, you can just
as well ask for what you need.I'm going to use a very contrived example, because I really can't
think of any real case that requires context:class User
{
<< new Validation(function ($value) { return false !==
filter_var($value, FILTER_VALIDATE_EMAIL); }) >>
public $email;
}Like I said, extremely contrived - in practice, you wouldn't need to
attach functionality in that way.
Right, in my mind this is less about context, and more about passing
code into the implementation. I think we agree that an implementation
of validation-by-annotation would look something like:
$validators = $reflected_thing->getAnnotations('someFilter');
foreach ( $validators as $validator ) {
$validator->validate($some_instance);
}
This gets its context from the code running the validator; the
implementation of the validate() method would normally just be part of
the annotation type:
<<new RangeValidator(1, 100)>>
var $foo;
The closure example you gave would be a way of providing on-the-fly
dynamic behaviour as the argument to an annotation. This wouldn't
really make sense for validation (a custom validator would still
probably be defined once and used for several fields, but theoretically
you could have:
<<new CustomValidator(function($value){ return is_prime($value); })>>
It's not the context that's magic here, it's the ability to specify code
as part of the annotation value, rather than the annotation definition.
A more obvious example therefore is using annotations to implement DbC:
<<new Precondition(function($values) { ... })>>
Although as I admitted to Guilherme, that requires some magic to run it
at the right time anyway...
An example from the other thread of a context-bound annotation would be implementing validation like this
You're not annotating the function - this is just a round-about way of
annotating the argument.You would do that directly, like this:
function foo(
<< ValidateRange(0, 100) >>
int $percentage
) { ... }
Sure, but it's easy to come up with a variant that requires access to
more than one parameter, say:
<< ValidateLessThan($min, $max) >>
function foo($min, $max)
Which without any context or AST would need to be either:
<< ValidateLessThan('$min', '$max') >>
function foo($min, $max)
...which rather defeats the point of having code rather than just a string.
Or:
<< ValidateCustom(function($min, $max) { return $min < $max } >>
function foo($min, $max)
...which feels rather clunky.
If the expression would have to be made up entirely of constants anyway, might the same "constant expressions" used in class const definitions be a better fit than "any valid PHP expression" - plus a specific exception for creating objects out of those constant expressions.
Probably not - what happens with what is today "nested annotations"
then? Or will you make an exception for those too?
How would you "nest" annotations in your scheme anyway? As far as I can
see, they would be either multi-level arrays (a valid constant
expression) or nested constructor calls. So there's still exactly one
exception made, the "new Foo(args)" construct.
The problem is, you're just reinventing a subset of the programming
language, and I'm sure you can keep expanding on that indefinitely.
What for? Just use the facilities already defined by the language.
That's a fair point. The flipside is that the more things you allow the
user to do, the more edge cases you have to deal with when evaluating
the expressions in your special "zero context".
This fear of any feature that lets you do "evil" is incomprehensible
to me. Most features of almost any programming language can be used
for "evil".
I am not suggesting removing the ability to avoid people doing evil; I'm
suggesting that the feature you call "simple annotations" could be a lot
simpler.
What is the value of being able to write this:
function foo() { return 'MyAnnotation'; }
<< foo() >>
class A {}
When you could just write this:
<< 'MyAnnotation' >>
IMO, the real question is whether a feature accomplishes
what you want. If you insist on something that also prevents things
you don't want, you're bound to end up with something a lot more
complex that fits into the language a lot less naturally...
In my mind, something that lets me evaluate an arbitrary expression in
some weird null-context, at an unspecified time (because I don't know
what code will be the first to request that annotation), only to have
the result stored in some invisible static variable, is more complex
than many of the alternatives.
Regards,
Rowan Collins
[IMSoP]
I'd like to finish this RFC, but I have two remaining issues.
The syntax is a minor issue - since any valid PHP expression can be
used, the bit-shift ambiguity is technically an issue, however
marginal.A lot of people commented on the syntax when I posted the RFC on
reddit - they don't like it.Anyone have any ideas for an alternative syntax? It needs to be
delimited, e.g. needs to use opening and closing delimiters or a
recognizable opening delimiter... Here's some ideas:@{ new Table("user") }
@[ new Table("user") ]
{{ new Table("user") }}
+{ new Table("user") }
I don't like any of these really, but the bit-shift operator isn't
going when the stuff inside is an expression which could include
bit-shift (is it?)The other issue is the dependency (context) injection example - no one
seems to be able to cite an actual use-case, and if that's the case, I
should probably just remove it from the RFC entirely?@Larry can you think of a case example in the myriad annotations
you've seen in Drupal code? :-)
That's on my todo list for later this week. :-) I'm recently arrived in
Paris so jetlagged beyond coherent thought at the moment, but I will see
what I can do on a Drupal case study.
--Larry Garfield
I believe I have already stated my position on all of these concerns
in my last email.
Sorry if I cannot keep everything in my mind.
There's no such thing as optional dependencies - if you depend on a
Doctrine ORM annotation, you depend on that package.
Both statements are not true.
#1
A good example of an optional dependency can be found in the Symfony
console package:
https://github.com/symfony/console/blob/master/composer.json#L28
https://github.com/symfony/console/blob/master/Application.php#L814-L816
#2
I could have code in a third-party package that provides some default
annotations which suggests me to install some annotation systems that
are capable of handling exactly those annotations. However, I decide to
only use one of the suggested annotation systems in my project. With
your solution this is not possible because it always ends up in fatal
errors; although I consciously decided to not install one suggested
dependency. See #1 for such an example.
The only solution for the maintainer of the third-party package would be
to provide multiple versions of the same package:
- No annotations.
- Annotations from System X.
- Annotations from System Y.
- Annotations from System X and Y.
- ...
This is not an acceptable approach.
The code should (must) error the moment you try to instantiate those
annotations - whether you intend to use them or not is besides the
question - a missing class is an error, in annotations as it is
anywhere else in your code.
It is not (again see #1) a problem at compile time and always at
runtime. You can try this easily on your console:
$ php -r 'if (false) { new NonExistingClass; }'
$ php -r 'if (true) { new NonExistingClass; }'
Fatal error: Uncaught Error: Class 'NonExistingClass' not found in
Command line code:1
Stack trace:
#0 {main}
thrown in Command line code on line 1
Silently ignoring missing annotations solves nothing - it just leads
to errors that are even harder to find and debug.
My proposal errors as soon as you try to retrieve an annotation with the
annotation reader which was not previously registered as such. However,
you can use Reflection and introspect any existing annotation without
any kind of error.
That is how it should be!
You seem awfully concerned about name collisions?
No, I am not. It is just a nice side product of my proposal.
The one new thing you brought up here, is the issue of potentially
loading/instantiating a bunch of annotations that go unused. [...]
From a performance point of view [...]
The problem is parsing and interpreting them although they might not be
used because the current path of logic will not go through them at all.
This is not a micro optimization thing. You just don't want to interact
with annotations at all until they are actually needed. This is true for
any kind of PHP code.
See console commands above.
The extreme case of what you're proposing, is what Go does - an
annotation is simply a string. No chance of any error, anywhere, ever,
right? Wrong. People put JSON data in those strings for example, and
parsing that data leads to run-time errors instead. That's the extreme
example, but the problem with what you're proposing is precisely the
same: you allow something like nested array data structures, those are
still going to get interpreted and mapped to objects at run-time, and
it leads to run-time errors instead.
Runtime errors for a runtime feature, well, yes. Again, see console
command example above.
@someAnnotation('{"json": "data"}') function f();
@someAnnotation('invalid json') function x();
$reflector = new ReflectionFunction('f');
$annotations = $reflector->getAnnotations();
/*
$annotations = [
'someAnnotation' => [
['{"json": "data"}'],
],
];
*/
$reflector = new ReflectionFunction('x');
$annotations = $reflector->getAnnotations();
/*
$annotations = [
'someAnnotation' => [
['invalid json'],
],
];
*/
annotation_register('someAnnotation', function (string $json) {
$decoded_json = json_decode($json);
if ($decoded_json === false) {
throw new JsonParseException(json_last_error_msg());
}
return $decoded_json;
});
$annotations = new AnnotatedFunction('f');
$some_annotations = $annotations->getAnnotations('someAnnotations');
echo $some_annotations[0]->json;
// data
$annotations = new AnnotatedFunction('x');
$some_annotations = $annotations->getAnnotations('someAnnotations');
// Fatal error: Uncaught JsonParseException in ...
The above is just fine. The annotation library registered the callback
and it knows what kind of errors can happen. The annotation library is
also the one reading the annotations and it can therefore easily handle
those errors and act upon them. Of course in the above case the fatal
error is just fine and it is the developers duty to write unit tests and
take care of writing proper configuration. It is not PHP's duty to
ensure to absolutely no error can happen in any subsystem. (This is
actually ridiculous to think of in the first place.)
I maintain that the majority use-case is object annotations - and that
deferring the construction of those objects doesn't solve anything.
Seriously? Every construction is deferred, always, again see the console
example from above.
You're just deferring or hiding the problem, adding complexity, and
mental as well as practical debugging effort - but the problem doesn't
go away.
I am putting a runtime feature to runtime like all runtime features are
at runtime in PHP. The error is hidden during reflection, that is true,
but on purpose! You do not want reflection to error out while you are
performing introspection on a data structure.
The annotation reader however will error out in the moment you try to
access something. This separation of concern makes it actually easier
and is in the vain of good software development too.
I understand what you want to solve. I am just telling you that this is
the wrong approach and others are telling it to you too.
--
Richard "Fleshgrinder" Fussenegger
Dear Internals,
I'm announcing a simplified RFC for annotations:
-1 again, I am sorry.
The problem is that an annotation by definition is just meta-data. Your
proposal however might result in fatal errors from meta-data:
$reflector = new ReflectionClass(User::class);
$reflector->getAnnotations();
// Fatal error: Uncaught Error: Class 'TableName' not found ...
This is an absolute No-Go for meta-data.
--
Richard "Fleshgrinder" Fussenegger
Tonight I started trying to write a proposal for even more simplified
annotations, e.g. something simple enough that it cannot result in
fatal errors. I eventually gave up, and here's why.
Essentially, if you have to restrict yourself to things that cannot
under any circumstances error out, the only things you can use are
constant scalar values - even constant scalar expressions could not be
allowed, because even those could error when CONST_NAME or
Class::CONST_NAME aren't defined.
So, in a nutshell, what you're saying (Richard) is that annotations
can only consist of scalar values, and possibly arrays of values? (and
I don't even see your gist doing that - it's just invoking closures,
as far as I can tell, and any of those could error, or not?)
So, regardless of syntax, literally the only annotation data allowed
would be blatantly simple things like:
["max_length" => 20]
["data_type" => "int"]
Or in other words, constant arrays, int, string, float and bool
values. Nothing else.
Nothing can depend on any kind of external symbol (class names,
constants, etc.) since any such reference can cause an error.
No static analysis or type-checking of any kind can ever be performed
- no assertions or declarations can be made about the shape or types
of metadata, at all.
The problem is, that's simply not useful.
It's also not very different from what we have today with php-doc
blocks - the only real difference is we can check array nesting and
int, string, float and bool syntax, e.g. nothing really useful or
important.
In other words, this would be a very slight improvement on syntax -
nothing that actually amounts to any kind of functionality.
If we were to ship a feature like that, what would happen is precisely
the same thing that happened with php-doc blocks: the real work will
be done in userland (and likely dominated by one library), there will
be no IDE support (except proprietary support for that one dominating
library), no useful offline static analysis tools.
At the end of the day, what we end up with will have all of the same
issues we have with php-doc blocks and existing libraries.
I don't understand how such a feature would be useful to anybody?
I especially don't understand why you want a language feature that
isn't allowed to reference external symbols - e.g. isn't allowed to
have any kind of relationship with anything declared in userland.
It seems to me a peculiar, arbitrary and unrealistic restriction to
put on annotations.
It's going to have no immediate usefulness, and mapping the data to
objects is going to happen in userland, just as it does today.
I'd like to work on this proposal, but I feel that you're giving me
nothing to work with here.
Dear Internals,
I'm announcing a simplified RFC for annotations:
-1 again, I am sorry.
The problem is that an annotation by definition is just meta-data. Your
proposal however might result in fatal errors from meta-data:$reflector = new ReflectionClass(User::class);
$reflector->getAnnotations();// Fatal error: Uncaught Error: Class 'TableName' not found ...
This is an absolute No-Go for meta-data.
--
Richard "Fleshgrinder" Fussenegger
Hi Rasmus,
Tonight I started trying to write a proposal for even more simplified
annotations, e.g. something simple enough that it cannot result in
fatal errors. I eventually gave up, and here's why.Essentially, if you have to restrict yourself to things that cannot
under any circumstances error out, the only things you can use are
constant scalar values - even constant scalar expressions could not be
allowed, because even those could error when CONST_NAME or
Class::CONST_NAME aren't defined.So, in a nutshell, what you're saying (Richard) is that annotations
can only consist of scalar values, and possibly arrays of values? (and
I don't even see your gist doing that - it's just invoking closures,
as far as I can tell, and any of those could error, or not?)So, regardless of syntax, literally the only annotation data allowed
would be blatantly simple things like:["max_length" => 20] ["data_type" => "int"]
Or in other words, constant arrays, int, string, float and bool
values. Nothing else.Nothing can depend on any kind of external symbol (class names,
constants, etc.) since any such reference can cause an error.No static analysis or type-checking of any kind can ever be performed
- no assertions or declarations can be made about the shape or types
of metadata, at all.The problem is, that's simply not useful.
That's actually what doctrine/annotations has been for a while tho, and a
lot of people rely just on that.
The data-structure is still enforced by a VO-style class (the annotation
itself) in the library, though.
It would still be very useful, in my opinion.
Note that the existing annotation libs out there do indeed trigger
autoloading for referenced class constants.
Marco Pivetta
I completely agree with Marco. Throwing class-loading errors for
value-object classes is alright. For logic-parsing inside annotations, I
don't feel any need for it. Metadata should be static, IMO, but it could
very well be (and should be) value-objects.
At the end of the day, what we end up with will have all of the same
issues we have with php-doc blocks and existing libraries.I don't understand how such a feature would be useful to anybody?
As an addition, I don't think this feature should ADD new functionality to
those already present on DocBlocks. I just feel like docblocks are an ugly
hack for the lack of native annotations. Pasting from a previous discussion
in this same list, Rowan and I were discussing whether to add a native
docblock-annotation-retrieval feature or a native annotation syntax:
-
It might not be objectively bad to add this feature in docblocks, but it
will be objectively wrong to keep calling them "DocBlocks" if they are no
longer documentation-only blocks. -
Even though PHP already treats docblocks differently from comments,
that's not the common view on userland, nor is explained on the manuals.
There are no separate entries to explain docblocks, and no mention to them
on the "Comments" page. The Reflection method to retrieve DocBlocks is
"getDocCOMMENT", which suggests they are comment that do not affect runtime
behaviour.
We'd have to update the comments page to say "'/' starts a comment, unless
if they're immediately followed by another asterisk ('/*'), in which case
it may or may not be a comment, depends on the following token". It's very
confusing.
-
To make this work within docblocks, we'd have to get rid of at least one
configuration setting (opcode.save_comments). -
Dockblocks are stripped from obfuscators and transpilers, which are a
part of the ecosystem and do affect users, even if they are not first-class
citizens here.
Annotations in dockblocks are hard to understand because they they may or
may not be runtime-affecting annotations and there is no way to tell.
I hate this comparison with docblock annotations, because they are an UGLY
HACK. It works wonderfully, yes, but so do global variables, singletons and
what-not, and we are not using them anymore, are we? Adding this very same
feature that already exists in userland as a dedicated syntax would fix a
lot of issues, cited above. There is no need to evaluate logic inside
annotations, let them be value-objects, let it throw errors autoloading
said objects, and let's get this rolling.
Please, don't embargo this ONCE again. It's a very much needed feature, and
people want it! There have been innumerous discussions on reddit, and
almost everyone agrees annotations are a missing feature and
comment-annotations are an ugly hack for the lack of native syntax.
Please, make this happen.
2016-06-28 11:12 GMT-03:00 Marco Pivetta ocramius@gmail.com:
Hi Rasmus,
Tonight I started trying to write a proposal for even more simplified
annotations, e.g. something simple enough that it cannot result in
fatal errors. I eventually gave up, and here's why.Essentially, if you have to restrict yourself to things that cannot
under any circumstances error out, the only things you can use are
constant scalar values - even constant scalar expressions could not be
allowed, because even those could error when CONST_NAME or
Class::CONST_NAME aren't defined.So, in a nutshell, what you're saying (Richard) is that annotations
can only consist of scalar values, and possibly arrays of values? (and
I don't even see your gist doing that - it's just invoking closures,
as far as I can tell, and any of those could error, or not?)So, regardless of syntax, literally the only annotation data allowed
would be blatantly simple things like:["max_length" => 20] ["data_type" => "int"]
Or in other words, constant arrays, int, string, float and bool
values. Nothing else.Nothing can depend on any kind of external symbol (class names,
constants, etc.) since any such reference can cause an error.No static analysis or type-checking of any kind can ever be performed
- no assertions or declarations can be made about the shape or types
of metadata, at all.The problem is, that's simply not useful.
That's actually what doctrine/annotations has been for a while tho, and a
lot of people rely just on that.
The data-structure is still enforced by a VO-style class (the annotation
itself) in the library, though.It would still be very useful, in my opinion.
Note that the existing annotation libs out there do indeed trigger
autoloading for referenced class constants.Marco Pivetta
Note that I only make comparisons with doc-blocks to point out the
fact that, functionally, they would not be very different from an
annotation feature that solely permits you to annotate with values. A
doc-block is a string, which is a value - an
array/int/float/string/bool is a value. It's only very slightly better
than a bare string, in the sense that neither doc-blocks nor simple
values permit any kind of definition of which annotations are actually
available, hence offer no means of validation of that data (except at
run-time by a userland facility), provides no support for static
analysis, no IDE support, no clear indication of real dependencies,
and so on.
Since the discussion isn't completely dead yet (thank you Pedro and
Marco), I will just briefly explain what I had in mind for another
RFC.
This would be largely based on my previous proposal:
https://wiki.php.net/rfc/simple-annotations
But with the following differences.
The syntax would no longer allow arbitrary expressions - instead, it
would allow object annotations only, declared in one of three ways:
<< RequiresLogin >> // equivalent to new RequiresLogin()
<< Length(20) >> // equivalent to new Length(20)
<< HttpMethod::post() >> // equivalent to static method-call HttpMethod::post()
The latter permits you to declare and call static factory methods,
e.g. "named constructors" for some types of annotations.
Annotations are now consistently objects, which simplifies filtering
annotations, etc.
This declaration syntax enables better in-language analysis - because
the declarations always include the annotation class-name, this means
it can now support deferred as well as conditional creation, by having
a slightly different reflection API:
$annotations = $class_reflection->getAnnotations();
At this point, we have a list of ReflectionAnnotation objects - the
actual annotation objects have not been created at this point, which
would allow for conditional evaluation, such as:
foreach ($annotations as $annotation) {
echo $annotation->className; // => "Length" etc.
if ($annotation->classExists()) {
var_dump($annotation->getInstance()); // => Length object, etc.
} else {
var_dump($annotation->getInstance()); // => UnknownAnnotation object
}
}
The UnknownAnnotation object has three properties: class-name,
method-name ("__construct" if constructor was used) and the list of
arguments.
In other words, this would provide support for "optional
dependencies", in much the same way that unserialize()
does it - the
getInstance() method will trigger autoloading, and if the class is
unavailable, it takes this "typeless" information and puts it in a
general object, so that the information itself isn't lost.
It may seem like this solves the optional dependency problem, but
let me be clear, it does not fully address that issue, because
something like << Method(Http::GET) >> could still fail, if the Method
class is defined, but the Http class is not. That's a much more
marginal case, but it's not completely unlikely that you would use
constants from one dependency and pass them to annotation classes from
another dependency, e.g. if the HTTP method constants were part of an
HTTP package, and the annotation itself were part of a different
package. Arguably it's much less likely to occur though - if you have
the annotation package installed, most likely you have the dependent
package of that package installed as well.
So this is much safer, but just to be clear, the only way you can make
it any safer than that, is by completely disallowing anything but
values as arguments to annotation constructors - which, in my opinion,
simply isn't useful.
Alternatively to the above, you could imagine a simpler reflection
facility that does eagerly construct annotations, but constructs
UnknownAnnotation instances for missing classes - this would perhaps
balance more towards immediate usefulness and less towards performance
perfectionism, as it increases the chance of loading an unused
annotation.
This would be my preference though - as previously argued, the odds of
having a large number of unrelated annotations on the same member are
extremely low in practice; an annotated entity tends to be a persisted
property, a form input element, or an action method, etc... rarely do
you mix annotations from different domains onto the same member;
annotations that are applicable to a given domain are usually relevant
to a consumer belonging to the same (or a closely related) domain, so
this performance concern is mostly a theoretical micro optimization.
For another, eager construction means we can filter annotations with
instanceof rather than by class-name, which is much powerful, enabling
you to leverage inheritance - for example, you'd be able to ask
directly for all instances of ValidationAnnotation and get those with
a single call, which is incredibly useful.
Anyways, let me know your feelings about that idea? If there's any
interest, I'd be happy to write yet another RFC based on that.
I completely agree with Marco. Throwing class-loading errors for
value-object classes is alright. For logic-parsing inside annotations, I
don't feel any need for it. Metadata should be static, IMO, but it could
very well be (and should be) value-objects.At the end of the day, what we end up with will have all of the same
issues we have with php-doc blocks and existing libraries.I don't understand how such a feature would be useful to anybody?
As an addition, I don't think this feature should ADD new functionality to
those already present on DocBlocks. I just feel like docblocks are an ugly
hack for the lack of native annotations. Pasting from a previous discussion
in this same list, Rowan and I were discussing whether to add a native
docblock-annotation-retrieval feature or a native annotation syntax:
It might not be objectively bad to add this feature in docblocks, but it
will be objectively wrong to keep calling them "DocBlocks" if they are no
longer documentation-only blocks.Even though PHP already treats docblocks differently from comments,
that's not the common view on userland, nor is explained on the manuals.
There are no separate entries to explain docblocks, and no mention to them
on the "Comments" page. The Reflection method to retrieve DocBlocks is
"getDocCOMMENT", which suggests they are comment that do not affect runtime
behaviour.We'd have to update the comments page to say "'/' starts a comment, unless
if they're immediately followed by another asterisk ('/*'), in which case
it may or may not be a comment, depends on the following token". It's very
confusing.
To make this work within docblocks, we'd have to get rid of at least one
configuration setting (opcode.save_comments).Dockblocks are stripped from obfuscators and transpilers, which are a
part of the ecosystem and do affect users, even if they are not first-class
citizens here.Annotations in dockblocks are hard to understand because they they may or
may not be runtime-affecting annotations and there is no way to tell.I hate this comparison with docblock annotations, because they are an UGLY
HACK. It works wonderfully, yes, but so do global variables, singletons and
what-not, and we are not using them anymore, are we? Adding this very same
feature that already exists in userland as a dedicated syntax would fix a
lot of issues, cited above. There is no need to evaluate logic inside
annotations, let them be value-objects, let it throw errors autoloading
said objects, and let's get this rolling.Please, don't embargo this ONCE again. It's a very much needed feature, and
people want it! There have been innumerous discussions on reddit, and
almost everyone agrees annotations are a missing feature and
comment-annotations are an ugly hack for the lack of native syntax.Please, make this happen.
2016-06-28 11:12 GMT-03:00 Marco Pivetta ocramius@gmail.com:
Hi Rasmus,
Tonight I started trying to write a proposal for even more simplified
annotations, e.g. something simple enough that it cannot result in
fatal errors. I eventually gave up, and here's why.Essentially, if you have to restrict yourself to things that cannot
under any circumstances error out, the only things you can use are
constant scalar values - even constant scalar expressions could not be
allowed, because even those could error when CONST_NAME or
Class::CONST_NAME aren't defined.So, in a nutshell, what you're saying (Richard) is that annotations
can only consist of scalar values, and possibly arrays of values? (and
I don't even see your gist doing that - it's just invoking closures,
as far as I can tell, and any of those could error, or not?)So, regardless of syntax, literally the only annotation data allowed
would be blatantly simple things like:["max_length" => 20] ["data_type" => "int"]
Or in other words, constant arrays, int, string, float and bool
values. Nothing else.Nothing can depend on any kind of external symbol (class names,
constants, etc.) since any such reference can cause an error.No static analysis or type-checking of any kind can ever be performed
- no assertions or declarations can be made about the shape or types
of metadata, at all.The problem is, that's simply not useful.
That's actually what doctrine/annotations has been for a while tho, and a
lot of people rely just on that.
The data-structure is still enforced by a VO-style class (the annotation
itself) in the library, though.It would still be very useful, in my opinion.
Note that the existing annotation libs out there do indeed trigger
autoloading for referenced class constants.Marco Pivetta
Hi Rasmus,
I think you may be onto something here... :)
The UnknownAnnotation object has three properties: class-name,
method-name ("__construct" if constructor was used) and the list of
arguments.
I'm not sure we need an extra UnknownAnnotation class. It seems like all
of that information could be available on the ReflectionAnnotation
object, so you can get it without the class being available if you want to.
If the idea is to lazily-evaluate the arguments to the annotation, that
could be handled by storing the AST and only evaluating them when you
call either $annotation->getInstance() or $annotation->getArguments():
use MyAnnotations\FooAnnotation as Foo;
// Class alias resolved at compile time as normal
<< Foo(time()) >>
class A {}
$class_reflection = new ReflectionClass('A');
$annotations = $class_reflection->getAnnotations();
foreach ($annotations as $annotation) {
if ( $annotation->getClassName() == 'MyAnnotations\FooAnnotation' ) {
var_dump( $annotation->getArguments() ); // time()
evaluated here
var_dump( $annotation->getInstance() ); // new MyAnnotations\FooAnnotation(time())
evaluated here
}
}
It's not the responsibility of the ReflectionAnnotation class to worry
about calling the autoloader, issuing errors, etc, it should just
delegate that to the engine like any other constructor call. There's no
need for an extra $annotation->classExists() method, either, because you
can just say "class_exists($annotation->getClassName())".
Interestingly, this same delayed evaluation of arguments opens us up to
include direct access to the AST as some (Nikita?) have asked for:
// Constructor argument can be any valid expression,
// however nonsensical it would be to evaluate it directly
<< Pre($a >= 42) >>
function foo($a) {}
$r = new ReflectionFunction('foo');
$annotations = $r->getAnnotations();
foreach ( $annotations as $annotation ) {
if ( $annotation->getClassName() == 'Pre' ) {
$ast = $annotation->getArgumentAST();
// Now we can do funky things with the AST, like evaluating it
in a custom context :)
}
}
Regards,
Rowan Collins
[IMSoP]
Since the discussion isn't completely dead yet (thank you Pedro and
Marco), I will just briefly explain what I had in mind for another
RFC.
I want to see this feature happen too but the right way, so let us just
spend some more time wrapping our heads around it. We could also discuss
and work on the RFC at a different place (GitHub with issues?), since
many people are always complaining about too many emails that are sent
to this ML.
The syntax would no longer allow arbitrary expressions - instead, it
would allow object annotations only, declared in one of three ways:<< RequiresLogin >> // equivalent to new RequiresLogin()
<< Length(20) >> // equivalent to new Length(20)
<< HttpMethod::post() >> // equivalent to static method-call HttpMethod::post()
The latter permits you to declare and call static factory methods,
e.g. "named constructors" for some types of annotations.Annotations are now consistently objects, which simplifies filtering
annotations, etc.This declaration syntax enables better in-language analysis - because
the declarations always include the annotation class-name, this means
it can now support deferred as well as conditional creation, by having
a slightly different reflection API:$annotations = $class_reflection->getAnnotations();
At this point, we have a list of ReflectionAnnotation objects - the
actual annotation objects have not been created at this point, which
would allow for conditional evaluation, such as:foreach ($annotations as $annotation) { echo $annotation->className; // => "Length" etc. if ($annotation->classExists()) { var_dump($annotation->getInstance()); // => Length object, etc. } else { var_dump($annotation->getInstance()); // => UnknownAnnotation object } }
The UnknownAnnotation object has three properties: class-name,
method-name ("__construct" if constructor was used) and the list of
arguments.In other words, this would provide support for "optional
dependencies", in much the same way thatunserialize()
does it - the
getInstance() method will trigger autoloading, and if the class is
unavailable, it takes this "typeless" information and puts it in a
general object, so that the information itself isn't lost.
Awesome! This is exactly the kind of thing that one wants from
reflection, information and not exceptions or worse. :)
It may seem like this solves the optional dependency problem, but
let me be clear, it does not fully address that issue, because
something like << Method(Http::GET) >> could still fail, if the Method
class is defined, but the Http class is not. That's a much more
marginal case, but it's not completely unlikely that you would use
constants from one dependency and pass them to annotation classes from
another dependency, e.g. if the HTTP method constants were part of an
HTTP package, and the annotation itself were part of a different
package. Arguably it's much less likely to occur though - if you have
the annotation package installed, most likely you have the dependent
package of that package installed as well.So this is much safer, but just to be clear, the only way you can make
it any safer than that, is by completely disallowing anything but
values as arguments to annotation constructors - which, in my opinion,
simply isn't useful.
I would argue that this should fail if the Method class from the example
exists and is actually being created. However, if the Method class does
not exist (and we construct an UnknownAnnotation stub for it) nothing
should go wrong. I mean, the assumption that the dependency of a missing
dependency is/might be missing sounds logical too me.
Of course we would need to have something like an
UnknownAnnotationArgument stub too.
Note that this allows you in consequence to create other kinds of
objects within annotations:
<< Annotation(new Argument) >> final class {}
This would allow nesting as some DocBlock libraries allow it currently.
Alternatively to the above, you could imagine a simpler reflection
facility that does eagerly construct annotations, but constructs
UnknownAnnotation instances for missing classes - this would perhaps
balance more towards immediate usefulness and less towards performance
perfectionism, as it increases the chance of loading an unused
annotation.This would be my preference though - as previously argued, the odds of
having a large number of unrelated annotations on the same member are
extremely low in practice; an annotated entity tends to be a persisted
property, a form input element, or an action method, etc... rarely do
you mix annotations from different domains onto the same member;
annotations that are applicable to a given domain are usually relevant
to a consumer belonging to the same (or a closely related) domain, so
this performance concern is mostly a theoretical micro optimization.For another, eager construction means we can filter annotations with
instanceof rather than by class-name, which is much powerful, enabling
you to leverage inheritance - for example, you'd be able to ask
directly for all instances of ValidationAnnotation and get those with
a single call, which is incredibly useful.
Isn't it possible to go for a generator (yield) like approach and
combine the best of both worlds?
That being said, I am in favor of this approach as well, because, as you
said, instanceof is more powerful and one expects all annotations to be
present, the UnknownAnnotation would be a special case.
Anyways, let me know your feelings about that idea? If there's any
interest, I'd be happy to write yet another RFC based on that.
+1 with one thing, go for the @ and not <<>>. It is possible with the
parser and so far most people stated that they prefer it, or put it to
vote with a separate poll.
@Route('/blog/{id}', new Method(Http::GET))
@ParamConverter('post', Post::class)
public function indexAction(Post $post) {}
--
Richard "Fleshgrinder" Fussenegger
For another, eager construction means we can filter annotations with
instanceof rather than by class-name, which is much powerful, enabling
you to leverage inheritance - for example, you'd be able to ask
directly for all instances of ValidationAnnotation and get those with
a single call, which is incredibly useful.
Sorry for the extra mail with only one point, but I missed this before.
This can be achieved using the fully-qualified class-name by using
is_a()
or is_subclass_of()
. This obviously needs to trigger the
autoloader anyway, but can still avoid actually instantiating the class.
(And thus, per my other mail, evaluating the args.)
I think lazy construction (without magic stub classes) gives the best of
both worlds here.
Thanks, Richard and Rowan - this gives me more to work with!
I will work on a new proposal and ask for more input when I have a
draft ready :-)
For another, eager construction means we can filter annotations with
instanceof rather than by class-name, which is much powerful, enabling
you to leverage inheritance - for example, you'd be able to ask
directly for all instances of ValidationAnnotation and get those with
a single call, which is incredibly useful.Sorry for the extra mail with only one point, but I missed this before. This
can be achieved using the fully-qualified class-name by usingis_a()
or
is_subclass_of()
. This obviously needs to trigger the autoloader anyway, but
can still avoid actually instantiating the class. (And thus, per my other
mail, evaluating the args.)I think lazy construction (without magic stub classes) gives the best of
both worlds here.
<< HttpMethod::post() >> // equivalent to static method-call
HttpMethod::post()
What would this, specifically, do? Would it call HttpMethod::post() when
the class gets instantiated and return its return on a
ReflectionClass::getAnnotations()? Why is it necessary to be able to
evaluate expressions on annotations? I do agree that throwing
errors/exceptions when classes are not found is needed (otherwise we wont
be able to use value-objects), but evaluating expressions seems a little
crazy for me... why should metadata be computed in runtime?
<< HttpMethod::post() >> // equivalent to static method-call
HttpMethod::post()What would this, specifically, do? Would it call HttpMethod::post()
when
the class gets instantiated and return its return on a
ReflectionClass::getAnnotations()? Why is it necessary to be able to
evaluate expressions on annotations?
I'm not sure if it's definitely necessary, but the stated use case was for factories / named constructors. PHP only has one constructor per class, so it's not uncommon to have static createFromX methods.
Since this will eventually evaluate as "new Foo(42)":
<< Foo(42) >>
The idea is that this would be available to call a factory instead:
<< Foo::fromRoman('XLII') >>
The neat thing being that by limiting the options to this, you can still extract a class name for every annotation.
I say "eventually", because as proposed, this won't actually happen until you explicitly ask for the "instance" of the annotation. See previous examples.
Regards,
--
Rowan Collins
[IMSoP]
This was just an example of a value object - for example:
class HttpMethod {
private $method;
protected function __construct($method) {
$this->method = $method;
}
public function getMethod() {
return $this->method;
}
public static function get() {
return new self("GET");
}
public static function post() {
return new self("POST");
}
// ...
}
You could picture using a flyweight constructor inside those factory
methods maybe, and probably other patterns... I think there's plenty
of cases for "named constructors", this is just the PHP variety of
that. There are also cases (such as this one) where only certain
constructions are permitted - allowing any string for HTTP method
(e.g. wrong names, wrong case etc.) is prevented by protecting the
constructor...
<< HttpMethod::post() >> // equivalent to static method-call
HttpMethod::post()What would this, specifically, do? Would it call HttpMethod::post() when the
class gets instantiated and return its return on a
ReflectionClass::getAnnotations()? Why is it necessary to be able to
evaluate expressions on annotations? I do agree that throwing
errors/exceptions when classes are not found is needed (otherwise we wont be
able to use value-objects), but evaluating expressions seems a little crazy
for me... why should metadata be computed in runtime?
This was just an example of a value object - for example:
class HttpMethod {
private $method;protected function __construct($method) { $this->method = $method; } public function getMethod() { return $this->method; } public static function get() { return new self("GET"); } public static function post() { return new self("POST"); } // ...
}
You could picture using a flyweight constructor inside those factory
methods maybe, and probably other patterns... I think there's plenty
of cases for "named constructors", this is just the PHP variety of
that. There are also cases (such as this one) where only certain
constructions are permitted - allowing any string for HTTP method
(e.g. wrong names, wrong case etc.) is prevented by protecting the
constructor...
A possibly uglier but useful case would be where you have a large number
of properties in an annotation and want to pass them in by name, but not
using an anonymous array. (See also, my earlier Drupal examples.)
To wit:
<Node::definition()>
class Node {
public static function definition() {
$def = new NodeDefinition();
$def->name = "node";
$def->label = "A Node";
$def->addLink('foo', 'bar');
// ...
return $def;
}
// The rest of the node class here.
}
$annotations = $class_reflection->getAnnotations();
print get_class($annotations[0]); // prints "NodeDefinition".
Or would that fail because it's not returning a self instance, but of
another class?
Rasmus, let me know when you have a proposal together and I'll try to
dig up time to try rendering Drupal annotations in it again. :-)
--Larry Garfield