FAQ
Hi!

That you for bringing real usage examples. This is an interesting point
to consider.

Reading through it, however, I can't help but wonder why these things
are not just data? I mean, why they are kept in the comments? If there
are reasons like performance, there could be solutions developed, and
"keeping things together" doesn't exactly work when the things are
2-screen-long data array and I presume equally complex class - they are
two separate and independent entities anyway, and their synchronization
has to be explicitly managed anyway, nobody can ensure they are in sync
just by looking at it - in fact, given its size, nobody can *anything*
just by looking at it. It's not really human-readable anymore than
database dump is human-readable - the data is there, true, but it's not
exactly how you'd prefer to interact with it.

That said, of course I imagine there are reasons why Drupal developers
made these choices, and I don't think it makes much sense to argue about
them now. I just outlined my first impression, but if it sounds wrong
and uninformed, just ignore it :) It does, however, makes sense arguing
about whether it is something we have to support directly in the
language, and to which lengths we should go to do so.
defining a given extension. Saying "well just abandon your approach
entirely" is not a satisfying answer.
I would not say "abandon your approach" - what works for you works for
you, so trying to convince you otherwise would be wasting your time. I
would say that if somebody is developing a new system and are
considering this approach - I *would* advise checking a different
direction.

That is to say, I would be perfectly happy if PHP had annotations that
do not support such use case, at least not directly. I know that sounds
somewhat insensitive to Drupal developers, and that's not my intent do
dismiss their concerns, but I think we should not take it as a primary
requirement, sine qua non.

My opinion is good design should figure out the best use cases for the
feature and serve them as much as possible, without trying to overload
the system to serve every need and every use case there could be. Doing
80% of cases well is much better than doing 99.999% cases poorly. My
opinion is hundred-element data dumps are well outside of the 80% for
annotations. If whatever design we end up with supports it - great, if
it doesn't - that's fine too.
* @Block(
* id = "system_branding_block",
* admin_label = @Translation("Site branding")
* )
Why not do just:

@Block([ "id" => "system_branding_block", "admin_label" =>
Translation("Site branding")])

Or, in current RFC syntax:

<<Block([ "id" => "system_branding_block", "admin_label" =>
Translation("Site branding")])>>

Now, we get to a touchy issue of how arrays can be consumed by
annotations clients and what "Translation" is. Here we have two news, as
it should be, good and bad:

- Good news is that AST makes kind of easy to have Translation have any
semantic the client wants
- Bad news is that currently RFC provides no tools for actually
operating on deeper data structures, AFAIK. If such tools are provided,
then I don't see why this won't work.

Also, of course, namespacing is a must, but that was already discussed
and I think being taken care of.
Yoinks. Now let's try and turn that into attributes. Here's my first
Here it's more complicated, because some data items clearly should be
different annotations, like "handlers" - these are probably something like:
<<StorageHandler(Drupal\node\NodeStorage)>>
<<FormHandler(Drupal\node\NodeForm)>>

etc. and some others may be data. Hard to know without understanding
true semantics of the thing.
OK, let's take that to its logical conclusion and directly map in the
full annotation:
<<ContentEntity([
That could work too, but see above about deep data, and also it looks
bad semantically - it's like having a class with one method five miles
long which accepts 50 parameters depending on what needs to be done. Not
enough structure. Again, this is saying without knowing semantics, so
maybe it's all wrong.

In any case, I think if we have: a) namespacing and b) tools to convert
AST to some useable data at least supporting arrays and maybe some kind
of namespace resolution - then I think it still can be supported, with
some effort of course but I would expect moving data of this complexity
to a different system to take some effort in any case.
Additionally, even then it's still "just an array", by which I mean an
anonymous struct, by which I mean "you get no help at all on typos."
You may have noticed that I typoed "route_provder" instead of
"route_provider" above. An an array key, that is a silent runtime error
that's crazy hard to debug. Forcing any meaningful data structure into
anonymous structs is doing everyone a disservice. (As a Drupal
developer, I can attest to that fact with personal scars.)
True. If we had named parameters, we could probably avoid that, but we
don't have them. You could make each parameter separate annotation, but
that might look insane. Or maybe not, maybe just unusual.

You could also have a tool that just reads annotation and validates
them. Just like static analysis tools for PHP.

Without really knowing your semantics I don't see how else the system
would know route_provder is not a correct parameter. The only thing that
really can validate such things right now in PHP is interface, and if we
really stretch it, maybe a class - but I'm not sure how that would
really fit. And if we go that route, then we probably couldn't have the
freedom AST allows us.
1) ::class constants should resolve at compile time, relative to use
statements, anywhere in the annotation.
That makes sense, but then it's unclear why should that be unique for
::class? Then all constants should work this way I think.
2) Namespacing for attribute names. This is necessary for both
collision-avoidance and to minimize typing of obscenely long attribute
names. The easiest way to do that would be:
That makes sense too. I think Dmitry said he plans to add that.
3) Some way to provide a data definition for the annotation that can be
checked at compile time. This could be classes, a la Doctrine. It could
be a new Annotation type as others have suggested. It could be
I am somewhat reluctant to define a new conceptual thing just for
annotations. Class seems natural at the first glance but implications of
it are unclear - what does it mean to instantiate such class? How
annotation data is now related to the object of this class?

Another option could be to (ab)use interfaces in a following fashion: if
we define

<<__Attribute>>
interface BlockPlugin() {
  function id();
  function admin_label();
}

Then when we say:

<<BlockPlugin(["id" => "system_branding_block", "admin_label" =>
Translation("Site branding") ])>>

we get an object that implements this interface and id() and
admin_label() returns something relevant. There are problems with this
approach:

1. Using array as parameter is ugly. Named arguments is what we need but
d'oh. Also enshrining array-as-parameters in syntax which is worse.

2. It is not clear what exactly admin_label() should return and how
Translation() should work - is it AST? Is it some kind of object? Should
we just give up on Translation and do it in some other way like this?

<<__Attribute>>
interface BlockPlugin() {
  function id();
  <<Translatable>>
  function admin_label();
}

Note that this is a way to enable nesting of attributes without getting
the data part too complex. But of course then all admin_labels must be
translatable or not-translatable, not half-way.

So the idea is half-baked but maybe it can be used, think about it.
--
Stas Malyshev
smalyshev@gmail.com

Search Discussions

  • Larry Garfield at Apr 30, 2016 at 9:56 pm

    On 04/30/2016 04:12 PM, Stanislav Malyshev wrote:
    Hi!

    That you for bringing real usage examples. This is an interesting point
    to consider.

    Reading through it, however, I can't help but wonder why these things
    are not just data? I mean, why they are kept in the comments? If there
    are reasons like performance, there could be solutions developed, and
    "keeping things together" doesn't exactly work when the things are
    2-screen-long data array and I presume equally complex class - they are
    two separate and independent entities anyway, and their synchronization
    has to be explicitly managed anyway, nobody can ensure they are in sync
    just by looking at it - in fact, given its size, nobody can *anything*
    just by looking at it. It's not really human-readable anymore than
    database dump is human-readable - the data is there, true, but it's not
    exactly how you'd prefer to interact with it.

    That said, of course I imagine there are reasons why Drupal developers
    made these choices, and I don't think it makes much sense to argue about
    them now. I just outlined my first impression, but if it sounds wrong
    and uninformed, just ignore it :) It does, however, makes sense arguing
    about whether it is something we have to support directly in the
    language, and to which lengths we should go to do so.
    At the risk of turning this into a Drupal tutorial (I will try not to),
    let me try to explain in brief:

    In previous Drupal versions, such data would be provided in an "info
    hook". That is, a magically named function that returns a deeply nested
    array of data that registers "stuff" with the system. That "stuff" was
    pretty much completely and utterly undefined, and as it was all
    anonymous structs (aka associative arrays) extremely hard to document.
    There's also often a corresponding "alter hook" (we still have those),
    which gets passed the full set of such data as a massive nested array by
    reference to manipulate. In addition, it was common to have the
    definition hook in one file and any code it referred to in another. In
    a few cases it was a class, but in other cases it was a template file,
    or a series of other magically named functions that could technically
    live anywhere. All kinds messy. :-)

    Moving that registration metadata into the same file as the thing it's
    describing was done for DX reasons: If you want to add a new plugin to
    Drupal (and there are several dozen kinds of plugin available in Drupal
    8, each with a corresponding interface), you have one single class file
    to edit, usually a base class to extend, and you're done. It makes
    adding new plugins and registering them really easy. Where the plugin
    then gets used is up to user configuration "elsewhere".

    Entities are implemented as just very complex plugins, sort of.
    (Disclaimer: I disagreed with this decision for a variety of reasons,
    but lost that battle.)

    In the complex entity case I showed, the registration information includes:

    1) Base definition information common to any plugin (unique ID,
    human-friendly label, etc.)

    2) database information (tables, keys, etc.). Honestly I think a next
    version of this system shouldn't allow for user control in that area and
    they should be fully automated.

    3) Various handlers. Many of these are essentially services that are
    coupled to a specific entity type. Trying to register them all via the
    container, though, would again spread out the work for a particular
    developer across a lot more places in a lot more contexts. A few are
    form definitions that get instantiated as ordinary forms in a certain
    context.

    4) Hooks into various other automation tools. In the ideal case,
    defining an entity class and its annotation, if you include the
    appropriate data, will result in about a dozen routes getting created
    with stock, auto-generated UIs and permission controls already baked
    in. It will integrate with the Views system (Drupal's content assembly
    / GUI query builder), access control, database-level revisioning,
    translation, serialization, and so forth. Basically, all of the cool
    user-facing automation that is why one uses Drupal is "just there", and
    because the UI is by default auto-generated it's very consistent for end
    users.

    Again, all of that could be done elsewhere (just like Doctrine entity
    annotations could be done via YAML or PHP arrays, too), but having it
    all together in one place instead of 15 that need manual synchronization
    of service names and such is a big DX win.

    Also, I mentioned we still have alter hooks to allow other modules to
    manipulate that definition, but they now get a keyed array of
    ContentEntityType objects rather than anonymous arrays. This is a good
    thing.

    (I guess I failed at not making this a Drupal tutorial.)

    Most of that data would not make sense on the object itself, because
    it's used in a completely different context. We do not have a node, or
    user, or comment object on hand when we need that data.

    Amusingly, we don't even use reflection to get that data. Because
    Drupal over-does everything, there are enough annotated classes that
    parsing all into memory at once just to reflect on the the docblock
    which we'd then have to parse was deemed memory-prohibitive. Instead, we
    submitted a patch upstream to Doctrine to let us parse out the comment
    from the file stream, which could then be discarded to save that
    memory. That decision was made long before we adopted PHP 5.5 as a
    minimum version, though, so I don't know if it would still be relevant
    today. Either way, for core PHP to only make it available via
    reflection is a reasonable decision, IMO, and I am not asking for native
    support otherwise.
    That is to say, I would be perfectly happy if PHP had annotations that
    do not support such use case, at least not directly. I know that sounds
    somewhat insensitive to Drupal developers, and that's not my intent do
    dismiss their concerns, but I think we should not take it as a primary
    requirement, sine qua non.

    My opinion is good design should figure out the best use cases for the
    feature and serve them as much as possible, without trying to overload
    the system to serve every need and every use case there could be. Doing
    80% of cases well is much better than doing 99.999% cases poorly. My
    opinion is hundred-element data dumps are well outside of the 80% for
    annotations. If whatever design we end up with supports it - great, if
    it doesn't - that's fine too.
    As a general concept I agree, although then the question becomes which
    80% to target. :-) I do not expect PHP to cater to Drupal's current
    annotation usage specifically; I would be satisfied if it provided a
    syntax that allowed us to represent the same or similar concepts without
    total hackery, even if we had to restructure the data to do so. (As I
    said, some of it I'd prefer wasn't even there.) That basically boils
    down to the need to support more than "dumb key value with an AST
    loophole" as a native syntax.
    * @Block(
    * id = "system_branding_block",
    * admin_label = @Translation("Site branding")
    * )
    Why not do just:

    @Block([ "id" => "system_branding_block", "admin_label" =>
    Translation("Site branding")])

    Or, in current RFC syntax:

    <<Block([ "id" => "system_branding_block", "admin_label" =>
    Translation("Site branding")])>>
    The first is essentially what we're doing, in Doctrine syntax. In
    Doctrine, those properties map to properties on the "Block" object that
    gets created. Wrapping them in an array in Doctrine wouldn't really buy
    us anything.
    Here it's more complicated, because some data items clearly should be
    different annotations, like "handlers" - these are probably something like:
    <<StorageHandler(Drupal\node\NodeStorage)>>
    <<FormHandler(Drupal\node\NodeForm)>>
    Possibly, if those were well-namespaced keys. Although clustering them
    all up into a full set of handlers is something we would need to do
    anyway, either here or in an aggregate object we stick those into.
    Without really knowing your semantics I don't see how else the system
    would know route_provder is not a correct parameter. The only thing
    that really can validate such things right now in PHP is interface,
    and if we really stretch it, maybe a class - but I'm not sure how that
    would really fit. And if we go that route, then we probably couldn't
    have the freedom AST allows us.
    Essentially what I'm asking for is a way for us to tell PHP what those
    semantics are. Vis, if we define an annotation that is expected to have
    keys A and B, and a developer puts in keys A and C, then a static
    analyzer (including my IDE) can spot that and say "yo, what's this C
    thing?". It's the same concept as mistyping the name of a property on
    an object that's been defined. $this->route_provder would make my IDE
    upset because there's no such property defined, but there is
    $this->route_provider.

    1) ::class constants should resolve at compile time, relative to use
    statements, anywhere in the annotation.
    That makes sense, but then it's unclear why should that be unique for
    ::class? Then all constants should work this way I think.
    Concur. (Although I've become less and less supportive of constants in
    general, as they are just another global. That's off topic for now,
    though.)
    2) Namespacing for attribute names. This is necessary for both
    collision-avoidance and to minimize typing of obscenely long attribute
    names. The easiest way to do that would be:
    That makes sense too. I think Dmitry said he plans to add that.
    Including "use" statement sensitivity? That would be necessary to avoid
    the crazy-long names. If so, +1.
    3) Some way to provide a data definition for the annotation that can be
    checked at compile time. This could be classes, a la Doctrine. It could
    be a new Annotation type as others have suggested. It could be
    I am somewhat reluctant to define a new conceptual thing just for
    annotations. Class seems natural at the first glance but implications of
    it are unclear - what does it mean to instantiate such class? How
    annotation data is now related to the object of this class?
    My initial thought would be something akin to what Doctrine does:

    class MyAnnotation {
        protected $foo;
        protected $bar;
    }

    <<MyAnnotation(foo = 'a', bar = 'b')>>
    class Me{}

    $def = $r->getAttribute(Me::class);

    $def instanceof MyAnnotation; //TRUE
    $def->foo == 'a'; // TRUE
    $def->bar == 'b'; // TRUE

    MyAnnotation can then have whatever other methods I feel like to
    access/mutate those values. If that requires limiting MyAnnotation in
    some way (no constructor, the properties have to be public, etc.), I'm
    open to that.

    I suppose this would, technically, allow a baz key to be defined and
    added to the object dynamically, just like any other object property;
    and an IDE can soft-complain like it does for an undefined property, but
    the runtime still accepts it.
    Another option could be to (ab)use interfaces in a following fashion: if
    we define

    <<__Attribute>>
    interface BlockPlugin() {
    function id();
    function admin_label();
    }

    Then when we say:

    <<BlockPlugin(["id" => "system_branding_block", "admin_label" =>
    Translation("Site branding") ])>>

    we get an object that implements this interface and id() and
    admin_label() returns something relevant. There are problems with this
    approach:

    1. Using array as parameter is ugly. Named arguments is what we need but
    d'oh. Also enshrining array-as-parameters in syntax which is worse.

    2. It is not clear what exactly admin_label() should return and how
    Translation() should work - is it AST? Is it some kind of object? Should
    we just give up on Translation and do it in some other way like this?

    <<__Attribute>>
    interface BlockPlugin() {
    function id();
    <<Translatable>>
    function admin_label();
    }

    Note that this is a way to enable nesting of attributes without getting
    the data part too complex. But of course then all admin_labels must be
    translatable or not-translatable, not half-way.

    So the idea is half-baked but maybe it can be used, think about it.
    That's essentially the same concept as I described above with the
    class. Putting annotations on the object being created seems like a
    reasonable way to handle nested annotations at first glance, although it
    still doesn't resolve the main issue of Translatable which is that we
    scan the source code statically to find translatable strings. (We look
    for t('some string'), @Translation('some string'), and various other
    known patterns.) This is definitely the part I understand how to solve
    the least.

    --Larry Garfield
  • Stanislav Malyshev at Apr 30, 2016 at 10:14 pm
    Hi!
    <<MyAnnotation(foo = 'a', bar = 'b')>>
    Here you have essentially created a named parameter syntax. That may be
    a problem later, especially if we do implement named parameters and this
    won't be the syntax we choose.
    $def instanceof MyAnnotation; //TRUE
    That looks fine, however the problem is that if MyAnnotation is a class,
    then PHP does not have multiple inheritance, so it's the only class it
    can be. And given that your class has no methods, $def has no methods
    either and can not have any semantics besides simple data object. Which
    begs the questions: a) why it needs to be a class/object at all and b)
    how would you support all those fancy AST things if you can't even call
    methods on it. Of course, this is solvable by various way, but looks a
    bit clunky conceptually. You expect classes to work in a certain way and
    attributes to work in a slightly different way, at least with current
    proposal.
    MyAnnotation can then have whatever other methods I feel like to
    access/mutate those values. If that requires limiting MyAnnotation in
    Mutable annotation is a very bad idea. That's BTW another problem of
    defining it as class - we don't have real means to express immutable
    value classes yet, not without some boilerplate. That's why interface is
    better - it allows you to do immutability cleanly.
    That's essentially the same concept as I described above with the
    class. Putting annotations on the object being created seems like a
    Very similar, expect for:
    1. Interface allows you to have richer semantics since you can implement
    other interfaces and have other classes. Class pretty much limits you to
    value object unless you do dirty tricks which essentially turn it into
    interface. trait may be a bit in-between solution, but I didn't really
    think about it yet, so there may be problems there.
    2. You have no mutability issues.
    3. You don't confuse template with actual data - i.e., with class, if I
    just instantiate it, would it be a valid attribute? Of what?
    4. You still can make complex attributes return ASTs.
    reasonable way to handle nested annotations at first glance, although it
    still doesn't resolve the main issue of Translatable which is that we
    scan the source code statically to find translatable strings. (We look
    That can be solved by making translatability a property of an attribute,
    not a string. That looks to me a better design anyway - if you have a
    button, what would it mean if its label sometimes translatable and
    sometimes not? I'd expect translatability to be requirement of a context
    - i.e. if we use string to display a button, it's translatable. Note
    that it doesn't mean it *has* translations for every language - you
    can't ensure that anyway - but it has the *option*. And if you make it a
    property of the attribute itself, then your tool can have the list of
    translatable attributes and then extract the values for these attributes
    for translation.

    That also makes more sense to me for translation - to translate
    properly, you need to know the context of the string, same text can
    translate differently depending on the context, so you'd want to know
    what value you're translating.

    --
    Stas Malyshev
    smalyshev@gmail.com
  • Rowan Collins at Apr 30, 2016 at 10:30 pm

    On 30/04/2016 23:14, Stanislav Malyshev wrote:
    $def instanceof MyAnnotation; //TRUE
    That looks fine, however the problem is that if MyAnnotation is a class,
    then PHP does not have multiple inheritance, so it's the only class it
    can be. And given that your class has no methods, $def has no methods
    either and can not have any semantics besides simple data object.
    What would prevent the class from having methods?

    class MyAnnotation
    {
    public $foo;
    public $bar;
    public function doStuff() { ... }
    }

    I don't see any more need for multiple inheritance here than in any
    other class definition.

    --
    Rowan Collins
    [IMSoP]
  • Stanislav Malyshev at Apr 30, 2016 at 10:45 pm
    Hi!
    What would prevent the class from having methods?

    class MyAnnotation
    {
    public $foo;
    public $bar;
    public function doStuff() { ... }
    }
    Oh, of course you can have methods, but then it is strange conceptually
    - you have a normal class, which some other part of the language just
    uses for something else that classes are not routinely used for. I.e.,
    does it call a constructor? When? With which arguments? What if it
    fails? What if I just create an object of this class - would it be the
    same as annotation object? How the "multiple annotations" syntax in RFC
    would work - what <<test(1,2)>> means - one object with two parameters
    or two objects with one parameter? What <<test($a + $b > 0)>> actually
    gets?

    Maybe that should work but all these should be then defined.
    I don't see any more need for multiple inheritance here than in any
    other class definition.
    There kind of is if we want annotations to have additional capabilities
    as annotations - e.g. the AST things.
    --
    Stas Malyshev
    smalyshev@gmail.com
  • Rowan Collins at Apr 30, 2016 at 11:22 pm

    On 30/04/2016 23:45, Stanislav Malyshev wrote:
    Oh, of course you can have methods, but then it is strange conceptually
    - you have a normal class, which some other part of the language just
    uses for something else that classes are not routinely used for. I.e.,
    does it call a constructor? When? With which arguments? What if it
    fails? What if I just create an object of this class - would it be the
    same as annotation object?
    Hm... I was going to say "well, PDO does this if you use
    PDOStatement::fetchObject"; but then I remembered that the integration
    with the object there IS a bit weird - it injects raw properties, and
    *then* calls the constructor.

    So, I'm not sure there's a limitation in terms of the object being
    data-only per se, but there are certainly oddities to be dealt with in
    terms of construction. And as you mentioned, mutability leads to another
    set of oddities - are the mutations stored for next time you request
    that annotation, or is the object recreated on each access?

    Regards,

    --
    Rowan Collins
    [IMSoP]
  • Larry Garfield at May 1, 2016 at 7:47 pm

    On 04/30/2016 06:21 PM, Rowan Collins wrote:
    On 30/04/2016 23:45, Stanislav Malyshev wrote:
    Oh, of course you can have methods, but then it is strange conceptually
    - you have a normal class, which some other part of the language just
    uses for something else that classes are not routinely used for. I.e.,
    does it call a constructor? When? With which arguments? What if it
    fails? What if I just create an object of this class - would it be the
    same as annotation object?
    Hm... I was going to say "well, PDO does this if you use
    PDOStatement::fetchObject"; but then I remembered that the integration
    with the object there IS a bit weird - it injects raw properties, and
    *then* calls the constructor.

    So, I'm not sure there's a limitation in terms of the object being
    data-only per se, but there are certainly oddities to be dealt with in
    terms of construction. And as you mentioned, mutability leads to
    another set of oddities - are the mutations stored for next time you
    request that annotation, or is the object recreated on each access?

    Regards,
    It would never occur to me to not have it regenerated on each access.
    If I want to cache it I will do so myself, thanks. :-)

    However, that is not an issue created by using a defined structure for
    the annotation result. The RFC currently says it returns an associative
    array, aka anonymous struct. Those are always highly mutable. A
    classed object is as mutable as its design allows it to be. To wit:

    <<__Annotation>>
    class Definition {
        protected $foo;
        protected $bar;
        public function getFoo() {}
        public function getBar() {}
    }

    <<Definition(foo => 1, bar => 2)>>
    class Meep {}

    The resulting annotation object would be an instance of Definition,
    which is for practical purposes immutable. If it were returned as an
    array ['foo' => 1, 'bar' => 2], that would obviously be mutable.

    Whether Definition should have mutator methods on it then becomes the
    implementer's decision, which is probably for the best.

    --Larry Garfield
  • Stanislav Malyshev at May 1, 2016 at 8:20 pm
    Hi!
    It would never occur to me to not have it regenerated on each access.
    If I want to cache it I will do so myself, thanks.

    Not sure why would you care. These should be value objects, so they
    should keep no state and as such it shouldn't matter when they are
    generated and how many of them.
    However, that is not an issue created by using a defined structure for
    the annotation result. The RFC currently says it returns an associative
    array, aka anonymous struct. Those are always highly mutable. A
    classed object is as mutable as its design allows it to be. To wit:
    As far as I can see, the RFC says it would return an array of RFC nodes.
    Now, it is true that array itself is mutable, we don't have immutable
    containers really, but that's not what I meant. The AST Node should not
    be really mutable.
    <<__Annotation>>
    class Definition {
    protected $foo;
    protected $bar;
    public function getFoo() {}
    public function getBar() {}
    }
    Except for definitions of $foo and $bar - for which I don't see much use
    - this is exactly what I proposed, only as a class and not interface.
    I've already explained why interface is better :)
    <<Definition(foo => 1, bar => 2)>>
    Again, here we have named arguments problem. Note that you're using
    diffrent syntax for named arguments than last time :)
    --
    Stas Malyshev
    smalyshev@gmail.com
  • Dmitry Stogov at May 5, 2016 at 6:47 am

    On 05/01/2016 10:47 PM, Larry Garfield wrote:
    On 04/30/2016 06:21 PM, Rowan Collins wrote:
    On 30/04/2016 23:45, Stanislav Malyshev wrote:
    Oh, of course you can have methods, but then it is strange conceptually
    - you have a normal class, which some other part of the language just
    uses for something else that classes are not routinely used for. I.e.,
    does it call a constructor? When? With which arguments? What if it
    fails? What if I just create an object of this class - would it be the
    same as annotation object?
    Hm... I was going to say "well, PDO does this if you use
    PDOStatement::fetchObject"; but then I remembered that the
    integration with the object there IS a bit weird - it injects raw
    properties, and *then* calls the constructor.

    So, I'm not sure there's a limitation in terms of the object being
    data-only per se, but there are certainly oddities to be dealt with
    in terms of construction. And as you mentioned, mutability leads to
    another set of oddities - are the mutations stored for next time you
    request that annotation, or is the object recreated on each access?

    Regards,
    It would never occur to me to not have it regenerated on each access.
    If I want to cache it I will do so myself, thanks. :-)

    However, that is not an issue created by using a defined structure for
    the annotation result. The RFC currently says it returns an
    associative array, aka anonymous struct. Those are always highly mutable.
    The RFC proposes only Reflection*::getAttributres() that returns by value.
    You may modify the returned copy, but the original attributes are immutable.

    Thanks. Dmitry.
    A classed object is as mutable as its design allows it to be. To wit:

    <<__Annotation>>
    class Definition {
    protected $foo;
    protected $bar;
    public function getFoo() {}
    public function getBar() {}
    }

    <<Definition(foo => 1, bar => 2)>>
    class Meep {}

    The resulting annotation object would be an instance of Definition,
    which is for practical purposes immutable. If it were returned as an
    array ['foo' => 1, 'bar' => 2], that would obviously be mutable.

    Whether Definition should have mutator methods on it then becomes the
    implementer's decision, which is probably for the best.

    --Larry Garfield
  • Marco Pivetta at May 2, 2016 at 6:12 am
    Hey Stas,
    On 1 May 2016 at 00:14, Stanislav Malyshev wrote:

    $def instanceof MyAnnotation; //TRUE
    That looks fine, however the problem is that if MyAnnotation is a class,
    then PHP does not have multiple inheritance, so it's the only class it
    can be. And given that your class has no methods, $def has no methods
    either and can not have any semantics besides simple data object. Which
    begs the questions:


    Note that an annotation usage here is basically a constructor call for a
    concrete instance.
    Still, this doesn't deny implementing interfaces for annotations, then
    implementing those on the concrete annotation class.
    I don't see the need for multiple inheritance therefore.

    a) why it needs to be a class/object at all


    Usually as a replacement for what you'd have as a "struct" in C.
    Having a class definition provides some basic security on what is going on
    (type-hints, etc).
    You probably wouldn't ever have behavior on an annotation though, since it
    is just a data structure: in fact, most of the existing annotations in PHP
    userland libs are currently just classes with constructor+public properties.

    Cheers,

    Marco Pivetta

    http://twitter.com/Ocramius

    http://ocramius.github.com/
  • Jesse Schalken at May 4, 2016 at 12:20 pm

    On Sat, Apr 30, 2016 at 9:47 AM, Larry Garfield wrote:

    3) Some way to provide a data definition for the annotation that can be
    checked at compile time. This could be classes, a la Doctrine. It could be
    a new Annotation type as others have suggested. It could be something
    else, akin to a struct like Liz Smith keeps toying with. I'm flexible
    here. But some way to provide a data definition for the annotation that is
    more robust than "you get an AST for an associative array that you can eval
    to an array yourself, good luck" is, I believe, mandatory for these to be
    successful for more than the most trivial use cases. If that data
    definition is a class or class-like type, that also resolves the namespace
    question very easily.
    I would love it if annotations were just classes with public properties,
    and the annotation shorthand for instantiating and setting properties was
    generally avaliable. At the moment if you wish to instantiate an object and
    set some public properties, you have to use a temporary variable

    $album = new Album();

    $album->name = "Albumius";
    $album->artist = "Artistus";

    $album->year = 2013;

    return $album;


    or define setters for all the properties so you can chain them

    return (new Album())

         ->setName("Albumius")

         ->setArtist("Artistus")

         ->setYear(2013);


    or forego the benefits of classes and use an array

    return [
         'name' => "Albumius",
         'artist' => "Artistus",
         'year' => 2013,
    ];


    C# has an "instantiate-and-set-properties" shorthand like this, which would
    be great to have in PHP:

    return new Album() {
         name = "Albumius",
         artist = "Artistus",
         year = 2013,
    };


    and if you drop the "new" (and constructor parens are already optional)
    you've got a pretty good annotation syntax right there.

    <<Links {
         canonical = "/node/{node}",
         deleteForm" = "/node/{node}/delete",
         editForm" = "/node/{node}/edit",
         versionHistory" = "/node/{node}/revisions",
         revision" = "/node/{node}/revisions/{node_revision}/view",
    }>>

    <<Foo("param", "param")>>

    <<Blah(5) { foo = 5 }>>


    So the annotation syntax could just be class instantiation without the
    "new".

    (maybe there should be a $ before the property names, not sure)
  • Rowan Collins at May 4, 2016 at 12:40 pm

    Jesse Schalken wrote on 04/05/2016 13:20:
    (maybe there should be a $ before the property names, not sure)
    And there's the rub! :P

    When named parameters have been discussed before, there was a lot of
    bikeshedding over what the syntax should look like, and this is arguably
    a very similar feature.

    You could either think of this as "setting lots of variables":

    new Foo { $bar = 1, $baz = 2 }

    or you could think of it as "an object literal like an array literal":

    new Foo { 'bar' => 1, 'baz' => 2 }


    And then we also need to think about sitting nicely with anonymous class
    syntax. Not to mention Joe's proposal for lexical scope:
    https://wiki.php.net/rfc/lexical-anon


    For the record, I like the idea, if we can come up with a consistent
    plan for how these pieces of syntax will work together, and not paint
    ourselves into an ASCII-art hole...

    Regards,
    --
    Rowan Collins
    [IMSoP]
  • Jesse Schalken at May 4, 2016 at 2:31 pm

    On 4 May 2016 10:41 pm, "Rowan Collins" wrote:

    You could either think of this as "setting lots of variables":

    new Foo { $bar = 1, $baz = 2 }

    or you could think of it as "an object literal like an array literal":

    new Foo { 'bar' => 1, 'baz' => 2 }
    I think a $ is only necessary to disambiguate, ie between variable and
    constant. It isn't necessary as a prefix for properties when it is
    unambiguous that the thing is a property. Eg property access is ->foo, not
    ->$foo.

    I don't think the string literal syntax is appropriate for
    classes/structs/records which have a defined, static structure. You would
    use that when you're talking about a hash table/associative array/map/dict,
    for which the key is often an arbitrary expression.

    So I would go with plain property name without prefix. It certainly looks
    nicer in the context of annotations.
    And then we also need to think about sitting nicely with anonymous class
    syntax. Not to mention Joe's proposal for lexical scope:
    https://wiki.php.net/rfc/lexical-anon
    >

    AFAIK anonymous classes always start with "new class ..", so there would be
    no ambiguity. It would be an optional {...} part that follows a class
    instantiation, anonymous or not.
    For the record, I like the idea, if we can come up with a consistent plan
    for how these pieces of syntax will work together, and not paint ourselves
    into an ASCII-art hole...
    >

    It sounds like this conversation has been had before (but I'm not sure
    about instantiate-and-set-properties specifically), but nonetheless the
    problem remains and it's a common pain point for me and fellow devs.
    Annotations sound like the ideal time to address it since they also need to
    instantiate classes and set public properties in one expression.
  • Dmitry Stogov at May 5, 2016 at 6:35 am

    On 04/30/2016 02:47 AM, Larry Garfield wrote:
    Most of the examples that have been given so far are either trivial
    boolean flags or data validation rules to be evaled. In practice,
    very little of Drupal's use of annotations in Drupal 8 fit either
    category. Rather, they're used primarily as, in essence, a serialized
    metadata object describing a class, which is used for registering that
    class and potentially others. I figured I'd give the proposed syntax
    a try with some Drupal examples and see how well it fit.

    Disclaimer: I'm sure someone will pipe up with "your use case is
    invalid because you shouldn't be using annotations that way." I will
    be the first to agree that Drupal loves to take everything it does to
    an extreme, and some things may be better done other ways. However,
    these are still real-world use cases (currently built with Doctrine
    Annotations) that people are going to want to try and reimplement
    eventually using a core language feature. This much data is put in
    one place primarily for DX reasons, to give developers a one-stop-shop
    for defining a given extension. Saying "well just abandon your
    approach entirely" is not a satisfying answer.

    Summary: It doesn't fit well at all, and there's features missing that
    would prevent Drupal from being able to use the proposed attributes
    RFC as-is, even without talking about classes-as-annotations. A
    series of improvement request/suggestions are listed at the end of
    this email.

    Simple example:

    Drupal plugins (usually) use annotations. Here's a simple example:

    /**
    * Provides a block to display 'Site branding' elements.
    *
    * @Block(
    * id = "system_branding_block",
    * admin_label = @Translation("Site branding")
    * )
    */
    class SystemBrandingBlock {

    }

    This defines a "block" (type of plugin). It's unique machine name
    identifier is "system_branding_block", and its human-facing label is
    "Site branding", which is marked as a translatable string. That all
    seems reasonable to include here.

    Here's what I came up with for a possible attributes version:

    <<PluginType('Block')>>
    <<PluginId('system_branding_block')>>
    <<PluginAdminLabel('Site branding')>>
    class SystemBrandingBlock {

    }

    Not too bad at first blush, but there's 2 problems.
    It's also possible to write:

    [
            "id" = "system_branding_block",
            "admin_label" = @Translation("Site branding")
    ]))>>

    Then you'll need you own layer that translates "Drupal" attributes from
    AST to everything you like.
    1) There's no indication that the label is a translatable string. One
    could hard-code that logic into whatever processing happens for
    PluginAdminLabel, but then there's no indication for our gettext
    scanner that "Site branding" is translatable and should be extracted
    for translation.

    2) If we want to say that the value "Block" corresponds to a class
    (something that would be up to the parser to do), there's no
    indication of the namespace against which to resolve "Block". The
    alternative would be to require including the full class name string,
    like so:

    <<PluginType('Drupal\Core\Block\Annotation\Block')>>

    But that DX is quite terrible. We introduced ::class in 5.5 for a
    reason. Better would be:

    <<PluginType(Block::class)>>

    But that works only if the attribute parser resolves Block::class
    against the currently "use"d namespaces so that it's a full class name
    string when reflection picks it up. If not, then that means the
    user-space parser needs to catch that, then go back to the file and
    figure out the available use statements and resolve against those.
    It's doable, but ugly and certainly more work than I'd want to put in
    as someone writing such a parser.

    I don't know if that's a feature of the patch at the moment, but it
    would need to be.

    So even in a simple case we have insufficient functionality.

    Complex example:

    OK, let's go to the other end and look at an example that is way more
    complicated. (Yes, maybe too complicated.) Doctrine annotations are
    also used to define Entity Types, which correspond to a class. Here's
    the annotation for a Node, in all its glory:

    /**
    * Defines the node entity class.
    *
    * @ContentEntityType(
    * id = "node",
    * label = @Translation("Content"),
    * bundle_label = @Translation("Content type"),
    * handlers = {
    * "storage" = "Drupal\node\NodeStorage",
    * "storage_schema" = "Drupal\node\NodeStorageSchema",
    * "view_builder" = "Drupal\node\NodeViewBuilder",
    * "access" = "Drupal\node\NodeAccessControlHandler",
    * "views_data" = "Drupal\node\NodeViewsData",
    * "form" = {
    * "default" = "Drupal\node\NodeForm",
    * "delete" = "Drupal\node\Form\NodeDeleteForm",
    * "edit" = "Drupal\node\NodeForm"
    * },
    * "route_provider" = {
    * "html" = "Drupal\node\Entity\NodeRouteProvider",
    * },
    * "list_builder" = "Drupal\node\NodeListBuilder",
    * "translation" = "Drupal\node\NodeTranslationHandler"
    * },
    * base_table = "node",
    * data_table = "node_field_data",
    * revision_table = "node_revision",
    * revision_data_table = "node_field_revision",
    * translatable = TRUE,
    * list_cache_contexts = { "user.node_grants:view" },
    * entity_keys = {
    * "id" = "nid",
    * "revision" = "vid",
    * "bundle" = "type",
    * "label" = "title",
    * "langcode" = "langcode",
    * "uuid" = "uuid",
    * "status" = "status",
    * "uid" = "uid",
    * },
    * bundle_entity_type = "node_type",
    * field_ui_base_route = "entity.node_type.edit_form",
    * common_reference_target = TRUE,
    * permission_granularity = "bundle",
    * links = {
    * "canonical" = "/node/{node}",
    * "delete-form" = "/node/{node}/delete",
    * "edit-form" = "/node/{node}/edit",
    * "version-history" = "/node/{node}/revisions",
    * "revision" = "/node/{node}/revisions/{node_revision}/view",
    * }
    * )
    */
    class Node {

    }

    Yoinks. Now let's try and turn that into attributes. Here's my first
    attempt:

    <<PluginType('ContentEntity')>>
    <<PluginId('node')>>
    <<ContentEntity\Label('Content')>>
    <<ContentEntity\BundleLabel('Content type')>>
    <<ContentEntity\Handler\Storage(NodeStorage::class)>>
    <<ContentEntity\Handler\StorageSchema(NodeStorageSchema::class)>>
    <<ContentEntity\Handler\ViewBuilder(NodeViewBuilder::class)>>
    <<ContentEntity\Handler\Access(NodeAccessControlHandler::class)>>
    <<ContentEntity\Handler\ViewsData(NodeViewsData::class)>>
    <<ContentEntity\Handler\Form\Default(NodeForm::class)>>
    <<ContentEntity\Handler\Form\Delete(NodeDeleteForm::class)>>
    <<ContentEntity\Handler\Form\Edit(NodeForm::class)>>
    <<ContentEntity\RouteProvider\Html(NodeRouteProvider::class)>>
    // ...
    class Node {

    }
    you don't need to split your annotation into many attributes. You should
    just adopt its syntax to become a valid PHP expression.
    This expression is not going to be evaluated. It's going to be just
    parsed into AST. and then you may traverse this AST and transform it
    into other data structures.
    The key idea of RFC was not to invite another language for meta-data,
    but use PHP language itself.

    I gave up at that point, as I'd already identified several problems.

    1) The attribute names themselves are horribly long, because they're
    not namespaced at all. That means I have to namespace them myself in
    the annotation, which leads to grotesquely long tokens.

    2) Again, I need to list the name of a class in the annotation. That
    means either a stupid-long-string (which therefore means I can't catch
    stupid typos with static analysis) or resolving ::class constants. I
    went with the latter here because the alternative is basically unusable.

    3) Nested data is unsupported, except through manual namespacing.

    4) Hashes are totally unsupported.
    <<ContentEntity\Handler\Form\Default(NodeForm::class)>> is a terrible
    idea, as even with my own parser I now cannot tell which part of that
    string is supposed to be a class name to map the data to and which isn't.

    5) Because the attribute names are just strings, I am repeating very
    long values with no static analysis support at all. One typo in that
    string, even though it's not quoted like a string, means stuff will
    just not work and I don't know how to catch it other than visual
    inspection (aka, "hey developer, guess!").

    OK, let's try using the AST format. That gets a little bit better:

    <<PluginType('ContentEntity')>>
    <<PluginId('node')>>
    <<ContentEntity\Label('Content')>>
    <<ContentEntity\BundleLabel('Content type')>>
    <<ContentEntity\Handler\Storage(NodeStorage::class)>>
    <<ContentEntity\Handler\StorageSchema(NodeStorageSchema::class)>>
    <<ContentEntity\Handler\ViewBuilder(NodeViewBuilder::class)>>
    <<ContentEntity\Handler\Access(NodeAccessControlHandler::class)>>
    <<ContentEntity\Handler\ViewsData(NodeViewsData::class)>>
    <<ContentEntity\Handler\Form(['default' => NodeForm::class,
    'delete' => NodeDeleteForm::class
    'edit' => NodeForm::class]>>
    <<ContentEntity\RouteProvider(['html' => NodeRouteProvider::class]>>
    class Node {

    }

    At least now I can define the hash in a parsable fashion. However,
    it's not clear to me if I can catch errors there with static
    analysis. Note, for instance, that I omitted the comma on the
    'delete' line. That would be a parse error in normal code. Would it
    be a parse error on compile? I would hope so, since it would be a
    parse error when I tried to reflect the attributes. It doesn't
    address the other issues, though.
    yes. it's going to be a parse error at compile time.
    The patch was provided and it's better to try it.
    OK, let's take that to its logical conclusion and directly map in the
    full annotation:

    <<ContentEntity([
    'id' => 'node',
    'label' => 'Content',
    'bundle_label' => 'Content type',
    'handlers' => [
    'storage' => NodeStorage::class,
    'storage_schema' => NodeStorageSchema::class,
    'view_builder' => NodeViewBuilder::class,
    'access' => NodeAccessControllerHandler::class,
    'views_data' => NodeViewsData::class,
    'form' => [
    'default' => NodeForm::class,
    'delete' => NodeDeleteForm::class,
    'edit' => NodeForm::class,
    ],
    'route_provder' => [
    'html' => NodeRouteProvider::class,
    ],
    ],
    // ...
    ])>>
    <<ContentEntity\Translatable>>
    <<Links([
    "canonical" => "/node/{node}",
    "delete-form" => "/node/{node}/delete",
    "edit-form" => "/node/{node}/edit",
    "version-history" => "/node/{node}/revisions",
    "revision" => "/node/{node}/revisions/{node_revision}/view",
    ])>>
    class Node {

    }

    I broke it up a little bit, but that may or may not make sense in
    practice.

    What do we get with this approach? Well, we can model the full data
    structure. That's good. If ContentEntity is supposed to map to a
    class in my system it would be slightly less fugly to use a full class
    name there now, as there's only maybe 3 attributes instead of 30.
    Still, we're really only getting any benefit out of it if:

    1) The ::class constant acutally works as I've shown here.

    2) The parser understands that I'm trying to define an array structure
    and can catch syntax typos for me.

    Otherwise, there's really no benefit over sticking the string in the
    docblock as we do now.

    Additionally, even then it's still "just an array", by which I mean an
    anonymous struct, by which I mean "you get no help at all on typos."
    You may have noticed that I typoed "route_provder" instead of
    "route_provider" above. An an array key, that is a silent runtime
    error that's crazy hard to debug. Forcing any meaningful data
    structure into anonymous structs is doing everyone a disservice. (As
    a Drupal developer, I can attest to that fact with personal scars.)

    Recommended changes:

    1) ::class constants should resolve at compile time, relative to use
    statements, anywhere in the annotation.
    we don't have fully constructed classes at compile time. Classes may be
    used during transformation from plain arrays and AST into application
    specific data structures.
    2) Namespacing for attribute names. This is necessary for both
    collision-avoidance and to minimize typing of obscenely long attribute
    names. The easiest way to do that would be:

    3) Some way to provide a data definition for the annotation that can
    be checked at compile time. This could be classes, a la Doctrine. It
    could be a new Annotation type as others have suggested. It could be
    something else, akin to a struct like Liz Smith keeps toying with.
    I'm flexible here. But some way to provide a data definition for the
    annotation that is more robust than "you get an AST for an associative
    array that you can eval to an array yourself, good luck" is, I
    believe, mandatory for these to be successful for more than the most
    trivial use cases. If that data definition is a class or class-like
    type, that also resolves the namespace question very easily.

    Note: We do NOT need objects created at compile time or include time,
    or even instantiation of the annotated class time. Only at
    reflection-lookup time. All that's needed before that is syntax
    validation (both compiler and IDE, once IDEs catch on). If that can
    go as far as key validation at compile time, great. If that can only
    happen at reflection-lookup time, that's acceptable as long as it
    happens.
    This is the way patch works. It performs only syntax validation at
    compile time (no key validation). And at reflection time you may
    validate and translate base structures into whatever you like.
    4) I don't know if there's a reasonable way to "nest" annotations. I
    do not have a solution for the translatable string marker issue, but
    it is a feature of Doctrine that Drupal leverages heavily and we would
    be loathe to lose. (It may even be a deal breaker.)
    @ is used as silence operator, but it's still a valid PHP expression syntax

    ("..."))>>
    I hope this case study has been useful. To reiterate, I am in favor
    of PHP adding annotation/attribute/whatever-you-call-it support; I
    just want to make sure it is robust enough to support the use cases
    that I know exist in the wild.
    yeah. thanks for spending your time. I hope my answers would help you
    adopting attributes idea for your needs.

    Thanks. Dmitry.
    --Larry Garfield
  • Stanislav Malyshev at May 5, 2016 at 6:48 am
    Hi!
    It's also possible to write:

    [
    "id" = "system_branding_block",
    "admin_label" = @Translation("Site branding")
    ]))>>


    you don't need to split your annotation into many attributes. You should
    just adopt its syntax to become a valid PHP expression.
    This expression is not going to be evaluated. It's going to be just
    parsed into AST. and then you may traverse this AST and transform it
    into other data structures.
    The key idea of RFC was not to invite another language for meta-data,
    but use PHP language itself.
    This is a good way to avoid handling a lot of issue, but what I am
    afraid of is that with this solution, what would happen that people
    start doing exactly that - inventing another languages for metadata. In
    fact, that's exactly what the expression above does - it uses "=" as
    named argument, and uses @ as special tag, not like PHP does. So it's in
    fact mini-language using PHP's AST parser to tokenize its grammar, but
    having separate semantics.

    Maybe that's what we want to have here - freedom for everybody to invent
    their own languages - but I fear the danger of fragmentation here and
    also people implementing tons of slightly different incompatible parsers
    for ASTs that are generated. We'd have Drupal attributes and Symphony
    attributes and Doctrine attributes and Zend attributes and so on, and
    each of them would have different semantics. Not sure this would be
    good. But maybe it avoids arguing about the syntax now.
    we don't have fully constructed classes at compile time. Classes may be
    used during transformation from plain arrays and AST into application
    specific data structures.
    We don't have classes but we do namespace resolution right? For
    namespace resolution, you don't need to have the class actually present.
    I don't think we need it for ::class either.

    --
    Stas Malyshev
    smalyshev@gmail.com
  • Dmitry Stogov at May 5, 2016 at 7:07 am

    On 05/05/2016 09:48 AM, Stanislav Malyshev wrote:
    Hi!
    It's also possible to write:

    [
    "id" = "system_branding_block",
    "admin_label" = @Translation("Site branding")
    ]))>>


    you don't need to split your annotation into many attributes. You should
    just adopt its syntax to become a valid PHP expression.
    This expression is not going to be evaluated. It's going to be just
    parsed into AST. and then you may traverse this AST and transform it
    into other data structures.
    The key idea of RFC was not to invite another language for meta-data,
    but use PHP language itself.
    This is a good way to avoid handling a lot of issue, but what I am
    afraid of is that with this solution, what would happen that people
    start doing exactly that - inventing another languages for metadata. In
    fact, that's exactly what the expression above does - it uses "=" as
    named argument,
    ops, "=" actually should be replaced with "=>", or this won't work.
    and uses @ as special tag, not like PHP does. So it's in
    fact mini-language using PHP's AST parser to tokenize its grammar, but
    having separate semantics.
    right. RFC doesn't propose any semantic, but higher layer may define
    completely different semantic.
    Maybe that's what we want to have here - freedom for everybody to invent
    their own languages - but I fear the danger of fragmentation here and
    also people implementing tons of slightly different incompatible parsers
    for ASTs that are generated. We'd have Drupal attributes and Symphony
    attributes and Doctrine attributes and Zend attributes and so on, and
    each of them would have different semantics. Not sure this would be
    good. But maybe it avoids arguing about the syntax now.
    Today, we have the same with doc-comments.
    Attributes eliminate the need for separate parser and perform syntax
    validation at compile time.
    They also provide flexible syntax to support all existing annotation
    systems, but they can't solve semantic problems, because they are just
    meta-data.

    Thanks. Dmitry.
    we don't have fully constructed classes at compile time. Classes may be
    used during transformation from plain arrays and AST into application
    specific data structures.
    We don't have classes but we do namespace resolution right? For
    namespace resolution, you don't need to have the class actually present.
    I don't think we need it for ::class either.
  • Larry Garfield at May 5, 2016 at 2:24 pm

    On 05/05/2016 02:07 AM, Dmitry Stogov wrote:
    Maybe that's what we want to have here - freedom for everybody to invent
    their own languages - but I fear the danger of fragmentation here and
    also people implementing tons of slightly different incompatible parsers
    for ASTs that are generated. We'd have Drupal attributes and Symphony
    attributes and Doctrine attributes and Zend attributes and so on, and
    each of them would have different semantics. Not sure this would be
    good. But maybe it avoids arguing about the syntax now.
    Today, we have the same with doc-comments.
    Attributes eliminate the need for separate parser and perform syntax
    validation at compile time.
    They also provide flexible syntax to support all existing annotation
    systems, but they can't solve semantic problems, because they are just
    meta-data.
    This, I think, is the key point of disagreement.

    The proposal does not, actually, provide enough functionality to be
    useful. It's a first step, but it doesn't go far enough to actually
    address the problem space. Because while it may provide rudimentary
    syntax validation (basically, is it a legal PHP string) it doesn't
    provide any semantic validation (it is a meaningful PHP string if
    interpreted the right way), because it doesn't define "right way".

    As Rowan noted, there are lots of technically-legal PHP strings that an
    AST would be totally fine with that are still completely different and
    incompatible as far as actually using them. To enhance his examples a bit:

    <<Push($foo >> Translator)>>

    <<Pipe($foo|lower|escape)>>

    <<Query($foo ?? '//item[id=42]')>>

    I could easily see, for instance, Doctrine annotations building the
    first, PHPUnit the second, and Zend the 3rd. Those would all be
    legal-ish, but semantically very different. And there's also then no
    guarantee that $foo >> Translator actually means a bit-shift (I don't
    even know what a bitshift in that case would mean), it could mean
    anything that Doctrine decided to mutate it into. Does the second
    example actually mean to pipe values, or could it also be parsed into
    something else? Are lower and escape function names, or magic values
    that my add-on parser knows?

    At that point, the only value-add over the status quo (hack the
    docblock) is a common lexer. But since the semantics are not guaranteed
    on top of that, it's really not that useful.

    I'm not fully convinced that all the way to Doctrine classes is the
    right alternative. It may be, it may not be, I'm not sure yet. But as
    someone who would be using this system in user-space, I am very
    convinced that the current proposal simply doesn't go far enough to be
    useful to me.

    --Larry Garfield
  • Rowan Collins at May 5, 2016 at 3:00 pm

    Larry Garfield wrote on 05/05/2016 15:24:
    <<Push($foo >> Translator)>>

    <<Pipe($foo|lower|escape)>>

    <<Query($foo ?? '//item[id=42]')>>

    I could easily see, for instance, Doctrine annotations building the
    first, PHPUnit the second, and Zend the 3rd. Those would all be
    legal-ish, but semantically very different. And there's also then no
    guarantee that $foo >> Translator actually means a bit-shift (I don't
    even know what a bitshift in that case would mean)

    To add some context to these examples: the first is borrowing the
    overload of ">>" from C++ (meaning "pass to stream") or similar uses in
    Ruby; the second the use of "|" as a pipe in templating languages like
    Smarty and Twig (meaning "pass expression to modifier function") or good
    old Unix shell tradition; and the third is inspired by PostgreSQL and
    imagines the operator overloaded to mean something like "matches the
    XPath-like expression given".

    I'm not sure any of those interpretations would actually be useful in
    the realm of annotations, but operator overloading is the first thing
    that sprung to mind when I wondered what a domain-specific language
    would look like if it were constrained only to producing a valid PHP
    AST. Another trick could be to abuse brackets, e.g.
    "something(anything)" will parse as a function call, but needn't be
    interpreted as one.

    Regards,
    --
    Rowan Collins
    [IMSoP]
  • Dan Ackroyd at May 5, 2016 at 10:36 pm

    On 5 May 2016 at 15:24, Larry Garfield wrote:
    because it doesn't define "right way". Good.
    I could easily see, for instance, Doctrine annotations building the
    first, PHPUnit the second, and Zend the 3rd.
    Good!

    It's not the job of PHP core to tell people how to use annotations.
    People can use them however they want.

    If it turns out that there is a single 'right' way of using them,
    everyone will gravitate to that way anyway.

    If it turns out there are different 'right' ways of using them for
    different use cases, people will be able to pick and choose the
    use-case that is most appropriate.

    And most importantly, if what people think is the 'right' way to use
    them evolves over time, that can be accomplished completely in
    user-land, without needing to update the internal implementation of
    annotations.

    cheers
    Dan
  • Jesse Schalken at May 6, 2016 at 2:06 am
    If you're going to say "do what you want" with regards to annotations, then
    just let them be a text string. Parsing the annotation as PHP but not
    evaluating it as PHP seems a very strange and arbitrary half-way point. If
    the thing consuming the AST is expected to eval() it, then why didn't PHP
    do that already? If the thing consuming the AST is expected not to eval()
    it, then it must effectively implement it's own language sharing PHP's
    syntax but not PHP's semantics. Since it can't possibly attach meaning to
    all of PHP's syntax, PHP will enforce that the string is valid PHP even
    though the annotation language will be a very small subset. Not only does
    that buy you very little in terms of validity checking, but it constrains
    the annotation language to be a subset of PHP's syntax even when such a
    constraint may be entirely inappropriate.

    A true "do what you want" approach, if that is the right approach, would be
    for the annotation body to be a free text string.
    On Fri, May 6, 2016 at 8:36 AM, Dan Ackroyd wrote:
    On 5 May 2016 at 15:24, Larry Garfield wrote:
    because it doesn't define "right way". Good.
    I could easily see, for instance, Doctrine annotations building the
    first, PHPUnit the second, and Zend the 3rd.
    Good!

    It's not the job of PHP core to tell people how to use annotations.
    People can use them however they want.

    If it turns out that there is a single 'right' way of using them,
    everyone will gravitate to that way anyway.

    If it turns out there are different 'right' ways of using them for
    different use cases, people will be able to pick and choose the
    use-case that is most appropriate.

    And most importantly, if what people think is the 'right' way to use
    them evolves over time, that can be accomplished completely in
    user-land, without needing to update the internal implementation of
    annotations.

    cheers
    Dan

    --
    PHP Internals - PHP Runtime Development Mailing List
    To unsubscribe, visit: http://www.php.net/unsub.php
  • Dmitry Stogov at May 6, 2016 at 6:02 am

    On 05/06/2016 05:06 AM, Jesse Schalken wrote:
    If you're going to say "do what you want" with regards to annotations, then
    just let them be a text string. Parsing the annotation as PHP but not
    evaluating it as PHP seems a very strange and arbitrary half-way point. If
    the thing consuming the AST is expected to eval() it, then why didn't PHP
    do that already? If the thing consuming the AST is expected not to eval()
    it, then it must effectively implement it's own language sharing PHP's
    syntax but not PHP's semantics. Since it can't possibly attach meaning to
    all of PHP's syntax, PHP will enforce that the string is valid PHP even
    though the annotation language will be a very small subset. Not only does
    that buy you very little in terms of validity checking, but it constrains
    the annotation language to be a subset of PHP's syntax even when such a
    constraint may be entirely inappropriate.

    A true "do what you want" approach, if that is the right approach, would be
    for the annotation body to be a free text string.
    You talk about a subset of the proposed RFC.
    It proposes an additional question about AST usage.

    Thanks. Dmitry.
    On Fri, May 6, 2016 at 8:36 AM, Dan Ackroyd wrote:
    On 5 May 2016 at 15:24, Larry Garfield wrote:
    because it doesn't define "right way". Good.
    I could easily see, for instance, Doctrine annotations building the
    first, PHPUnit the second, and Zend the 3rd.
    Good!

    It's not the job of PHP core to tell people how to use annotations.
    People can use them however they want.

    If it turns out that there is a single 'right' way of using them,
    everyone will gravitate to that way anyway.

    If it turns out there are different 'right' ways of using them for
    different use cases, people will be able to pick and choose the
    use-case that is most appropriate.

    And most importantly, if what people think is the 'right' way to use
    them evolves over time, that can be accomplished completely in
    user-land, without needing to update the internal implementation of
    annotations.

    cheers
    Dan

    --
    PHP Internals - PHP Runtime Development Mailing List
    To unsubscribe, visit: http://www.php.net/unsub.php
  • Fleshgrinder at May 8, 2016 at 3:54 pm

    On 5/6/2016 8:02 AM, Dmitry Stogov wrote:
    On 05/06/2016 05:06 AM, Jesse Schalken wrote:
    If you're going to say "do what you want" with regards to annotations,
    then
    just let them be a text string. Parsing the annotation as PHP but not
    evaluating it as PHP seems a very strange and arbitrary half-way
    point. If
    the thing consuming the AST is expected to eval() it, then why didn't PHP
    do that already? If the thing consuming the AST is expected not to eval()
    it, then it must effectively implement it's own language sharing PHP's
    syntax but not PHP's semantics. Since it can't possibly attach meaning to
    all of PHP's syntax, PHP will enforce that the string is valid PHP even
    though the annotation language will be a very small subset. Not only does
    that buy you very little in terms of validity checking, but it constrains
    the annotation language to be a subset of PHP's syntax even when such a
    constraint may be entirely inappropriate.

    A true "do what you want" approach, if that is the right approach,
    would be
    for the annotation body to be a free text string.
    You talk about a subset of the proposed RFC.
    It proposes an additional question about AST usage.
    I think he is talking exactly about the proposed RFC, it is completely
    arbitrary and will lead to much confusion and it is not anymore useful
    than the current PhpDoc approach that we have in userland. Having an
    attribute grammar [1] adds overhead to PHP while parsing our files and
    removes only the regular expression stuff that is currently implemented
    in the annotation systems of all the software out there; which is at
    least offline.

    I do not see a single benefit in the current feature proposal.
    Especially since one can already run the content of a PhpDoc tag through
    the AST thingy and *bam* you have exactly the same thing.

    What we need is an annotation system that works for userland and not
    this attribute grammar crutch just because it is easier to come up with
    and agree upon.

    [1] https://en.wikipedia.org/wiki/Attribute_grammar

    --
    Richard "Fleshgrinder" Fussenegger
  • Alexander Lisachenko at May 5, 2016 at 7:24 am
    Hello, internals!


    2016-05-05 9:48 GMT+03:00 Stanislav Malyshev <smalyshev@gmail.com>:
    Maybe that's what we want to have here - freedom for everybody to invent
    their own languages - but I fear the danger of fragmentation here and
    also people implementing tons of slightly different incompatible parsers
    for ASTs that are generated. We'd have Drupal attributes and Symphony
    attributes and Doctrine attributes and Zend attributes and so on, and
    each of them would have different semantics. Not sure this would be
    good. But maybe it avoids arguing about the syntax now.
    AST for attributes is a nice thing for abstracting from the concrete
    details about how attribute is handling by the concrete implementation. I
    can see a lot of common with class autoloading - earlier there were a lot
    of custom loaders, thanks to spl_autoload_register() that defines a stack
    of callbacks responsible for loading classes by their names. And everyone
    uses custom class loader, but later PSR-0 and PSR-4 were described and
    adopted in composer, so now we have one general tool for that. What if we
    select the same direction with the stack of callback?

    How it should work: PHP engine stores all attributes in the plain AST
    without any transformations. This data should be accessible via
    ReflectionXXX->getAttributes(ReflectionAttribute::RETURN_AST). After that
    userland library can register a hook as attribute loader: e.g
    ReflectionAttribute::registerProcessor(SomeHandler::class, $isPrepend =
    true) or spl_attribute_handler_register(SomeProcessor::class, $isPrepend =
    true)

    Each processor is a class with two methods:

    interface AttributeProcessorInterface {
         public function supports(Php\Ast\Node $attributeNode) : boolean;
         /** @return mixed */
         public function process(Php\Ast\Node $attributeNode);
    }

    After that if we call
    ReflectionXXX->getAttributes(ReflectionAttribute::RETURN_VALUE) PHP engine
    will call each processor and asks it if it supports this AST node. If
    processor supports this node, then engine call it's process($attributeNode)
    method, returning the result as a result, otherwise looks for another
    processor. If no processors can handle this AST then PHP can throw an
    exception about with information about missing processors for attributes.

    I think this way can give a good start point with possibility to
    standardize handling of attributes in the future. From the PHP engine side,
    all attributes are AST nodes that can be processed later on the userland
    side.
  • Dmitry Stogov at May 5, 2016 at 7:34 am

    On 05/05/2016 10:24 AM, Alexander Lisachenko wrote:
    Hello, internals!


    2016-05-05 9:48 GMT+03:00 Stanislav Malyshev <smalyshev@gmail.com

    Maybe that's what we want to have here - freedom for everybody to
    invent
    their own languages - but I fear the danger of fragmentation here and
    also people implementing tons of slightly different incompatible
    parsers
    for ASTs that are generated. We'd have Drupal attributes and Symphony
    attributes and Doctrine attributes and Zend attributes and so on, and
    each of them would have different semantics. Not sure this would be
    good. But maybe it avoids arguing about the syntax now.


    AST for attributes is a nice thing for abstracting from the concrete
    details about how attribute is handling by the concrete
    implementation. I can see a lot of common with class autoloading -
    earlier there were a lot of custom loaders, thanks to
    spl_autoload_register() that defines a stack of callbacks responsible
    for loading classes by their names. And everyone uses custom class
    loader, but later PSR-0 and PSR-4 were described and adopted in
    composer, so now we have one general tool for that. What if we select
    the same direction with the stack of callback?

    How it should work: PHP engine stores all attributes in the plain AST
    without any transformations. This data should be accessible via
    ReflectionXXX->getAttributes(ReflectionAttribute::RETURN_AST). After
    that userland library can register a hook as attribute loader: e.g
    ReflectionAttribute::registerProcessor(SomeHandler::class, $isPrepend
    = true) or spl_attribute_handler_register(SomeProcessor::class,
    $isPrepend = true)

    Each processor is a class with two methods:

    interface AttributeProcessorInterface {
    public function supports(Php\Ast\Node $attributeNode) : boolean;
    /** @return mixed */
    public function process(Php\Ast\Node $attributeNode);
    }

    After that if we call
    ReflectionXXX->getAttributes(ReflectionAttribute::RETURN_VALUE) PHP
    engine will call each processor and asks it if it supports this AST
    node. If processor supports this node, then engine call it's
    process($attributeNode) method, returning the result as a result,
    otherwise looks for another processor. If no processors can handle
    this AST then PHP can throw an exception about with information about
    missing processors for attributes.

    I think this way can give a good start point with possibility to
    standardize handling of attributes in the future. From the PHP engine
    side, all attributes are AST nodes that can be processed later on the
    userland side.
    Something like this may be implemented, but it should be well designed
    and approved first.
    I'm not sure if this functionality should be especially implemented as
    part of Reflection API (this is easily implementable in PHP itself).
    But in any case, this requires the base attribute functionality proposed
    in RFC (or some other).

    Thanks. Dmitry.
  • Lester Caine at May 5, 2016 at 8:08 am

    On 05/05/16 08:34, Dmitry Stogov wrote:
    I think this way can give a good start point with possibility to
    standardize handling of attributes in the future. From the PHP engine
    side, all attributes are AST nodes that can be processed later on the
    userland side.
    Something like this may be implemented, but it should be well designed
    and approved first.
    I'm not sure if this functionality should be especially implemented as
    part of Reflection API (this is easily implementable in PHP itself).
    But in any case, this requires the base attribute functionality proposed
    in RFC (or some other).
    That is all I'm asking ... I thought initially the rfc defined more than
    it does, but just creating another 'free for all' on how something is
    used seems a pointless exercise?

    --
    Lester Caine - G8HFL
    -----------------------------
    Contact - http://lsces.co.uk/wiki/?page=contact
    L.S.Caine Electronic Services - http://lsces.co.uk
    EnquirySolve - http://enquirysolve.com/
    Model Engineers Digital Workshop - http://medw.co.uk
    Rainbow Digital Media - http://rainbowdigitalmedia.co.uk
  • Rowan Collins at May 5, 2016 at 10:36 am

    Stanislav Malyshev wrote on 05/05/2016 07:48:
    The key idea of RFC was not to invite another language for meta-data,
    but use PHP language itself.
    This is a good way to avoid handling a lot of issue, but what I am
    afraid of is that with this solution, what would happen that people
    start doing exactly that - inventing another languages for metadata.

    I tend to agree - what the proposal basically says is "here's a generic
    parser, invent a domain-specific language using that parser".
    Theoretically, people could implement all sorts of weird "operator
    overloading" behaviour:

    <<Push($foo >> Translator)>>

    <<Pipe($foo|lower|escape)>>

    <<Query($foo ?? '//item[id=42]')>>

    Do we really need or want that kind of flexibility, just to avoid
    agreeing a specific structure for metadata?

    Regards,
    Rowan Collins
    [IMSoP]

Related Discussions

Discussion Navigation
viewthread | post
Discussion Overview
groupphp-internals @
categoriesphp
postedApr 30, '16 at 9:12p
activeMay 8, '16 at 3:54p
posts26
users10
websitephp.net

People

Translate

site design / logo © 2018 Grokbase