[RFC] Simple Annotations

php.internals

Rasmus Schultz

10 years ago
Dear Internals, I'm announcing a simplified RFC for annotations: https://wiki.php.net/rfc/simple-annotations It's an alternative to the proposed Attributes RFC and the (2010) Annotations RFC. I'm attempting with this to describe a feature that is closer to the language than what is proposed by the Attributes RFC, by permitting the use of any valid PHP expression as an annotation value. Where the Attributes RFC proposes new syntax for what is essentially arrays, this proposal instead permits you to use actual arrays, e.g. without inventing any new syntax. It also allows you to use values of any other type, including objects. This makes the proposed feature more immediately useful, with a minimal learning curve. Compared with the old Annotations RFC (and Doctrine Annotations, etc.) this proposal does not attempt to define or enforce any rules about what annotations are, permitted usage, inheritance rules, etc. - instead it adds a very simple language feature upon which the community may choose to build (and evolve) facilities that implement additional rules and logic. My hope is that, if we can agree on something very small and simple, we can defer the more opinionated discussions about rules and logic to the community. In my opinion, language features should be simple, and consistent with the language - I believe the way to do that, is to avoid discussions about how such as facility should be used, and instead focus on how it should work. There is a lot of opinion built into the old Annotations RFC, and into Doctrine - features that attempt to dictate how the feature should be used. I don't believe the language should dictate what is or isn't correct or appropriate use. Please review and comment. Thanks, Rasmus Schultz

Davey

10 years ago
On Fri, May 13, 2016 at 2:11 PM, Rasmus Schultz <rasmus@mindplay.dk> wrote:
> Dear Internals, > > I'm announcing a simplified RFC for annotations: > > https://wiki.php.net/rfc/simple-annotations > > It's an alternative to the proposed Attributes RFC and the (2010) > Annotations RFC. > > I'm attempting with this to describe a feature that is closer to the > language than what is proposed by the Attributes RFC, by permitting > the use of any valid PHP expression as an annotation value. > > Where the Attributes RFC proposes new syntax for what is essentially > arrays, this proposal instead permits you to use actual arrays, e.g. > without inventing any new syntax. It also allows you to use values of > any other type, including objects. This makes the proposed feature > more immediately useful, with a minimal learning curve. > > Compared with the old Annotations RFC (and Doctrine Annotations, etc.) > this proposal does not attempt to define or enforce any rules about > what annotations are, permitted usage, inheritance rules, etc. - > instead it adds a very simple language feature upon which the > community may choose to build (and evolve) facilities that implement > additional rules and logic. > > My hope is that, if we can agree on something very small and simple, > we can defer the more opinionated discussions about rules and logic to > the community. > > In my opinion, language features should be simple, and consistent with > the language - I believe the way to do that, is to avoid discussions > about how such as facility should be used, and instead focus on how it > should work. There is a lot of opinion built into the old Annotations > RFC, and into Doctrine - features that attempt to dictate how the > feature should be used. I don't believe the language should dictate > what is or isn't correct or appropriate use.
I think I like this, however a couple of simple questions: 1) Any chance for (optionally) _naming_ annotations? It would be nice to be able to do: ->getAnnotation('table') and not have to handle unnecessary annotations 2) I like the idea of Hacks memoize for example, how do you see those being implemented? Or don't you? 3) When are annotations executed? On ->getAnnotations()? Every time? Thanks,

Mathieu Rochette

10 years ago
On 05/13/2016 02:57 PM, Davey Shafik wrote:
> On Fri, May 13, 2016 at 2:11 PM, Rasmus Schultz <rasmus@mindplay.dk> wrote: > >> Dear Internals, >> >> I'm announcing a simplified RFC for annotations: >> >> https://wiki.php.net/rfc/simple-annotations >> >> It's an alternative to the proposed Attributes RFC and the (2010) >> Annotations RFC. >> >> I'm attempting with this to describe a feature that is closer to the >> language than what is proposed by the Attributes RFC, by permitting >> the use of any valid PHP expression as an annotation value. >> >> Where the Attributes RFC proposes new syntax for what is essentially >> arrays, this proposal instead permits you to use actual arrays, e.g. >> without inventing any new syntax. It also allows you to use values of >> any other type, including objects. This makes the proposed feature >> more immediately useful, with a minimal learning curve. >> >> Compared with the old Annotations RFC (and Doctrine Annotations, etc.) >> this proposal does not attempt to define or enforce any rules about >> what annotations are, permitted usage, inheritance rules, etc. - >> instead it adds a very simple language feature upon which the >> community may choose to build (and evolve) facilities that implement >> additional rules and logic. >> >> My hope is that, if we can agree on something very small and simple, >> we can defer the more opinionated discussions about rules and logic to >> the community. >> >> In my opinion, language features should be simple, and consistent with >> the language - I believe the way to do that, is to avoid discussions >> about how such as facility should be used, and instead focus on how it >> should work. There is a lot of opinion built into the old Annotations >> RFC, and into Doctrine - features that attempt to dictate how the >> feature should be used. I don't believe the language should dictate >> what is or isn't correct or appropriate use. > > I think I like this, however a couple of simple questions: > > 1) Any chance for (optionally) _naming_ annotations? It would be nice to be > able to do: ->getAnnotation('table') and not have to handle unnecessary > annotations
not sure you need that, you can do this instead: ->getAnnotations(Table::class) however it won't throw an error if you have more than one "Table::class annotation" whereas I supposed you would expect so with a named annotation
> > 2) I like the idea of Hacks memoize for example, how do you see those being > implemented? Or don't you?
if Rasmus goal is to that "we can agree on something very small and simple" I guess it would best be left for another rfc
> > 3) When are annotations executed? On ->getAnnotations()? Every time?
given they are context free, I suppose they would be executed on the first call only but what about this kind of annotation classes? : class Counter { private static $i = 0; private $n; function __construct() { $this->n = self::$i++; } function get() { return $this->n; } } both the getAnnotations() calls order and whether or not annotations are executed every time would make a difference
> > Thanks, >
-- Mathieu Rochette

Marco Pivetta

10 years ago
Love the simplicity of the RFC, but I can already see people doing terrible things with it: << new DateTimeImmutable() >> << log(get_instance('logger')) >> And other global-state related stuff. TBH, I'd rather just allow a constant array (with constant expressions only), and that would be good enough :-) Cheers, Marco Pivetta http://twitter.com/Ocramius http://ocramius.github.com/ On 13 May 2016 at 15:15, Mathieu Rochette <mathieu@texthtml.net> wrote:

Fleshgrinder

10 years ago
On 5/13/2016 2:57 PM, Davey Shafik wrote:
> 2) I like the idea of Hacks memoize for example, how do you see those being > implemented? Or don't you? >
I wrote it in the /attribute grammar/ thread and I write it here again. Stuff like memoize should be implemented as keywords as part of the language and not via annotations. memoized function f() {} Same goes for deprecated: deprecated memoized function f() {} Or other special effects: deprecated inlined memoized function f() {} These things are not meta-data for userland, they are meta-data for PHP and should be part of, well, PHP.
-- Richard "Fleshgrinder" Fussenegger

Benjamin Eberlei

10 years ago
On Fri, May 13, 2016 at 2:11 PM, Rasmus Schultz <rasmus@mindplay.dk> wrote:
> Dear Internals, > > I'm announcing a simplified RFC for annotations: > > https://wiki.php.net/rfc/simple-annotations > > It's an alternative to the proposed Attributes RFC and the (2010) > Annotations RFC. > > I'm attempting with this to describe a feature that is closer to the > language than what is proposed by the Attributes RFC, by permitting > the use of any valid PHP expression as an annotation value. > > Where the Attributes RFC proposes new syntax for what is essentially > arrays, this proposal instead permits you to use actual arrays, e.g. > without inventing any new syntax. It also allows you to use values of > any other type, including objects. This makes the proposed feature > more immediately useful, with a minimal learning curve. >
The attributes RFC actually does not specific syntax or arrays, but either a scalar (number, string) or an ast\node. A proposal for arrays *would* actually be "closer to the language" as you describe it. Compared with the old Annotations RFC (and Doctrine Annotations, etc.)
> this proposal does not attempt to define or enforce any rules about > what annotations are, permitted usage, inheritance rules, etc. - > instead it adds a very simple language feature upon which the > community may choose to build (and evolve) facilities that implement > additional rules and logic. > > My hope is that, if we can agree on something very small and simple, > we can defer the more opinionated discussions about rules and logic to > the community. > > In my opinion, language features should be simple, and consistent with > the language - I believe the way to do that, is to avoid discussions > about how such as facility should be used, and instead focus on how it > should work. There is a lot of opinion built into the old Annotations > RFC, and into Doctrine - features that attempt to dictate how the > feature should be used. I don't believe the language should dictate > what is or isn't correct or appropriate use. > > Please review and comment.
I see you took my question about context from the other thread and defined annotations to be context less. You should add to the RFC examples of what is happening, if you do access context, for example access to undefined variable notices: <<$x>> class Foo {} $refl = new ReflectionClass('Foo'); var_dump($refl->getAttributes(Foo::class)); // PHP Notice: Undefined variable: x in Command line code on line 1 Still this is imho a weak point of your RFC. You already stated that a "simplified annotation" the way you see it is the expression wrapped in a closure: function () { return php-expression; } So if an annotation is a function/closure that belongs to its target (class, property, function, method, ...) then why wouldn't there be a way to specify arguments? The RFC should give an answer to that. The statement you add doesn't make sense without examples. How does dependency injection even work, when you don't have a context? "Annotations that do require context should explicitly ask for that context - for example, you could use an anonymous function, a callable, or an anonymous class, to provide context via dependency injection." greetings Benjamin

Rasmus Schultz

10 years ago
Thank you for your comments! Benjamin, I have removed the comment about annotation expressions executing in an empty closure, since this example was clearly confusing, and I have tried to clarify the fact that annotation expressions execute in an empty scope. To be clear, annotation expressions are *not* closures, and they do not accept arguments - there is no reason to complicate the concept of annotations by introducing an implied scope, when you can introduce one explicitly, for example: class Validation { private $validate; public function __construct(callable $validate) { $this->validate = $validate; } public function validate($instance) { return call_user_func($this->validate, $instance); } } << new Validation(function (User $user) { return false !== filter_var($user->email, FILTER_VALIDATE_EMAIL); }) >> class User { public $email; } Of course, you could just as well do this with an interface - as it the case with most examples I can think of. If you can think of a meaningful annotation that actually requires context and can't be implemented in a better/safer/cleaner way with abstraction, please post? I'd like to add an example, but really have never seen one that can't be solved better without annotations. Marco,
> Love the simplicity of the RFC, but I can already see people doing terrible things with it
Every language lets you do terrible and great things - I don't believe in putting up roadblocks and complicating the language that prevent people from learning.
> TBH, I'd rather just allow a constant array (with constant expressions only), and that would be good enough :-)
I don't agree with the idea of crippling a language feature to prevent beginners from making mistakes - when doing so would also prevent static analysis and block experienced developers from doing useful things with objects. Davey and Mathieu, Annotations can be "named" by using classes. Per the RFC, defining or enforcing a rule about allowing only one instance of the same type, is not a feature. Hack's memoize is an annotation for the language interpreter itself - that's beyond the scope of this RFC, but could be implemented in the future. Your third question is answered by the RFC here:
> Annotation expressions are not evaluated until reflection is invoked, and are evaluated only once and internally memoized upon the first call to getAnnotations().
Thank you for your input so far! The RFC is 0.2 with the last updates based on your feedback - I would like to add a suitable case example for annotations that require context before I'd say this RFC is 1.0. If anyone can point at a real case example using Doctrine Annotations (or anything else) please do! Thanks, Rasmus On Sat, May 14, 2016 at 4:38 AM, Benjamin Eberlei <kontakt@beberlei.de> wrote:

Dan Ackroyd

10 years ago
On 14 May 2016 at 12:48, Rasmus Schultz <rasmus@mindplay.dk> wrote:
> > Hack's memoize is an annotation for the language interpreter itself - > that's beyond the scope of this RFC, but could be implemented in the > future.
Please can you add something to the RFC that reserves the possibility to do this, so that it can be introduced in a BC compatible way in the future? Hand-waving away problems and saying that they aren't in scope is a sure-fire way to build deficiencies into a design. This is not a smaller problem in userland code, as it is easy for people to migrate from one library to another, but is almost impossible to overcome in core PHP, where any BC-breaking change causes lots of difficultly for end-users. cheers Dan

Benjamin Eberlei

10 years ago
On Sat, May 14, 2016 at 1:48 PM, Rasmus Schultz <rasmus@mindplay.dk> wrote:
> Thank you for your comments! > > Benjamin, > > I have removed the comment about annotation expressions executing in > an empty closure, since this example was clearly confusing, and I have > tried to clarify the fact that annotation expressions execute in an > empty scope. To be clear, annotation expressions are *not* closures, > and they do not accept arguments - there is no reason to complicate > the concept of annotations by introducing an implied scope, when you > can introduce one explicitly, for example: > > class Validation { > private $validate; > > public function __construct(callable $validate) { > $this->validate = $validate; > } > > public function validate($instance) { > return call_user_func($this->validate, $instance); > } > } > > << new Validation(function (User $user) { return false !== > filter_var($user->email, FILTER_VALIDATE_EMAIL); }) >> > class User { > public $email; > } > > Of course, you could just as well do this with an interface - as it > the case with most examples I can think of. > > If you can think of a meaningful annotation that actually requires > context and can't be implemented in a better/safer/cleaner way with > abstraction, please post? I'd like to add an example, but really have > never seen one that can't be solved better without annotations. >
Every proposal before was about annotation/attributes as metadata for code. Your proposal makes them executable functions instead (nameless, attached to code blocks). I think this is just way out of scope of what the 80% want to achieve with this feature.

Rasmus Schultz

10 years ago
Dan, I've added a note about special annotations to the "future scope" section, naming the memoization-annotation as an example. Benjamin, I don't think you mean "out of scope" of what 80% want, I think you mean "beyond the scope"? I think this will definitely cover what 80% want to achieve, and possibly a lot more - possibly new and useful patterns that we can't even predict. On Sat, May 14, 2016 at 3:41 PM, Benjamin Eberlei <kontakt@beberlei.de> wrote:

Rowan Collins

10 years ago
On 14/05/2016 20:49, Rasmus Schultz wrote:
> Dan, > > I've added a note about special annotations to the "future scope" > section, naming the memoization-annotation as an example.
This doesn't really explain how such a feature would fit it into the proposal. For instance, how can we avoid the syntax clashing with existing userland issues? Given that annotations in this proposal don't have names, we can't just reserve all names beginning "__" or "php\". Perhaps they would be pseudo-function calls, like <<__memoize()>>? How would that (or any other undefined function) act if accessed on a version that didn't have the feature? I realise you don't want to define the details, but an idea of what an annotation targeting the engine would look like would make the future-proofing clearer. Regards,
-- Rowan Collins [IMSoP]

Rasmus Schultz

10 years ago
Rowan, I'm afraid I don't follow. Why is it a problem to simply add a function or class to the global namespace? << new Memoize() >> or maybe a class with factory functions for built-in annotations: << Meta::memoize() >> I tend to agree with Richard though, that system directives really ought to be supported by the language with keywords rather than annotations. Richard, Regarding your point of view:
> The problem is that an annotation by definition is just meta-data. Your > proposal however might result in fatal errors from meta-data
I agree that annotations are just meta-data. Values. I disagree that we can't permit the use of PHP expressions to generate those values. You can't eliminate problems such as missing classes - and you shouldn't. With the examples in the Attributes RFC, as well as with e.g. Doctrine Annotations, we have the exact same problems, they're just one step removed from the language feature, which only adds complexity and makes it harder to debug these things. The only work-around to an issue such as a missing class, is to complete ignore the annotation - which leads to undefined behavior and scenarios that are even harder to debug. The other big problem with this position, is that it goes more or less directly against what the community wants - the majority of those who use annotations in PHP today, use Doctrine Annotations, and they expect annotations as classes. Source code can contain errors. I don't think there's really any way around that. Even if you wittled down metadata to the absolute minimum - a string, such as what it is in Go code, that string is invariably going to contain something that needs to be parsed or interpreted somehow, somewhere, at some point, and it can lead to errors. In my opinion, it's better to allow those errors to be caught early - for example, being able to catch syntax errors in expressions already at load-time. The attributes RFC is much worse in this regard - you could be lugging around all kinds of dependency errors, missing classes and whatnot, and you'd never know about it until the day you hit one of those annotations. Now instead, you're just stuck having to type out redundant unit-tests to prove that every annotation is valid, when the language could have taken care of that in a much simpler way. The other issue is dependencies. The Attributes RFC will lead to code with all sorts of hidden dependencies - possibly dependencies that you can't even know about, because you can't know how your metadata is going to be interpreted precisely. You can't know which classes your metadata might get mapped to or how. Simple Annotations take care of that, and they force you to e.g. `composer require` the package that provides the annotation classes you're using - this is a good thing. Hiding dependencies makes it really difficult for others to reason about your code. The Attributes RFC will lead to a lot of hidden rules - whereas Simple Annotations are simple and straight-forward, nothing hidden or implicit, your dependencies visible and traceable, which will lead to much better tooling. For example, an IDE or offline code analysis tool would catch syntax errors and missing classes and warn about those right away. I'm sorry, but I think that the idea of meta-data that can't error somehow, somewhere, at some point, is completely unrealistic. And I'd much rather have a simple facility that enables those errors to surface quickly. On Mon, May 16, 2016 at 11:16 AM, Rowan Collins <rowan.collins@gmail.com> wrote:

Rowan Collins

10 years ago
On 16/05/2016 14:05, Rasmus Schultz wrote:
> I'm afraid I don't follow. Why is it a problem to simply add a > function or class to the global namespace? > > << new Memoize() >> > > or maybe a class with factory functions for built-in annotations: > > << Meta::memoize() >>
Well, those particular names look quite likely to collide with users' code, though I guess more obviously reserved names could be used. More generally, though, neither feels a natural fit, because of the need to specify a value, rather than a name. On Reflection, what would an instance of class Memoize look like? Could you call "new Memoize" somewhere else? If a memoize() function was executed, what value would it return? Since you can only see the evaluated value, how could you detect the annotation for debugging? Indeed, if we're interested only in values, not where they come from, are the following all equivalent, and the mechanics totally unndetectable by Reflection? <<null>> <<(function(){return null;})()>> function foo() { return null; } << foo() >> If the function form doesn't receive any context, its only purpose would be to copy some runtime global state into the annotation value on first access, which seems like a strange thing to do.
> I tend to agree with Richard though, that system directives really > ought to be supported by the language with keywords rather than > annotations.
That would be one way around it, indeed, so rather than "Future Scope", you could label "compiler directive" type annotations as "deliberately out of scope". Regards,
-- Rowan Collins [IMSoP]

Fleshgrinder

10 years ago
On 5/16/2016 3:05 PM, Rasmus Schultz wrote:
> I'm sorry, but I think that the idea of meta-data that can't error > somehow, somewhere, at some point, is completely unrealistic. > > And I'd much rather have a simple facility that enables those errors > to surface quickly. >
It should error and it should error exactly at the point when you want to use it and at no other point. Right now they will error anytime anyone wants to do anything with any annotation. I fully understand the urge to directly access objects and I actually support it and I want them to error out at the point where you try to instantiate them. However, not when I simply access annotations in introspection. According to your logic I have to load the whole dependency chain, even thought I just want to generate my documentation that might have some annotations in use. I also need to load all my dependencies even though I wanted to leave the security dependency out because I wanted to easily disable the security annotation system locally for development. I even have to load the whole dependency chain, even though I just want to introspect the single data structure at hand without invoking anything. Even worse if I am using a package that has Doctrine annotations but I use it without them: /fatal error/ Nice? No! Solutions? Make the /simple/ annotations /simple/. ;-) Only allow scalar data to be defined and allow userland to register themselves for specific annotations. I mentioned this before but somehow everyone wants something more fancy and I have no karma (even though requested but ignored) to write my own RFC. That being said, you have the ability to write an RFC so please just take it. :-) Some PHP code to illustrate what I was thinking of since I am better with code than with words. :-P https://gist.github.com/Fleshgrinder/d26cd4751827b8c10a0dc33da32b48c3 Reflection is the wrong tool for the job to build annotation systems. Reflection should not be used in production it should be used to introspect data structures and reason about them or other stuff during unit tests. However, reflection should provide the ability to read annotations, it just does not do anything with them by default and outputs stupid arrays. You will definitely understand what I mean it you follow the Gist. I am sure there is room for improvement and that I overlooked something in my Gist. But I think it is a starting point, some additional notes: - I do not see any benefit in annotations for parameters. - I do not see any benefit in annotations for Generators. - I do not see any benefit for annotations in php-src. My proposal would include to ban annotations from php-src and make it a pure userland functionality. This has many advantages: - No name collisions. - Clear policy for the future. - php-src features are never optional. - php-src does not provide meta-data. Let me know what you think or if you have questions in regards to the Gist that are unclear. (Or maybe you found mistakes.)
-- Richard "Fleshgrinder" Fussenegger

Rasmus Schultz

10 years ago
Richard, I believe I have already stated my position on all of these concerns in my last email. Short recap: There's no such thing as optional dependencies - if you depend on a Doctrine ORM annotation, you depend on that package. The code should (must) error the moment you try to instantiate those annotations - whether you intend to use them or not is besides the question - a missing class is an error, in annotations as it is anywhere else in your code. Silently ignoring missing annotations solves nothing - it just leads to errors that are even harder to find and debug. You seem awfully concerned about name collisions? Most packages use a vendor\package namespace convention now - PHP has always been adding things to the global namespace, and it's never been a really substantial problem. I'm not that concerned. The one new thing you brought up here, is the issue of potentially loading/instantiating a bunch of annotations that go unused. From a dependency point of view, I maintain that that's correct and it should error out on missing classes. From a performance point of view, you may have a valid concern, and this had occurred to me before - but I decided to leave it alone, for a couple of reasons. The most important reason being, look at how annotations get used in practice: controllers get things like route and filter annotations, all of which are usually needed at the same time; entities get ORM annotations, all of which are usually needed at the same time; form models get validation and input-type annotations, all of which are usually needed at the same time. And so on. The fact of the matter is that classes tend to have a single responsibility, and this usually means that applicable annotations tend to belong to a specific domain, and are needed at the same time. In other words, this problem is pretty theoretical - in reality, it's a very small problem, which, if somebody was very concerned about, could absolutely be solved in userland, by adding a cache layer. Which projects like Doctrine Annotations would likely do anyway, in order to cache the result of mode complex annotation logic to control inheritance, cardinality, applicability, etc. - all the complex stuff that this RFC stays away from. The extreme case of what you're proposing, is what Go does - an annotation is simply a string. No chance of any error, anywhere, ever, right? Wrong. People put JSON data in those strings for example, and parsing that data leads to run-time errors instead. That's the extreme example, but the problem with what you're proposing is precisely the same: you allow something like nested array data structures, those are still going to get interpreted and mapped to objects at run-time, and it leads to run-time errors instead. I maintain that the majority use-case is object annotations - and that deferring the construction of those objects doesn't solve anything. You're just deferring or hiding the problem, adding complexity, and mental as well as practical debugging effort - but the problem doesn't go away. I am and will always be in favor of language design that takes the most direct route to satisfy a requirement. In this case, the requirement is annotations as objects - and the most natural and direct route is the "new" keyword, static method calls, or whatever way you normally create objects; inventing a more indirect way to do that just adds complexity. On Mon, May 16, 2016 at 8:39 PM, Fleshgrinder <php@fleshgrinder.com> wrote:

Rasmus Schultz

10 years ago
I'd like to finish this RFC, but I have two remaining issues. The syntax is a minor issue - since any valid PHP expression can be used, the bit-shift ambiguity is technically an issue, however marginal. A lot of people commented on the syntax when I posted the RFC on reddit - they don't like it. Anyone have any ideas for an alternative syntax? It needs to be delimited, e.g. needs to use opening and closing delimiters or a recognizable opening delimiter... Here's some ideas: @{ new Table("user") } @[ new Table("user") ] {{ new Table("user") }} +{ new Table("user") } I don't like any of these really, but the bit-shift operator isn't going when the stuff inside is an expression which could include bit-shift (is it?) The other issue is the dependency (context) injection example - no one seems to be able to cite an actual use-case, and if that's the case, I should probably just remove it from the RFC entirely? @Larry can you think of a case example in the myriad annotations you've seen in Drupal code? :-) Anyone else using Doctrine Annotations actually seen anyone making use of a closure in an annotation? On Tue, May 17, 2016 at 11:06 AM, Rasmus Schultz <rasmus@mindplay.dk> wrote:

Fleshgrinder

10 years ago
On 5/17/2016 11:06 AM, Rasmus Schultz wrote:
> I believe I have already stated my position on all of these concerns > in my last email. >
Sorry if I cannot keep everything in my mind. On 5/17/2016 11:06 AM, Rasmus Schultz wrote:
> There's no such thing as optional dependencies - if you depend on a > Doctrine ORM annotation, you depend on that package. >
Both statements are not true. #1 A good example of an optional dependency can be found in the Symfony console package: https://github.com/symfony/console/blob/master/composer.json#L28 https://github.com/symfony/console/blob/master/Application.php#L814-L816 #2 I could have code in a third-party package that provides some default annotations which suggests me to install some annotation systems that are capable of handling exactly those annotations. However, I decide to only use one of the suggested annotation systems in my project. With your solution this is not possible because it always ends up in fatal errors; although I consciously decided to not install one suggested dependency. See #1 for such an example. The only solution for the maintainer of the third-party package would be to provide multiple versions of the same package: 1. No annotations. 2. Annotations from System X. 3. Annotations from System Y. 4. Annotations from System X and Y. 5. ... This is not an acceptable approach. On 5/17/2016 11:06 AM, Rasmus Schultz wrote:
> The code should (must) error the moment you try to instantiate those > annotations - whether you intend to use them or not is besides the > question - a missing class is an error, in annotations as it is > anywhere else in your code. >
It is not (again see #1) a problem at compile time and always at runtime. You can try this easily on your console: $ php -r 'if (false) { new NonExistingClass; }' $ php -r 'if (true) { new NonExistingClass; }' Fatal error: Uncaught Error: Class 'NonExistingClass' not found in Command line code:1 Stack trace: #0 {main} thrown in Command line code on line 1 On 5/17/2016 11:06 AM, Rasmus Schultz wrote:
> Silently ignoring missing annotations solves nothing - it just leads > to errors that are even harder to find and debug. >
My proposal errors as soon as you try to retrieve an annotation with the annotation reader which was not previously registered as such. However, you can use Reflection and introspect any existing annotation without any kind of error. That is how it should be! On 5/17/2016 11:06 AM, Rasmus Schultz wrote:
> You seem awfully concerned about name collisions? >
No, I am not. It is just a nice side product of my proposal. On 5/17/2016 11:06 AM, Rasmus Schultz wrote:
> The one new thing you brought up here, is the issue of potentially > loading/instantiating a bunch of annotations that go unused. [...] > From a performance point of view [...] >
The problem is parsing and interpreting them although they might not be used because the current path of logic will not go through them at all. This is not a micro optimization thing. You just don't want to interact with annotations at all until they are actually needed. This is true for any kind of PHP code. See console commands above. On 5/17/2016 11:06 AM, Rasmus Schultz wrote:
> The extreme case of what you're proposing, is what Go does - an > annotation is simply a string. No chance of any error, anywhere, ever, > right? Wrong. People put JSON data in those strings for example, and > parsing that data leads to run-time errors instead. That's the extreme > example, but the problem with what you're proposing is precisely the > same: you allow something like nested array data structures, those are > still going to get interpreted and mapped to objects at run-time, and > it leads to run-time errors instead. >
Runtime errors for a runtime feature, well, yes. Again, see console command example above. @someAnnotation('{"json": "data"}') function f(); @someAnnotation('invalid json') function x(); $reflector = new ReflectionFunction('f'); $annotations = $reflector->getAnnotations(); /* $annotations = [ 'someAnnotation' => [ ['{"json": "data"}'], ], ]; */ $reflector = new ReflectionFunction('x'); $annotations = $reflector->getAnnotations(); /* $annotations = [ 'someAnnotation' => [ ['invalid json'], ], ]; */ annotation_register('someAnnotation', function (string $json) { $decoded_json = json_decode($json); if ($decoded_json === false) { throw new JsonParseException(json_last_error_msg()); } return $decoded_json; }); $annotations = new AnnotatedFunction('f'); $some_annotations = $annotations->getAnnotations('someAnnotations'); echo $some_annotations[0]->json; // data $annotations = new AnnotatedFunction('x'); $some_annotations = $annotations->getAnnotations('someAnnotations'); // Fatal error: Uncaught JsonParseException in ... The above is just fine. The annotation library registered the callback and it knows what kind of errors can happen. The annotation library is also the one reading the annotations and it can therefore easily handle those errors and act upon them. Of course in the above case the fatal error is just fine and it is the developers duty to write unit tests and take care of writing proper configuration. It is not PHP's duty to ensure to absolutely no error can happen in any subsystem. (This is actually ridiculous to think of in the first place.) On 5/17/2016 11:06 AM, Rasmus Schultz wrote:
> I maintain that the majority use-case is object annotations - and that > deferring the construction of those objects doesn't solve anything. >
Seriously? Every construction is deferred, always, again see the console example from above. On 5/17/2016 11:06 AM, Rasmus Schultz wrote:
> You're just deferring or hiding the problem, adding complexity, and > mental as well as practical debugging effort - but the problem doesn't > go away. >
I am putting a runtime feature to runtime like all runtime features are at runtime in PHP. The error is hidden during reflection, that is true, but on purpose! You do not want reflection to error out while you are performing introspection on a data structure. The annotation reader however will error out in the moment you try to access something. This separation of concern makes it actually easier and is in the vain of good software development too. I understand what you want to solve. I am just telling you that this is the wrong approach and others are telling it to you too.
-- Richard "Fleshgrinder" Fussenegger

Fleshgrinder

10 years ago
On 5/13/2016 2:11 PM, Rasmus Schultz wrote:
> Dear Internals, > > I'm announcing a simplified RFC for annotations: > > https://wiki.php.net/rfc/simple-annotations >
-1 again, I am sorry. The problem is that an annotation by definition is just meta-data. Your proposal however might result in fatal errors from meta-data: $reflector = new ReflectionClass(User::class); $reflector->getAnnotations(); // Fatal error: Uncaught Error: Class 'TableName' not found ... This is an absolute No-Go for meta-data.
-- Richard "Fleshgrinder" Fussenegger

Rasmus Schultz

10 years ago
Tonight I started trying to write a proposal for even more simplified annotations, e.g. something simple enough that it cannot result in fatal errors. I eventually gave up, and here's why. Essentially, if you have to restrict yourself to things that cannot under any circumstances error out, the only things you can use are constant scalar values - even constant scalar expressions could not be allowed, because even those could error when CONST_NAME or Class::CONST_NAME aren't defined. So, in a nutshell, what you're saying (Richard) is that annotations can only consist of scalar values, and possibly arrays of values? (and I don't even see your gist doing that - it's just invoking closures, as far as I can tell, and any of those could error, or not?) So, regardless of syntax, literally the only annotation data allowed would be blatantly simple things like: ["max_length" => 20] ["data_type" => "int"] Or in other words, constant arrays, int, string, float and bool values. Nothing else. Nothing can depend on any kind of external symbol (class names, constants, etc.) since any such reference can cause an error. No static analysis or type-checking of any kind can ever be performed - no assertions or declarations can be made about the shape or types of metadata, at all. The problem is, that's simply not useful. It's also not very different from what we have today with php-doc blocks - the only real difference is we can check array nesting and int, string, float and bool syntax, e.g. nothing really useful or important. In other words, this would be a very slight improvement on syntax - nothing that actually amounts to any kind of functionality. If we were to ship a feature like that, what would happen is precisely the same thing that happened with php-doc blocks: the real work will be done in userland (and likely dominated by one library), there will be no IDE support (except proprietary support for that one dominating library), no useful offline static analysis tools. At the end of the day, what we end up with will have all of the same issues we have with php-doc blocks and existing libraries. I don't understand how such a feature would be useful to anybody? I especially don't understand why you want a language feature that isn't allowed to reference external symbols - e.g. isn't allowed to have any kind of relationship with anything declared in userland. It seems to me a peculiar, arbitrary and unrealistic restriction to put on annotations. It's going to have no immediate usefulness, and mapping the data to objects is going to happen in userland, just as it does today. I'd like to work on this proposal, but I feel that you're giving me nothing to work with here. On Mon, May 16, 2016 at 12:38 PM, Fleshgrinder <php@fleshgrinder.com> wrote:

Marco Pivetta

10 years ago
Hi Rasmus, On 27 June 2016 at 20:37, Rasmus Schultz <rasmus@mindplay.dk> wrote:
> Tonight I started trying to write a proposal for even more simplified > annotations, e.g. something simple enough that it cannot result in > fatal errors. I eventually gave up, and here's why. > > Essentially, if you have to restrict yourself to things that cannot > under any circumstances error out, the only things you can use are > constant scalar values - even constant scalar expressions could not be > allowed, because even those could error when CONST_NAME or > Class::CONST_NAME aren't defined. > > So, in a nutshell, what you're saying (Richard) is that annotations > can only consist of scalar values, and possibly arrays of values? (and > I don't even see your gist doing that - it's just invoking closures, > as far as I can tell, and any of those could error, or not?) > > So, regardless of syntax, literally the only annotation data allowed > would be blatantly simple things like: > > ["max_length" => 20] > > ["data_type" => "int"] > > Or in other words, constant arrays, int, string, float and bool > values. Nothing else. > > Nothing can depend on any kind of external symbol (class names, > constants, etc.) since any such reference can cause an error. > > No static analysis or type-checking of any kind can ever be performed > - no assertions or declarations can be made about the shape or types > of metadata, at all. > > The problem is, that's simply not useful. >
That's actually what doctrine/annotations has been for a while tho, and a lot of people rely just on that. The data-structure is still enforced by a VO-style class (the annotation itself) in the library, though. It would still be very useful, in my opinion. Note that the existing annotation libs out there do indeed trigger autoloading for referenced class constants. Marco Pivetta http://twitter.com/Ocramius http://ocramius.github.com/

Pedro Cordeiro

10 years ago
I completely agree with Marco. Throwing class-loading errors for value-object classes is alright. For logic-parsing inside annotations, I don't feel any need for it. Metadata should be static, IMO, but it could very well be (and should be) value-objects.
>At the end of the day, what we end up with will have all of the same >issues we have with php-doc blocks and existing libraries. > >I don't understand how such a feature would be useful to anybody?
As an addition, I don't think this feature should ADD new functionality to those already present on DocBlocks. I just feel like docblocks are an ugly hack for the lack of native annotations. Pasting from a previous discussion in this same list, Rowan and I were discussing whether to add a native docblock-annotation-retrieval feature or a native annotation syntax: 1) It might not be objectively bad to add this feature in docblocks, but it will be objectively wrong to keep calling them "DocBlocks" if they are no longer documentation-only blocks. 2) Even though PHP already treats docblocks differently from comments, that's not the common view on userland, nor is explained on the manuals. There are no separate entries to explain docblocks, and no mention to them on the "Comments" page. The Reflection method to retrieve DocBlocks is "getDocCOMMENT", which suggests they are comment that do not affect runtime behaviour. We'd have to update the comments page to say "'/*' starts a comment, unless if they're immediately followed by another asterisk ('/**'), in which case it may or may not be a comment, depends on the following token". It's very confusing. 3) To make this work within docblocks, we'd have to get rid of at least one configuration setting (opcode.save_comments). 4) Dockblocks are stripped from obfuscators and transpilers, which are a part of the ecosystem and do affect users, even if they are not first-class citizens here. Annotations in dockblocks are hard to understand because they they may or may not be runtime-affecting annotations and there is no way to tell. I hate this comparison with docblock annotations, because they are an UGLY HACK. It works wonderfully, yes, but so do global variables, singletons and what-not, and we are not using them anymore, are we? Adding this very same feature that already exists in userland as a dedicated syntax would fix a lot of issues, cited above. There is no need to evaluate logic inside annotations, let them be value-objects, let it throw errors autoloading said objects, and let's get this rolling. Please, don't embargo this ONCE again. It's a very much needed feature, and people want it! There have been innumerous discussions on reddit, and almost everyone agrees annotations are a missing feature and comment-annotations are an ugly hack for the lack of native syntax. Please, make this happen. 2016-06-28 11:12 GMT-03:00 Marco Pivetta <ocramius@gmail.com>:

Rasmus Schultz

10 years ago
Note that I only make comparisons with doc-blocks to point out the fact that, functionally, they would not be very different from an annotation feature that solely permits you to annotate with values. A doc-block is a string, which is a value - an array/int/float/string/bool is a value. It's only very slightly better than a bare string, in the sense that neither doc-blocks nor simple values permit any kind of definition of which annotations are actually available, hence offer no means of validation of that data (except at run-time by a userland facility), provides no support for static analysis, no IDE support, no clear indication of real dependencies, and so on. Since the discussion isn't completely dead yet (thank you Pedro and Marco), I will just briefly explain what I had in mind for another RFC. This would be largely based on my previous proposal: https://wiki.php.net/rfc/simple-annotations But with the following differences. The syntax would no longer allow arbitrary expressions - instead, it would allow object annotations only, declared in one of three ways: << RequiresLogin >> // equivalent to new RequiresLogin() << Length(20) >> // equivalent to new Length(20) << HttpMethod::post() >> // equivalent to static method-call HttpMethod::post() The latter permits you to declare and call static factory methods, e.g. "named constructors" for some types of annotations. Annotations are now consistently objects, which simplifies filtering annotations, etc. This declaration syntax enables better in-language analysis - because the declarations always include the annotation class-name, this means it can now support deferred as well as conditional creation, by having a slightly different reflection API: $annotations = $class_reflection->getAnnotations(); At this point, we have a list of ReflectionAnnotation objects - the actual annotation objects have *not* been created at this point, which would allow for conditional evaluation, such as: foreach ($annotations as $annotation) { echo $annotation->className; // => "Length" etc. if ($annotation->classExists()) { var_dump($annotation->getInstance()); // => Length object, etc. } else { var_dump($annotation->getInstance()); // => UnknownAnnotation object } } The UnknownAnnotation object has three properties: class-name, method-name ("__construct" if constructor was used) and the list of arguments. In other words, this would provide support for "optional dependencies", in much the same way that unserialize() does it - the getInstance() method will trigger autoloading, and if the class is unavailable, it takes this "typeless" information and puts it in a general object, so that the information itself isn't lost. It may *seem* like this solves the optional dependency problem, but let me be clear, it does *not* fully address that issue, because something like << Method(Http::GET) >> could still fail, if the Method class is defined, but the Http class is not. That's a much more marginal case, but it's not completely unlikely that you would use constants from one dependency and pass them to annotation classes from another dependency, e.g. if the HTTP method constants were part of an HTTP package, and the annotation itself were part of a different package. Arguably it's much less likely to occur though - if you have the annotation package installed, most likely you have the dependent package of that package installed as well. So this is much safer, but just to be clear, the only way you can make it any safer than that, is by completely disallowing anything but values as arguments to annotation constructors - which, in my opinion, simply isn't useful. Alternatively to the above, you could imagine a simpler reflection facility that does eagerly construct annotations, but constructs UnknownAnnotation instances for missing classes - this would perhaps balance more towards immediate usefulness and less towards performance perfectionism, as it increases the chance of loading an unused annotation. This would be my preference though - as previously argued, the odds of having a large number of unrelated annotations on the same member are extremely low in practice; an annotated entity tends to be a persisted property, a form input element, or an action method, etc... rarely do you mix annotations from different domains onto the same member; annotations that are applicable to a given domain are usually relevant to a consumer belonging to the same (or a closely related) domain, so this performance concern is mostly a theoretical micro optimization. For another, eager construction means we can filter annotations with instanceof rather than by class-name, which is much powerful, enabling you to leverage inheritance - for example, you'd be able to ask directly for all instances of ValidationAnnotation and get those with a single call, which is incredibly useful. Anyways, let me know your feelings about that idea? If there's any interest, I'd be happy to write yet another RFC based on that. On Tue, Jun 28, 2016 at 4:30 PM, Pedro Cordeiro <pedronaroga@gmail.com> wrote:

Rowan Collins

10 years ago
Hi Rasmus, I think you may be onto something here... :) On 28/06/2016 17:31, Rasmus Schultz wrote:
> The UnknownAnnotation object has three properties: class-name, > method-name ("__construct" if constructor was used) and the list of > arguments.
I'm not sure we need an extra UnknownAnnotation class. It seems like all of that information could be available on the ReflectionAnnotation object, so you can get it without the class being available if you want to. If the idea is to lazily-evaluate the arguments to the annotation, that could be handled by storing the AST and only evaluating them when you call either $annotation->getInstance() or $annotation->getArguments(): use MyAnnotations\FooAnnotation as Foo; // Class alias resolved at compile time as normal << Foo(time()) >> class A {} $class_reflection = new ReflectionClass('A'); $annotations = $class_reflection->getAnnotations(); foreach ($annotations as $annotation) { if ( $annotation->getClassName() == 'MyAnnotations\\FooAnnotation' ) { var_dump( $annotation->getArguments() ); // `time()` evaluated here var_dump( $annotation->getInstance() ); // `new MyAnnotations\FooAnnotation(time())` evaluated here } } It's not the responsibility of the ReflectionAnnotation class to worry about calling the autoloader, issuing errors, etc, it should just delegate that to the engine like any other constructor call. There's no need for an extra $annotation->classExists() method, either, because you can just say "class_exists($annotation->getClassName())". Interestingly, this same delayed evaluation of arguments opens us up to include direct access to the AST as some (Nikita?) have asked for: // Constructor argument can be any valid expression, // however nonsensical it would be to evaluate it directly << Pre($a >= 42) >> function foo($a) {} $r = new ReflectionFunction('foo'); $annotations = $r->getAnnotations(); foreach ( $annotations as $annotation ) { if ( $annotation->getClassName() == 'Pre' ) { $ast = $annotation->getArgumentAST(); // Now we can do funky things with the AST, like evaluating it in a custom context :) } } Regards,
-- Rowan Collins [IMSoP]

Fleshgrinder

10 years ago
On 6/28/2016 6:31 PM, Rasmus Schultz wrote:
> Since the discussion isn't completely dead yet (thank you Pedro and > Marco), I will just briefly explain what I had in mind for another > RFC. >
I want to see this feature happen too but the right way, so let us just spend some more time wrapping our heads around it. We could also discuss and work on the RFC at a different place (GitHub with issues?), since many people are always complaining about too many emails that are sent to this ML. On 6/28/2016 6:31 PM, Rasmus Schultz wrote:
> The syntax would no longer allow arbitrary expressions - instead, it > would allow object annotations only, declared in one of three ways: > > << RequiresLogin >> // equivalent to new RequiresLogin() > > << Length(20) >> // equivalent to new Length(20) > > << HttpMethod::post() >> // equivalent to static method-call HttpMethod::post() > > The latter permits you to declare and call static factory methods, > e.g. "named constructors" for some types of annotations. > > Annotations are now consistently objects, which simplifies filtering > annotations, etc. > > This declaration syntax enables better in-language analysis - because > the declarations always include the annotation class-name, this means > it can now support deferred as well as conditional creation, by having > a slightly different reflection API: > > $annotations = $class_reflection->getAnnotations(); > > At this point, we have a list of ReflectionAnnotation objects - the > actual annotation objects have *not* been created at this point, which > would allow for conditional evaluation, such as: > > foreach ($annotations as $annotation) { > echo $annotation->className; // => "Length" etc. > > if ($annotation->classExists()) { > var_dump($annotation->getInstance()); // => Length object, etc. > } else { > var_dump($annotation->getInstance()); // => UnknownAnnotation object > } > } > > The UnknownAnnotation object has three properties: class-name, > method-name ("__construct" if constructor was used) and the list of > arguments. > > In other words, this would provide support for "optional > dependencies", in much the same way that unserialize() does it - the > getInstance() method will trigger autoloading, and if the class is > unavailable, it takes this "typeless" information and puts it in a > general object, so that the information itself isn't lost. >
Awesome! This is exactly the kind of thing that one wants from reflection, information and not exceptions or worse. :) On 6/28/2016 6:31 PM, Rasmus Schultz wrote:
> It may *seem* like this solves the optional dependency problem, but > let me be clear, it does *not* fully address that issue, because > something like << Method(Http::GET) >> could still fail, if the Method > class is defined, but the Http class is not. That's a much more > marginal case, but it's not completely unlikely that you would use > constants from one dependency and pass them to annotation classes from > another dependency, e.g. if the HTTP method constants were part of an > HTTP package, and the annotation itself were part of a different > package. Arguably it's much less likely to occur though - if you have > the annotation package installed, most likely you have the dependent > package of that package installed as well. > > So this is much safer, but just to be clear, the only way you can make > it any safer than that, is by completely disallowing anything but > values as arguments to annotation constructors - which, in my opinion, > simply isn't useful. >
I would argue that this should fail if the Method class from the example exists and is actually being created. However, if the Method class does not exist (and we construct an UnknownAnnotation stub for it) nothing should go wrong. I mean, the assumption that the dependency of a missing dependency is/might be missing sounds logical too me. Of course we would need to have something like an UnknownAnnotationArgument stub too. Note that this allows you in consequence to create other kinds of objects within annotations: << Annotation(new Argument) >> final class {} This would allow nesting as some DocBlock libraries allow it currently. On 6/28/2016 6:31 PM, Rasmus Schultz wrote:
> Alternatively to the above, you could imagine a simpler reflection > facility that does eagerly construct annotations, but constructs > UnknownAnnotation instances for missing classes - this would perhaps > balance more towards immediate usefulness and less towards performance > perfectionism, as it increases the chance of loading an unused > annotation. > > This would be my preference though - as previously argued, the odds of > having a large number of unrelated annotations on the same member are > extremely low in practice; an annotated entity tends to be a persisted > property, a form input element, or an action method, etc... rarely do > you mix annotations from different domains onto the same member; > annotations that are applicable to a given domain are usually relevant > to a consumer belonging to the same (or a closely related) domain, so > this performance concern is mostly a theoretical micro optimization. > > For another, eager construction means we can filter annotations with > instanceof rather than by class-name, which is much powerful, enabling > you to leverage inheritance - for example, you'd be able to ask > directly for all instances of ValidationAnnotation and get those with > a single call, which is incredibly useful. >
Isn't it possible to go for a generator (yield) like approach and combine the best of both worlds? That being said, I am in favor of this approach as well, because, as you said, instanceof is more powerful and one expects all annotations to be present, the UnknownAnnotation would be a special case. On 6/28/2016 6:31 PM, Rasmus Schultz wrote:
> Anyways, let me know your feelings about that idea? If there's any > interest, I'd be happy to write yet another RFC based on that. >
+1 with one thing, go for the @ and not <<>>. It is possible with the parser and so far most people stated that they prefer it, or put it to vote with a separate poll. @Route('/blog/{id}', new Method(Http::GET)) @ParamConverter('post', Post::class) public function indexAction(Post $post) {}
-- Richard "Fleshgrinder" Fussenegger

Rowan Collins

10 years ago
On 28/06/2016 17:31, Rasmus Schultz wrote:
> For another, eager construction means we can filter annotations with > instanceof rather than by class-name, which is much powerful, enabling > you to leverage inheritance - for example, you'd be able to ask > directly for all instances of ValidationAnnotation and get those with > a single call, which is incredibly useful.
Sorry for the extra mail with only one point, but I missed this before. This can be achieved using the fully-qualified class-name by using is_a() or is_subclass_of(). This obviously needs to trigger the autoloader anyway, but can still avoid actually instantiating the class. (And thus, per my other mail, evaluating the args.) I think lazy construction (without magic stub classes) gives the best of both worlds here.

Rasmus Schultz

10 years ago
Thanks, Richard and Rowan - this gives me more to work with! I will work on a new proposal and ask for more input when I have a draft ready :-) On Tue, Jun 28, 2016 at 7:56 PM, Rowan Collins <rowan.collins@gmail.com> wrote:

Pedro Cordeiro

10 years ago
> << HttpMethod::post() >> // equivalent to static method-call > HttpMethod::post() >
What would this, specifically, do? Would it call HttpMethod::post() when the class gets instantiated and return its return on a ReflectionClass::getAnnotations()? Why is it necessary to be able to evaluate expressions on annotations? I do agree that throwing errors/exceptions when classes are not found is needed (otherwise we wont be able to use value-objects), but evaluating expressions seems a little crazy for me... why should metadata be computed in runtime?

Rowan Collins

10 years ago
On 28 June 2016 20:10:15 GMT+01:00, Pedro Cordeiro <pedronaroga@gmail.com> wrote:
>> << HttpMethod::post() >> // equivalent to static method-call >> HttpMethod::post() >> > >What would this, specifically, do? Would it call HttpMethod::post() >when >the class gets instantiated and return its return on a >ReflectionClass::getAnnotations()? Why is it necessary to be able to >evaluate expressions on annotations?
I'm not sure if it's definitely necessary, but the stated use case was for factories / named constructors. PHP only has one constructor per class, so it's not uncommon to have static createFromX methods. Since this will eventually evaluate as "new Foo(42)": << Foo(42) >> The idea is that this would be available to call a factory instead: << Foo::fromRoman('XLII') >> The neat thing being that by limiting the options to this, you can still extract a class name for every annotation. I say "eventually", because as proposed, this won't actually happen until you explicitly ask for the "instance" of the annotation. See previous examples. Regards,
-- Rowan Collins [IMSoP]

Rasmus Schultz

10 years ago
This was just an example of a value object - for example: class HttpMethod { private $method; protected function __construct($method) { $this->method = $method; } public function getMethod() { return $this->method; } public static function get() { return new self("GET"); } public static function post() { return new self("POST"); } // ... } You could picture using a flyweight constructor inside those factory methods maybe, and probably other patterns... I think there's plenty of cases for "named constructors", this is just the PHP variety of that. There are also cases (such as this one) where only certain constructions are permitted - allowing any string for HTTP method (e.g. wrong names, wrong case etc.) is prevented by protecting the constructor... On Tue, Jun 28, 2016 at 9:10 PM, Pedro Cordeiro <pedronaroga@gmail.com> wrote:

Larry Garfield

10 years ago
On 06/28/2016 04:24 PM, Rasmus Schultz wrote:
> This was just an example of a value object - for example: > > class HttpMethod { > private $method; > > protected function __construct($method) { > $this->method = $method; > } > > public function getMethod() { > return $this->method; > } > > public static function get() { > return new self("GET"); > } > > public static function post() { > return new self("POST"); > } > > // ... > } > > You could picture using a flyweight constructor inside those factory > methods maybe, and probably other patterns... I think there's plenty > of cases for "named constructors", this is just the PHP variety of > that. There are also cases (such as this one) where only certain > constructions are permitted - allowing any string for HTTP method > (e.g. wrong names, wrong case etc.) is prevented by protecting the > constructor...
A possibly uglier but useful case would be where you have a large number of properties in an annotation and want to pass them in by name, but not using an anonymous array. (See also, my earlier Drupal examples.) To wit: <<Node::definition()>> class Node { public static function definition() { $def = new NodeDefinition(); $def->name = "node"; $def->label = "A Node"; $def->addLink('foo', 'bar'); // ... return $def; } // The rest of the node class here. } $annotations = $class_reflection->getAnnotations(); print get_class($annotations[0]); // prints "NodeDefinition". Or would that fail because it's not returning a self instance, but of another class? Rasmus, let me know when you have a proposal together and I'll try to dig up time to try rendering Drupal annotations in it again. :-) --Larry Garfield