List of attributes

php.internals

Nicolas Grekas

5 years ago
Hi Benjamin, hi everyone I'm wondering if the syntax that allows for several attributes is really future-proof when considering nested attributes: *1.* #[foo] #[bar] VS *2.* #[foo, bar] Add nested attributes to the mix, here are two possible ways: *A.* #[foo( #[bar] )] or *B.* #[foo( bar )] The A. syntax is consistent with the 1. list. I feel like syntax B is not desired and could be confusing from a grammar pov. BUT in syntax 2., we allow an attribute to be unprefixed (bar), so that syntax B is consistent with 2. Shouldn't we remove syntax 2. in 8.0 and consider it again when nested attributes are introduced? I voted yes for syntax 2. when the attributes were using << >>. I would vote NO now with the new syntax. Nicolas

Rowan Collins

5 years ago
On 27 September 2020 09:22:48 BST, Nicolas Grekas <nicolas.grekas@gmail.com> wrote:
>The A. syntax is consistent with the 1. list. >I feel like syntax B is not desired and could be confusing from a >grammar >pov. >BUT in syntax 2., we allow an attribute to be unprefixed (bar), so that >syntax B is consistent with 2.
I think it's fairly straightforward to see #[...] as meaning "list of attributes", rather than as "change meaning of all identifiers recursively". So, this will look for a constant "MAGIC", not a nested attribute: #[Foo(true, 42), Bar(MAGIC)] class X ... Nested attributes are a slightly confusing concept anyway, because "normal" attributes are always attached to something, but nested attributes occur in place of a normal value. I can see their usefulness, but having the extra #[...] to say "not a normal value, extra magic happening here" seems sensible. Presumably we could also allow grouping of nested attributes such that this: #[Foo(#[Bar, Baz]) class X ... Would just be equivalent to this: #[Foo([ #[Bar], #[Baz] ]) class X ... Regards,
-- Rowan Tommins [IMSoP]

Benjamin Eberlei

5 years ago
On Sun, Sep 27, 2020 at 10:23 AM Nicolas Grekas <nicolas.grekas@gmail.com> wrote:
> Hi Benjamin, hi everyone > > I'm wondering if the syntax that allows for several attributes is really > future-proof when considering nested attributes: >
I feel this question is what an RFC for nested attributes has to weigh. We have established that in practice nested is possible syntax wise. Personally I am not interested in introducing nested attributes, because they would allow a complexity in attribute usage that might be better handled by actual configuration files (XML, JSON, YAML, INI choose your poison). As such I am not looking into working on this addition myself and haven't put any thought into the actual syntax. In general we have not established yet if the support for nested attributes would get a super majority of voters and its increased complexity is the major reason why I opted to leave it out of the original RFC. For Doctrine ORM for example I prototyped this new attribute based driver that flattens the structure so that nested attributes are not needed https://github.com/doctrine/orm/pull/8266 - without making the usage less clear (in my opinion).
> > > *1.* > #[foo] > #[bar] > > VS > > > *2.* > #[foo, bar] > > Add nested attributes to the mix, here are two possible ways: > > > *A.* > #[foo( > #[bar] > )] > > or > > > *B.* > #[foo( > bar > )] >
I'll throw a few more options into the mix - #[foo(bar())] - #[foo(#bar())] - #[foo(new bar())]

Derick Rethans

5 years ago
On 27 September 2020 09:22:48 BST, Nicolas Grekas <nicolas.grekas@gmail.com> wrote:
>Hi Benjamin, hi everyone > >I'm wondering if the syntax that allows for several attributes is >really >future-proof when considering nested attributes: > > >*1.* >#[foo] >#[bar] > >VS > > >*2.* >#[foo, bar]
<snip>
>Shouldn't we remove syntax 2. in 8.0 and consider it again when nested >attributes are introduced?
No. You're about 2 months too late for that. I'm also not in favour of nested attributes. cheers, Derick

Nikita Popov

5 years ago
On Sun, Sep 27, 2020 at 10:23 AM Nicolas Grekas <nicolas.grekas@gmail.com> wrote:
> Hi Benjamin, hi everyone > > I'm wondering if the syntax that allows for several attributes is really > future-proof when considering nested attributes: > > > *1.* > #[foo] > #[bar] > > VS > > > *2.* > #[foo, bar] > > Add nested attributes to the mix, here are two possible ways: > > > *A.* > #[foo( > #[bar] > )] > > or > > > *B.* > #[foo( > bar > )] > > The A. syntax is consistent with the 1. list. > I feel like syntax B is not desired and could be confusing from a grammar > pov. > BUT in syntax 2., we allow an attribute to be unprefixed (bar), so that > syntax B is consistent with 2. > > Shouldn't we remove syntax 2. in 8.0 and consider it again when nested > attributes are introduced? > > I voted yes for syntax 2. when the attributes were using << >>. I would > vote NO now with the new syntax. > > Nicolas >
As far as my understanding goes, if we introduce "nested" attributes, it will be in the form of relaxing constraints on constant expressions, i.e. by allowing you to write #[Attr(new NestedAttr)]. Nikita

Nicolas Grekas

5 years ago
> Hi Benjamin, hi everyone >> >> I'm wondering if the syntax that allows for several attributes is really >> future-proof when considering nested attributes: >> >> >> *1.* >> #[foo] >> #[bar] >> >> VS >> >> >> *2.* >> #[foo, bar] >> >> Add nested attributes to the mix, here are two possible ways: >> >> >> *A.* >> #[foo( >> #[bar] >> )] >> >> or >> >> >> *B.* >> #[foo( >> bar >> )] >> >> The A. syntax is consistent with the 1. list. >> I feel like syntax B is not desired and could be confusing from a grammar >> pov. >> BUT in syntax 2., we allow an attribute to be unprefixed (bar), so that >> syntax B is consistent with 2. >> >> Shouldn't we remove syntax 2. in 8.0 and consider it again when nested >> attributes are introduced? >> >> I voted yes for syntax 2. when the attributes were using << >>. I would >> vote NO now with the new syntax. >> >> Nicolas >> > > As far as my understanding goes, if we introduce "nested" attributes, it > will be in the form of relaxing constraints on constant expressions, i.e. > by allowing you to write #[Attr(new NestedAttr)]. >
That's what I've read in previous discussions and in other replies in this thread. This would break the possibility to read annotations without failing hard when a class is missing. I would much prefer a solution that preserves this capability (which is the reason why we have ReflectionAttribute::newInstance(): unless this one is called, no autoloading happens. To me, this means that "new Foo()" nested in an attribute can't be a solution. Neither should we be able to nest PHP constants there btw. Since we borrowed the syntax to Rust, here is the syntax they use:
> #[validate(length(min = 1), custom = "validate_unique_username")]
I think this would fit quite nicely for us too, by turning eg length into an instance of ReflectionAttribute. Note that a use case for nested attributes is discussed in https://github.com/symfony/symfony/pull/38309, to replace things like: /** @Assert\All([ @Assert\Email, @Assert\NotBlank, @Assert\Length(max=100) ]) */ We can work around of course, and we will for 8.0, but it would be nice to have a plan forward because similar use cases (grouping attributes in a wrapper attribute) are going to be pretty common IMHO. But we're getting a bit of topic. I'm fine keeping things as is for 8.0. I just wanted to raise a point about the syntax for list of attributes but it didn't get traction apparently. Cheers, Nicolas

Benjamin Eberlei

5 years ago
On Mon, Sep 28, 2020 at 2:58 PM Nicolas Grekas <nicolas.grekas@gmail.com> wrote:
> > Hi Benjamin, hi everyone >>> >>> I'm wondering if the syntax that allows for several attributes is really >>> future-proof when considering nested attributes: >>> >>> >>> *1.* >>> #[foo] >>> #[bar] >>> >>> VS >>> >>> >>> *2.* >>> #[foo, bar] >>> >>> Add nested attributes to the mix, here are two possible ways: >>> >>> >>> *A.* >>> #[foo( >>> #[bar] >>> )] >>> >>> or >>> >>> >>> *B.* >>> #[foo( >>> bar >>> )] >>> >>> The A. syntax is consistent with the 1. list. >>> I feel like syntax B is not desired and could be confusing from a grammar >>> pov. >>> BUT in syntax 2., we allow an attribute to be unprefixed (bar), so that >>> syntax B is consistent with 2. >>> >>> Shouldn't we remove syntax 2. in 8.0 and consider it again when nested >>> attributes are introduced? >>> >>> I voted yes for syntax 2. when the attributes were using << >>. I would >>> vote NO now with the new syntax. >>> >>> Nicolas >>> >> >> As far as my understanding goes, if we introduce "nested" attributes, it >> will be in the form of relaxing constraints on constant expressions, i.e. >> by allowing you to write #[Attr(new NestedAttr)]. >> > > That's what I've read in previous discussions and in other replies in this > thread. > This would break the possibility to read annotations without failing hard > when a class is missing. > > I would much prefer a solution that preserves this capability (which is > the reason why we have ReflectionAttribute::newInstance(): unless this one > is called, no autoloading happens. > > To me, this means that "new Foo()" nested in an attribute can't be a > solution. > Neither should we be able to nest PHP constants there btw. >
Can you clarify your reasoning here to get to this conclusion? because with #[Foo(Foo::BAR)] we also not trigger autoloading right now until newInstance() or getArguments() is called. This is the way it is working right now: https://gist.github.com/beberlei/8150f60abaab3b0a70ebb76ce6a379b0 Attributes allow constant expressions as arguments, the same which you can do in constant or property declarations for the default value. An approach with new NestedAttr() would work the same, only triggering autoload when argument needs to be evaluated for use.

Nicolas Grekas

5 years ago
> > Hi Benjamin, hi everyone >>>> >>>> I'm wondering if the syntax that allows for several attributes is really >>>> future-proof when considering nested attributes: >>>> >>>> >>>> *1.* >>>> #[foo] >>>> #[bar] >>>> >>>> VS >>>> >>>> >>>> *2.* >>>> #[foo, bar] >>>> >>>> Add nested attributes to the mix, here are two possible ways: >>>> >>>> >>>> *A.* >>>> #[foo( >>>> #[bar] >>>> )] >>>> >>>> or >>>> >>>> >>>> *B.* >>>> #[foo( >>>> bar >>>> )] >>>> >>>> The A. syntax is consistent with the 1. list. >>>> I feel like syntax B is not desired and could be confusing from a >>>> grammar >>>> pov. >>>> BUT in syntax 2., we allow an attribute to be unprefixed (bar), so that >>>> syntax B is consistent with 2. >>>> >>>> Shouldn't we remove syntax 2. in 8.0 and consider it again when nested >>>> attributes are introduced? >>>> >>>> I voted yes for syntax 2. when the attributes were using << >>. I would >>>> vote NO now with the new syntax. >>>> >>>> Nicolas >>>> >>> >>> As far as my understanding goes, if we introduce "nested" attributes, it >>> will be in the form of relaxing constraints on constant expressions, i.e. >>> by allowing you to write #[Attr(new NestedAttr)]. >>> >> >> That's what I've read in previous discussions and in other replies in >> this thread. >> This would break the possibility to read annotations without failing hard >> when a class is missing. >> >> I would much prefer a solution that preserves this capability (which is >> the reason why we have ReflectionAttribute::newInstance(): unless this one >> is called, no autoloading happens. >> >> To me, this means that "new Foo()" nested in an attribute can't be a >> solution. >> Neither should we be able to nest PHP constants there btw. >> > > Can you clarify your reasoning here to get to this conclusion? because > with #[Foo(Foo::BAR)] we also not trigger autoloading right now until > newInstance() > or getArguments() is called. This is the way it is working right now: > https://gist.github.com/beberlei/8150f60abaab3b0a70ebb76ce6a379b0 > > Attributes allow constant expressions as arguments, the same which you can > do in constant or property declarations for the default value. > > An approach with new NestedAttr() would work the same, only triggering > autoload when argument needs to be evaluated for use. >
My bad about constants, I was wrong, they already work as part of constant expressions as you highlighted. I was propagating this laziness requirement to nested attributes: they should also be represented as instances of ReflectionAttribute to me at some point. It would look important to me to be able to inspect a tree of attributes and decide what to do with each of them before actually instantiating them (and triggering the autoload cascade). I'm not convinced that nested attributes should be brought to us via an improvement of constant-expressions: first because that would break this laziness requirement, second because I just doubt that constant-expressions should support objects. About providing laziness for attributes, this could be done by ReflectionAttribute::getAttributes(): #[Foo(bar())] could lead to getAttribute returning an array containing a ReflectionAttribute instance for attribute "bar". Of course, this has many consequences and also many alternatives that'd need to be evaluated. I won't be able to lead an effort towards nested attributes in the short term, so I'll just let this on the table here :) Cheers, Nicolas

Theodore Brown

5 years ago
On Mon, Oct 5, 2020 at 9:24 AM Nicolas Grekas <nicolas.grekas@gmail.com> wrote: >> I'm wondering if the syntax that allows for several attributes is >> really future-proof when considering nested attributes: >> >> >> *1.* >> #[foo] >> #[bar] >> >> VS >> >> *2.* >> #[foo, bar] >> >> Add nested attributes to the mix, here are two possible ways: >> >> *A.* >> #[foo( >> #[bar] >> )] >> >> or >> >> >> *B.* >> #[foo( >> bar >> )] >> >> The A. syntax is consistent with the 1. list. I feel like syntax >> B is not desired and could be confusing from a grammar pov. >> BUT in syntax 2., we allow an attribute to be unprefixed (bar), >> so that syntax B is consistent with 2. >> >> Shouldn't we remove syntax 2. in 8.0 and consider it again when nested >> attributes are introduced? >> >> I voted yes for syntax 2. when the attributes were using << >>. I would >> vote NO now with the new syntax. >> >> Nicolas > > As far as my understanding goes, if we introduce "nested" attributes, it > will be in the form of relaxing constraints on constant expressions, i.e. > by allowing you to write #[Attr(new NestedAttr)]. > > Nikita Back when the original `<<>>` attribute syntax was being implemented, there was an attempt to do just this. But it turned out to be very difficult to implement, and it was ultimately given up on since it required significant changes to const expressions. Earlier this year there was also a poll to gauge interest in supporting function calls in constant expressions, but most voters opposed it. [1] Even if a proposal for relaxing constraints on constant expressions gains enough support, it's not clear this is the ideal path forward for nested attributes, since as Nicolas pointed out this wouldn't have the same lazy-loading semantics as existing attributes. Straightforward support for nested attributes was one of my main motivations for proposing the `@@` syntax in the Shorter Attribute Syntax RFC, [2] and I had hoped to collaborate on a follow-up RFC to support nested attributes with the AT-AT syntax. This would allow existing nested docblock annotations such as Nicolas's example to translate intuitively to native attributes: @@Assert\All([ @@Assert\Email, @@Assert\NotBlank, @@Assert\Length(max: 100) ]) This would also preserve the lazy-loading feature where these attribute classes aren't loaded until code calls `newInstance` on one of the `ReflectionAttribute` objects. But then the Shorter Attribute Syntax Change RFC [3] came along and derailed this plan... In theory nested attributes could be supported in the same way with the `#[]` syntax, but it's more verbose and I think less intuitive (e.g. people may try to use the grouped syntax in this context, but it wouldn't work). Also the combination of brackets delineating both arrays and attributes reduces readability: #[Assert\All([ #[Assert\Email], #[Assert\NotBlank], #[Assert\Length(max: 100)] ])] But at this point I assume this is the most viable path forward for nested attributes (barring another syntax re-vote and delay of PHP 8). I know Derick and Benjamin have stated they aren't in favor of nested attributes and didn't put any thought into the syntax for them, but I feel this is unfortunate since nested attributes are an established pattern with legitimate use cases in existing libraries. Best regards, Theodore [1]: https://wiki.php.net/rfc/calls_in_constant_expressions_poll [2]: https://wiki.php.net/rfc/shorter_attribute_syntax [3]: https://wiki.php.net/rfc/shorter_attribute_syntax_change

Benas IML

5 years ago
On Mon, Oct 19, 2020, 6:17 PM Theodore Brown <theodorejb@outlook.com> wrote:
> On Mon, Oct 5, 2020 at 9:24 AM Nicolas Grekas <nicolas.grekas@gmail.com> > wrote: > > >> I'm wondering if the syntax that allows for several attributes is > >> really future-proof when considering nested attributes: > >> > >> > >> *1.* > >> #[foo] > >> #[bar] > >> > >> VS > >> > >> *2.* > >> #[foo, bar] > >> > >> Add nested attributes to the mix, here are two possible ways: > >> > >> *A.* > >> #[foo( > >> #[bar] > >> )] > >> > >> or > >> > >> > >> *B.* > >> #[foo( > >> bar > >> )] > >> > >> The A. syntax is consistent with the 1. list. I feel like syntax > >> B is not desired and could be confusing from a grammar pov. > >> BUT in syntax 2., we allow an attribute to be unprefixed (bar), > >> so that syntax B is consistent with 2. > >> > >> Shouldn't we remove syntax 2. in 8.0 and consider it again when nested > >> attributes are introduced? > >> > >> I voted yes for syntax 2. when the attributes were using << >>. I would > >> vote NO now with the new syntax. > >> > >> Nicolas > > > > As far as my understanding goes, if we introduce "nested" attributes, it > > will be in the form of relaxing constraints on constant expressions, i.e. > > by allowing you to write #[Attr(new NestedAttr)]. > > > > Nikita > > Back when the original `<<>>` attribute syntax was being implemented, > there was an attempt to do just this. But it turned out to be very > difficult to implement, and it was ultimately given up on since it > required significant changes to const expressions. Earlier this year > there was also a poll to gauge interest in supporting function calls > in constant expressions, but most voters opposed it. [1] > > Even if a proposal for relaxing constraints on constant expressions > gains enough support, it's not clear this is the ideal path forward > for nested attributes, since as Nicolas pointed out this wouldn't > have the same lazy-loading semantics as existing attributes. > > Straightforward support for nested attributes was one of my main > motivations for proposing the `@@` syntax in the Shorter Attribute > Syntax RFC, [2] and I had hoped to collaborate on a follow-up RFC to > support nested attributes with the AT-AT syntax. This would allow > existing nested docblock annotations such as Nicolas's example to > translate intuitively to native attributes: > > @@Assert\All([ > @@Assert\Email, > @@Assert\NotBlank, > @@Assert\Length(max: 100) > ]) > > This would also preserve the lazy-loading feature where these > attribute classes aren't loaded until code calls `newInstance` > on one of the `ReflectionAttribute` objects. > > But then the Shorter Attribute Syntax Change RFC [3] came along and > derailed this plan... > > In theory nested attributes could be supported in the same way with > the `#[]` syntax, but it's more verbose and I think less intuitive > (e.g. people may try to use the grouped syntax in this context, but > it wouldn't work). Also the combination of brackets delineating both > arrays and attributes reduces readability: >
Welp, both variants seem to be readable.
> #[Assert\All([ > #[Assert\Email], > #[Assert\NotBlank], > #[Assert\Length(max: 100)] > ])] > > But at this point I assume this is the most viable path forward for > nested attributes (barring another syntax re-vote and delay of PHP 8). >
Are you actually kidding me? 4th revote? Please no. I know Derick and Benjamin have stated they aren't in favor of nested
> attributes and didn't put any thought into the syntax for them, but I > feel this is unfortunate since nested attributes are an established > pattern with legitimate use cases in existing libraries. > > Best regards, > Theodore
Best regards, Benas
> > [1]: https://wiki.php.net/rfc/calls_in_constant_expressions_poll > [2]: https://wiki.php.net/rfc/shorter_attribute_syntax > [3]: https://wiki.php.net/rfc/shorter_attribute_syntax_change > -- > PHP Internals - PHP Runtime Development Mailing List > To unsubscribe, visit:
https://www.php.net/unsub.php

Rowan Collins

5 years ago
On Mon, 19 Oct 2020 at 16:17, Theodore Brown <theodorejb@outlook.com> wrote:
> > In theory nested attributes could be supported in the same way with > the `#[]` syntax, but it's more verbose and I think less intuitive > (e.g. people may try to use the grouped syntax in this context, but > it wouldn't work). Also the combination of brackets delineating both > arrays and attributes reduces readability: > > #[Assert\All([ > #[Assert\Email], > #[Assert\NotBlank], > #[Assert\Length(max: 100)] > ])] >
I think you're presupposing how a feature would work that hasn't even been specced out yet. On the face of it, it would seem logical and achievable for the above to be equivalent to this: #[Assert\All( #[ Assert\Email, Assert\NotBlank, Assert\Length(max: 100) ] )] i.e. for a list of grouped attributes in nested context to be equivalent to an array of nested attributes. Unless nested attributes were limited to being direct arguments to another attribute, the *semantics* of nested attributes inside arrays would have to be defined anyway (e.g. how they would look in reflection, whether they would be recursively instantiated by newInstance(), etc). Regards,
-- Rowan Tommins [IMSoP]

Theodore Brown

5 years ago
On Tue, Oct 20, 2020 at 10:13 AM Rowan Tommins <rowan.collins@gmail.com> wrote: > On Mon, 19 Oct 2020 at 16:17, Theodore Brown <theodorejb@outlook.com> wrote: > > > > > In theory nested attributes could be supported in the same way with > > the `#[]` syntax, but it's more verbose and I think less intuitive > > (e.g. people may try to use the grouped syntax in this context, but > > it wouldn't work). Also the combination of brackets delineating both > > arrays and attributes reduces readability: > > > > #[Assert\All([ > > #[Assert\Email], > > #[Assert\NotBlank], > > #[Assert\Length(max: 100)] > > ])] > > > > I think you're presupposing how a feature would work that hasn't > even been specced out yet. On the face of it, it would seem logical > and achievable for the above to be equivalent to this: > > #[Assert\All( > #[ > Assert\Email, > Assert\NotBlank, > Assert\Length(max: 100) > ] > )] > > i.e. for a list of grouped attributes in nested context to be > equivalent to an array of nested attributes. Hi Rowan, The problem with this is that it results in inconsistent semantics, where the number of items in an attribute group results in a different type of value being passed. I.e. if you remove two of the three attributes from the list, suddenly the code will break since the `Assert\All` attribute is no longer being passed an array. // error when Assert\All is instantiated since it's no longer being // passed an array of attributes: #[Assert\All( #[ Assert\Email, ] )] So when removing nested attributes from a list such that there's only one left in the list, you'd have to remember to additionally wrap the attribute group in array brackets: #[Assert\All( [#[ Assert\Email, ]] )] And of course when you want to add additional attributes to a group that originally had only one attribute, you'd have to remember to remove the extra array brackets. Generally speaking, having the number of items in a list change the type of the list is a recipe for confusion and unexpected errors. So I think the only sane approach is to ban using the grouped syntax in combination with nested attributes. Unfortunately, no one thought through these issues when proposing to change the shorter attribute syntax away from @@ and add the grouped syntax, and now it seems like we're stuck with a syntax that is unnecessarily complex and confusing to use for nested attributes. > Unless nested attributes were limited to being direct arguments to > another attribute, the *semantics* of nested attributes inside > arrays would have to be defined anyway (e.g. how they would look in > reflection, whether they would be recursively instantiated by > newInstance(), etc). Yes, the exact semantics of nested attributes need to be defined, but this is a completely separate topic from the grouped syntax issue described above. As a user I would expect nested attributes to be reflected the same as any other attribute (i.e. as a `ReflectionAttribute` instance). Calling `getArguments` on a parent attribute would return an array with `ReflectionAttribute` objects for any nested attribute passed as a direct argument or array value. On first thought I think it makes sense to recursively instantiate nested attributes when calling `newInstance` on a parent attribute. This would allow attribute class constructors to specify the type for each attribute argument. But again, this is a separate discussion from the issue of nested attribute syntax. Kind regards, Theodore

Michael Voříšek - ČVUT FEL

5 years ago
Wdyt about dedicating "#" char for attributes? We can deprecate comments with "#" in 8.0 or 8.1 and in later version support attributes with "#" single character - without [] brackets, ie. support: #Attribute1 #Attribte2 public function x()... which will be equivalent to: #[ #Attribute1 #Attribte2 ] public function x()... Is there anything against excl. BC break of "#" comments? Who is for it? With kind regards / Mit freundlichen Grüßen / S přátelským pozdravem, Michael Voříšek On 23 Oct 2020 17:52, Theodore Brown wrote:
> On Tue, Oct 20, 2020 at 10:13 AM Rowan Tommins <rowan.collins@gmail.com> wrote: > > On Mon, 19 Oct 2020 at 16:17, Theodore Brown <theodorejb@outlook.com> wrote: > > In theory nested attributes could be supported in the same way with > the `#[]` syntax, but it's more verbose and I think less intuitive > (e.g. people may try to use the grouped syntax in this context, but > it wouldn't work). Also the combination of brackets delineating both > arrays and attributes reduces readability: > > #[Assert\All([ > #[Assert\Email], > #[Assert\NotBlank], > #[Assert\Length(max: 100)] > ])] > > I think you're presupposing how a feature would work that hasn't > even been specced out yet. On the face of it, it would seem logical > and achievable for the above to be equivalent to this: > > #[Assert\All( > #[ > Assert\Email, > Assert\NotBlank, > Assert\Length(max: 100) > ] > )] > > i.e. for a list of grouped attributes in nested context to be > equivalent to an array of nested attributes.
Hi Rowan, The problem with this is that it results in inconsistent semantics, where the number of items in an attribute group results in a different type of value being passed. I.e. if you remove two of the three attributes from the list, suddenly the code will break since the `Assert\All` attribute is no longer being passed an array. // error when Assert\All is instantiated since it's no longer being // passed an array of attributes: #[Assert\All( #[ Assert\Email, ] )] So when removing nested attributes from a list such that there's only one left in the list, you'd have to remember to additionally wrap the attribute group in array brackets: #[Assert\All( [#[ Assert\Email, ]] )] And of course when you want to add additional attributes to a group that originally had only one attribute, you'd have to remember to remove the extra array brackets. Generally speaking, having the number of items in a list change the type of the list is a recipe for confusion and unexpected errors. So I think the only sane approach is to ban using the grouped syntax in combination with nested attributes. Unfortunately, no one thought through these issues when proposing to change the shorter attribute syntax away from @@ and add the grouped syntax, and now it seems like we're stuck with a syntax that is unnecessarily complex and confusing to use for nested attributes.
> Unless nested attributes were limited to being direct arguments to > another attribute, the *semantics* of nested attributes inside > arrays would have to be defined anyway (e.g. how they would look in > reflection, whether they would be recursively instantiated by > newInstance(), etc).
Yes, the exact semantics of nested attributes need to be defined, but this is a completely separate topic from the grouped syntax issue described above. As a user I would expect nested attributes to be reflected the same as any other attribute (i.e. as a `ReflectionAttribute` instance). Calling `getArguments` on a parent attribute would return an array with `ReflectionAttribute` objects for any nested attribute passed as a direct argument or array value. On first thought I think it makes sense to recursively instantiate nested attributes when calling `newInstance` on a parent attribute. This would allow attribute class constructors to specify the type for each attribute argument. But again, this is a separate discussion from the issue of nested attribute syntax. Kind regards, Theodore
-- PHP Internals - PHP Runtime Development Mailing List To unsubscribe, visit: https://www.php.net/unsub.php

Benjamin Morel

5 years ago
On Fri, 23 Oct 2020 at 19:08, Michael Voříšek - ČVUT FEL < vorismi3@fel.cvut.cz> wrote:
> We can deprecate comments with "#" in 8.0 or 8.1 > > ... > > Is there anything against excl. BC break of "#" comments? Who is for it? > >
I'm all for it, especially considering that it's trivial to fix old codebases with automated tools with 100% confidence. But I'm afraid it's too late to do this for 8.0, even though a major release would have been the perfect time to deprecate such a feature. — Benjamin

Rowan Collins

5 years ago
On 23/10/2020 16:52, Theodore Brown wrote:
> The problem with this is that it results in inconsistent semantics, > where the number of items in an attribute group results in a > different type of value being passed. I.e. if you remove two of the > three attributes from the list, suddenly the code will break since > the `Assert\All` attribute is no longer being passed an array.
That's a fair point. Perhaps nested attributes could *always* be considered a list? That may sound odd, but it's actually quite consistent with "normal" attributes: #[Foo] class C {} var_dump( (new ReflectionClass('C'))->getAttributes() ); Gives an array with one item in it, not an object: array(1) {     [0]=> object(ReflectionAttribute)#2 (0) { } } So similarly: #[Foo( 42, #[Bar] ) class C {} var_dump( (new ReflectionClass('C'))->getAttributes()[0]->getArguments() ); Could give an array with one item in it as the argument: array(2) {     [0] => int(42),     [1] => array(1) {         [0]=> object(ReflectionAttribute)#3 (0) { }     } } This actually feels quite intuitive, because we're used to square brackets representing arrays. The only downside is a slight inconvenience for cases where you want an argument of exactly one nested attribute, but I don't know how common that is. Regards,
-- Rowan Tommins (né Collins) [IMSoP]

Nicolas Grekas

5 years ago
Hi all! about why we'd need nested attributes, here is a discussion about nested validation constraints: https://github.com/symfony/symfony/issues/38503 You'll see that we're looking hard into ways around using nested attributes, but they all fall short. We actually concluded that we should not create a hacky syntax before php-internals settled on the topic. I invite you to review the ideas and arguments posted there to see a real world need on the topic (please don't mind sharing your thoughts about the topic in the PR of course if you think you can help us move forward.) See more comments below: Le ven. 23 oct. 2020 à 17:52, Theodore Brown <theodorejb@outlook.com> a écrit :
> On Tue, Oct 20, 2020 at 10:13 AM Rowan Tommins <rowan.collins@gmail.com> > wrote: > > > On Mon, 19 Oct 2020 at 16:17, Theodore Brown <theodorejb@outlook.com> > wrote: > > > > > > > > In theory nested attributes could be supported in the same way with > > > the `#[]` syntax, but it's more verbose and I think less intuitive > > > (e.g. people may try to use the grouped syntax in this context, but > > > it wouldn't work). Also the combination of brackets delineating both > > > arrays and attributes reduces readability: > > > > > > #[Assert\All([ > > > #[Assert\Email], > > > #[Assert\NotBlank], > > > #[Assert\Length(max: 100)] > > > ])] > > > > > > > I think you're presupposing how a feature would work that hasn't > > even been specced out yet. On the face of it, it would seem logical > > and achievable for the above to be equivalent to this: > > > > #[Assert\All( > > #[ > > Assert\Email, > > Assert\NotBlank, > > Assert\Length(max: 100) > > ] > > )] > > > > i.e. for a list of grouped attributes in nested context to be > > equivalent to an array of nested attributes. > > Hi Rowan, > > The problem with this is that it results in inconsistent semantics, > where the number of items in an attribute group results in a > different type of value being passed. I.e. if you remove two of the > three attributes from the list, suddenly the code will break since > the `Assert\All` attribute is no longer being passed an array. >
Yes. This would be solved by NOT accepting #[] inside attribute declarations. Since we borrowed from Rust, let's borrow this again: http://web.mit.edu/rust-lang_v1.25/arch/amd64_ubuntu1404/share/doc/rust/html/reference/attributes.html#attributes The Rust syntax accepts the #[] only on the outside of the declaration. The example above should be written as: #[Assert\All([ Assert\Email(), Assert\NotBlank(), Assert\Length(max: 100) ])] The closing brackets () would be required to remove any ambiguity with constants. Since constant expressions won't ever allow functions in them, this isn't ambiguous with function calls.
> // error when Assert\All is instantiated since it's no longer being > // passed an array of attributes: > > #[Assert\All( > #[ > Assert\Email, > ] > )] > > So when removing nested attributes from a list such that there's only > one left in the list, you'd have to remember to additionally wrap the > attribute group in array brackets: > > #[Assert\All( > [#[ > Assert\Email, > ]] > )] > > And of course when you want to add additional attributes to a group > that originally had only one attribute, you'd have to remember to > remove the extra array brackets. > > Generally speaking, having the number of items in a list change the > type of the list is a recipe for confusion and unexpected errors. So > I think the only sane approach is to ban using the grouped syntax in > combination with nested attributes. > > Unfortunately, no one thought through these issues when proposing to > change the shorter attribute syntax away from @@ and add the grouped > syntax, and now it seems like we're stuck with a syntax that is > unnecessarily complex and confusing to use for nested attributes. > > > Unless nested attributes were limited to being direct arguments to > > another attribute, the *semantics* of nested attributes inside > > arrays would have to be defined anyway (e.g. how they would look in > > reflection, whether they would be recursively instantiated by > > newInstance(), etc). > > Yes, the exact semantics of nested attributes need to be defined, but > this is a completely separate topic from the grouped syntax issue > described above. > > As a user I would expect nested attributes to be reflected the same > as any other attribute (i.e. as a `ReflectionAttribute` instance). > Calling `getArguments` on a parent attribute would return an array > with `ReflectionAttribute` objects for any nested attribute passed > as a direct argument or array value. >
I 100% agree with this. On first thought I think it makes sense to recursively instantiate
> nested attributes when calling `newInstance` on a parent attribute. > This would allow attribute class constructors to specify the type > for each attribute argument. But again, this is a separate discussion > from the issue of nested attribute syntax. >
Also 100% agree with this. Cheers, Nicolas

Rowan Collins

5 years ago
On 28/10/2020 16:58, Nicolas Grekas wrote:
> about why we'd need nested attributes, here is a discussion about > nested validation constraints: > https://github.com/symfony/symfony/issues/38503
Thanks, it's useful to see some real-world examples. As I suspected, nearly all of these explicitly take a *list* of nested attributes, not a single attribute.
> While most of the constraints receive the nested constraints as
simple array, |Collection| however requires a mapping (field to constraint) and is usually combined with other composite constraints, which gives us a second nesting level. There is much discussion of Collection as an edge case, but although the nested attributes are indeed inside a mapping, the *value* of that mapping is again a list of attributes. Single items are allowed, but this seems to be a special case for convenience. In the below example from https://symfony.com/doc/current/reference/constraints/Collection.html the @Assert\Email could equivalently be written as an array with one item:     /**      * @Assert\Collection(      *     fields = {      *         "personal_email" = @Assert\Email,      *         "short_bio" = {      *             @Assert\NotBlank,      *             @Assert\Length(      *                 max = 100,      *                 maxMessage = "Your short bio is too long!"      *             )      *         }      *     },      *     allowMissingFields = true      * )      */ This reinforces my earlier suggestion (https://externals.io/message/111936#112109) that #[Foo] in a nested context can simply imply an array of one attribute, rendering the above as: #[Assert\Collection(     fields: [          "personal_email" => #[Assert\Email],          "short_bio" => #[                 Assert\NotBlank,                 Assert\Length(                       max: 100,                       maxMessage: "Your short bio is too long!"                )         ]     ],     allowMissingFields: true ] Regards,
-- Rowan Tommins (né Collins) [IMSoP]