So this thread kind of died, apparently mostly due to some concerns about
possible side-effects.

I would just like to point out one thing: when it comes to
metadata/annotations, whatever you come up with, even if it's something
that allows only constant expressions and simple data-structures, as
opposed to classes, what's going to happen - like, immediately - is someone
will figure out how to map it to classes.

The thing is that, even if annotations are simple data attached to
source-code elements, someone is going to need/want things like range and
type validations for those annotations and their attributes - essentially
the only way to get that, is with code, and the most obvious way to do
that, is map annotations to classes, which is basically what every userland
annotation project is doing.

I maintain the position that simply allowing arbitrary expressions as
annotations is the simplest, most direct way to this feature.

Whatever you come up with that is more restrictive, someone will simply
figure out how to map that to the thing they really wanted, which is
objects - and then you have all the same problems and risks of
global/mutable state, side-effects, etc.

Anything directly or indirectly executable is going to allow you to create
those problems - presently that is true everywhere in PHP, literally for
everything, including files, classes, functions, even expressions can have
side-effects.

Inventing a more restrictive form of annotations will only make the road
longer and more confusing - it won't remove those risks or solve those
problems. The only real solution to those problems, is teach people how to
program.

Another concern was about optional dependency on classes for annotations.
Again, that's going to happen when people figure out how to map a more
restrictive feature to classes - and in either case, if you could somehow
ignore annotations that fail to initialize because of a missing dependency,
that would be a very bad idea; quietly ignoring a missing class would
create very strange and difficult debugging situations.

I think that this problem is exaggerated. Needing to include the
annotations you actually use is hardly a roadblock to anything. Leaving out
dependencies would be as strange to me as allowing the extends or
implements keyword to ignore a missing interface or base class. I simply
don't see the usefulness, and can only see that turning into a real
debugging nightmare.

I think that, given how simple this proposed feature really is, it's really
not likely to generate a lot of surprises for anyone - I mean, we're used
to the fact that new Foo() fails if the class Foo does not exist. In fact,
it would be a lot more surprising if you had to explain why that wasn't the
case. We're also used to the fact that such expressions fail not when
they're loaded, but when they're evaluated - like when you run a method.
That's not surprising at all.

I think that this feature is really very simple, in every sense of the word
- it's easy to explain, there are very few rules and no real surprises, it
just basically lets you insert a list of values or objects defined using
simple everyday PHP expressions, which behave, for better or for worse,
like they do every day.

I wish you wouldn't shoot down this feature on the basis of some missing
language qualities you seem to be seeking - if PHP someday supports
immutable objects, or functions without side-effects, annotations would
immediately support those too. At the moment, it does not, and I don't
believe it's going to be even the least bit surprising that annotations
don't have those language qualities either.

Nor do I think people are going to do "evil" with it - I think that for
example Symfony annotations, or annotations in C#, have all of the same
problems, just like any language with global and mutable state, and people
are quite happy and productive using them anyway.

I think that there is real value to having a very simple feature that
leverages the language as-is instead of inventing new, special syntax,
restrictions or limitations, and I'd be much less worried about "evil"
somebody might do with that, and more excited to see the interesting and
clever things people would come up with.

It seemed like there was some initial excitement about this idea.

I'm not opposed to developing the idea further, if there's any interest.
For example, one possible idea would be to allow object constructions only:

    class User {
        << Length(20) >>
        public $email;
    }

This creates a limitation, but covers the most popular use-case of object
annotations, and that limitation would enable a reflection reader along the
lines of:

    $annotations =
$reflection->getProperty("email")->getAnnotations(User::class);

This differs from what I previously proposed, since you are now required to
ask specifically for one type of annotation, which means you can have
"optional" annotations - just check first, e.g. if
(class_exists(Length::class)) { ... } and only the annotations you
explicitly ask for get initialized.

Avoiding early evaluation of unused annotations in this way also comes with
a new limitation though - you can no longer ask for all
ValidationAnnotation objects, for example, and get all your different
sub-classes of those annotations. Which means you need a different design
approach for that, e.g. using composition instead of inheritance:

    class User {
        << Validate(new EmailValidator()) >>
        public $email;
    }

You can still make it work, but in a kind of round-about way compared to my
previous proposal.

The downside is this requires more explanation to make use of this
mechanism.

To upshot is composition over inheritance, which is actually a good thing -
although you could have done that with the original proposal too.

I don't really see the introduction of this limitation as solving a real
problem, I'm just putting it out there to get some discussion going.

I'm open to ideas.

Any thoughts?


On Tue, May 17, 2016 at 10:50 PM, Rasmus Schultz <ras...@mindplay.dk> wrote:

> Annotations-based validation frameworks typically have
> property-annotations for property-validations - if your validation
> depends on more than one property, that's object validation, which is
> typically done via an interface rather than by applying an annotation.
>
> Annotations are not typically the only means you have of doing a
> single thing - generally, they should provide meta-data, not
> implementations; a validation facility can then consume this metadata
> and establish which validations are applicable.
>
> For example, rather than a LengthValidation annotation containing an
> implementation, you're probably better off with a Length annotation,
> which is just meta-data, free of intent - a validation facility can
> use it to apply a validation, a form renderer can apply a
> maxlength-attribute to an input-element, an object/relational-mapper
> can verify the range constraint and throw a RangeException, a
> schema-generator can use it to establish the size of a VARCHAR, and so
> on.
>
> This is far more powerful than specialized annotations that only serve
> one purpose and have a baked-in implementation.
>
> Consider this opposing example:
>
> << Validation\RangeValidation(8,20) >>
> << ORM\Field\VarChar(20) >>
> << Forms\Input\MaxLength(20) >>
> public $first_name;
>
> You can see where this is going...
>
> Anyways, the point is, I really don't know why you'd need context - or
> how you'd even establish context when there isn't one... when there's
> no object instance yet, then what?
>
> Show me another programming language where annotations have access to
> context?
>
>
> On Tue, May 17, 2016 at 8:37 PM, Rowan Collins <rowan.coll...@gmail.com>
> wrote:
> > On 17/05/2016 18:47, Rasmus Schultz wrote:
> >>>
> >>> I don't really understand what closures have to do with annotations, or
> >>> how that relates to capturing context.
> >>
> >>
> >> The point is that, rather than trying to capture context, you can just
> >> as well ask for what you need.
> >>
> >> I'm going to use a very contrived example, because I really can't
> >> think of any real case that requires context:
> >>
> >> class User
> >> {
> >>     << new Validation(function ($value) { return false !==
> >> filter_var($value, FILTER_VALIDATE_EMAIL); }) >>
> >>     public $email;
> >> }
> >>
> >> Like I said, extremely contrived - in practice, you wouldn't need to
> >> attach functionality in that way.
> >
> >
> > Right, in my mind this is less about *context*, and more about *passing
> code
> > into the implementation*. I think we agree that an implementation of
> > validation-by-annotation would look something like:
> >
> > $validators = $reflected_thing->getAnnotations('someFilter');
> > foreach ( $validators as $validator ) {
> > $validator->validate($some_instance);
> > }
> >
> > This gets its *context* from the code running the validator; the
> > implementation of the validate() method would normally just be part of
> the
> > annotation type:
> >
> > <<new RangeValidator(1, 100)>>
> > var $foo;
> >
> >
> > The closure example you gave would be a way of providing on-the-fly
> dynamic
> > *behaviour* as the argument to an annotation. This wouldn't really make
> > sense for validation (a custom validator would still probably be defined
> > once and used for several fields, but theoretically you could have:
> >
> > <<new CustomValidator(function($value){ return is_prime($value); })>>
> >
> > It's not the context that's magic here, it's the ability to specify code
> as
> > part of the annotation *value*, rather than the annotation *definition*.
> >
> >
> > A more obvious example therefore is using annotations to implement DbC:
> >
> > <<new Precondition(function($values) { ... })>>
> >
> > Although as I admitted to Guilherme, that requires some magic to run it
> at
> > the right time anyway...
> >
> >
> >>> An example from the other thread of a context-bound annotation would be
> >>> implementing validation like this
> >>
> >>
> >> You're not annotating the function - this is just a round-about way of
> >> annotating the argument.
> >>
> >> You would do that directly, like this:
> >>
> >> function foo(
> >>     << ValidateRange(0, 100) >>
> >>     int $percentage
> >> ) { ... }
> >
> >
> > Sure, but it's easy to come up with a variant that requires access to
> more
> > than one parameter, say:
> >
> > << ValidateLessThan($min, $max) >>
> > function foo($min, $max)
> >
> > Which without any context or AST would need to be either:
> >
> > << ValidateLessThan('$min', '$max') >>
> > function foo($min, $max)
> >
> > ...which rather defeats the point of having code rather than just a
> string.
> >
> > Or:
> >
> > << ValidateCustom(function($min, $max) { return $min < $max } >>
> > function foo($min, $max)
> >
> > ...which feels rather clunky.
> >
> >
> >>> If the expression would have to be made up entirely of constants
> anyway,
> >>> might the same "constant expressions" used in class const definitions
> be a
> >>> better fit than "any valid PHP expression" - plus a specific exception
> for
> >>> creating objects out of those constant expressions.
> >>
> >>
> >> Probably not - what happens with what is today "nested annotations"
> >> then? Or will you make an exception for those too?
> >
> >
> > How would you "nest" annotations in your scheme anyway? As far as I can
> see,
> > they would be either multi-level arrays (a valid constant expression) or
> > nested constructor calls. So there's still exactly one exception made,
> the
> > "new Foo(args)" construct.
> >
> >
> >> The problem is, you're just reinventing a subset of the programming
> >> language, and I'm sure you can keep expanding on that indefinitely.
> >> What for? Just use the facilities already defined by the language.
> >
> >
> > That's a fair point. The flipside is that the more things you allow the
> user
> > to do, the more edge cases you have to deal with when evaluating the
> > expressions in your special "zero context".
> >
> >
> >> This fear of any feature that lets you do "evil" is incomprehensible
> >> to me. Most features of almost any programming language can be used
> >> for "evil".
> >
> >
> > I am not suggesting removing the ability to avoid people doing evil; I'm
> > suggesting that the feature you call "simple annotations" could be a lot
> > simpler.
> >
> > What is the value of being able to write this:
> >
> > function foo() { return 'MyAnnotation'; }
> >
> > << foo() >>
> > class A {}
> >
> > When you could just write this:
> >
> > << 'MyAnnotation' >>
> >
> >
> >> IMO, the real question is whether a feature accomplishes
> >> what you want. If you insist on something that also prevents things
> >> you don't want, you're bound to end up with something a lot more
> >> complex that fits into the language a lot less naturally...
> >
> >
> > In my mind, something that lets me evaluate an arbitrary expression in
> some
> > weird null-context, at an unspecified time (because I don't know what
> code
> > will be the first to request that annotation), only to have the result
> > stored in some invisible static variable, is *more* complex than many of
> the
> > alternatives.
> >
> >
> > Regards,
> > --
> > Rowan Collins
> > [IMSoP]
> >
> > --
> > PHP Internals - PHP Runtime Development Mailing List
> > To unsubscribe, visit: http://www.php.net/unsub.php
> >
>

Reply via email to