Python's implementation of decorators is great for these kinds of reasons.
You get direct access to the function being called, the arguments which
were passed to it and any return value after (and if) you execute it.
Python's magic here, of course, is that functions and class methods are
first class objects we can reassign at will, so decorators are just
syntactical convenience.

An attributes-based PHP implementation will I think necessarily look and
behave a bit differently, but we should be able to achieve much the same
purposes with before and after hooks. I'm willing to forgo being able to
manipulate the arguments passed to the decorated function before it's
called, but any before hook would at least need to be able to access them
to be useful. Similarly I think you need to be able to get the return value
of the decorated function in an after hook. The Timer is a trivial example
which doesn't need these, but most useful hooks would.

A simple real-world use case example: I have a library which abstracts a
database layer and the Database class is full of public methods which take
a table name parameter. In every single method which does this, right at
the top I have a condition if ($this->isValidTableName($name))

I'd love to be able to replace that with a #[ValidTableName] decorator,
which obviously would need to access the parameter passed to the attributed
function. If I could throw an exception from that decorator, to effectively
prevent the decorated method being called if the table name is not valid
(and this should require no additional implementation in C beyond what
Ben's suggested), overall I think we've got a good basis of a useful
feature.

Equally though, I think this is potentially a really, really useful and
powerful enough feature that it deserves more attention and forethought
before I try to cobble something together as a Frankenstein monster. I'm
starting to familiarize myself with the attributes implementation in the
meantime to try and build up an idea of how I might build this.

Regards,
David


On Sun, Mar 14, 2021 at 4:31 PM Larry Garfield <la...@garfieldtech.com>
wrote:

> On Sun, Mar 14, 2021, at 7:33 AM, David Gebler wrote:
> > Hi Ben,
> > I have been looking at your #[Deprecated] PR to get an idea of where to
> > start with implementing this, I think Peter's suggestion of how it might
> > look syntactically is also interesting - it's exactly that sort of
> question
> > of how would you imagine devs implementing and using decorators in PHP
> that
> > I wanted to get a feel for before I started trying to build it or draft
> an
> > RFC. Your other tips regarding zend_observer and zend_call_function are
> > much appreciated, saves me a lot of digging through the source figuring
> out
> > the appropriate APIs for myself.
> >
> > Before and after hooks are a similar but slightly different
> implementation,
> > semantically, to what I was originally suggesting but may be the
> preferred
> > option. I think to be useful, you probably still need some way to choose
> > not to call the original, decorated function - but I guess you could just
> > throw an exception in your before hook and that might be sufficient
> control
> > in practice.
> >
> > Regards,
> > David
> >
> > On Sun, Mar 14, 2021 at 11:52 AM Benjamin Eberlei <kont...@beberlei.de>
> > wrote:
> >
> > >
> > >
> > > On Sat, Mar 13, 2021 at 5:51 PM David Gebler <davidgeb...@gmail.com>
> > > wrote:
> > >
> > >> With the introduction of attributes in PHP 8, this new behaviour is
> still
> > >> quite sparsely documented. Some of the articles I've seen out there,
> > >> though, liken PHP's attributes to similar constructs in other
> languages
> > >> including decorators in Python.
> > >>
> > >> Attributes are not the same thing as (Python's concept of) decorators
> and
> > >> they shouldn't be confused; a decorator is a function which wraps
> another
> > >> function and is automatically called in place of the wrapped function.
> > >>
> > >> This isn't currently possible in PHP. Using frameworks like Symfony,
> we
> > >> can
> > >> start to build things like this:
> > >>
> > >> class UserProfileController {
> > >>     #[LoginRequired]
> > >>     public function editProfile(...) { }
> > >> }
> > >>
> > >> but the logic of enforcing our "require the user to be logged in"
> > >> decorator
> > >> relies on the surrounding framework controlling the flow of execution,
> > >> reading the attribute and deciding whether to call the decorated
> method
> > >> editProfile() at all.
> > >>
> > >> What we *can't* do is something like this:
> > >>
> > >> class Foo {
> > >>     private function timer(callable $wrapped)
> > >>     {
> > >>         $start = microtime(true);
> > >>         $wrapped();
> > >>         $end = microtime(true);
> > >>         $total = $end - $start;
> > >>         echo "Executed function in $total second(s)\n";
> > >>     }
> > >>
> > >>      #[timer]
> > >>     public function bar($a, $b) { ... }
> > >>
> > >>     #[timer]
> > >>     public function baz($a, $b) { ... }
> > >> }
> > >>
> > >> What I'm wondering is whether there's a desire / interest for a
> built-in
> > >> attribute to provide this kind of behaviour modification.
> > >>
> > >> I'm thinking something like
> > >>
> > >> class Foo {
> > >>     private function timer(callable $wrapped) { ... }
> > >>
> > >>     #[Decorator([self::class, 'timer'])]
> > >>     public function bar() {
> > >>         echo "Bar";
> > >>     }
> > >> }
> > >>
> > >> Where this would result in any call to $foo->bar() being equivalent
> to as
> > >> if the above were defined as:
> > >>
> > >> class Foo {
> > >>     private function timer(callable $wrapped) { ... }
> > >>
> > >>      public function __bar() {
> > >>         echo "Bar";
> > >>      }
> > >>
> > >>     public function bar() {
> > >>         return $this->timer([$this, '__bar']);
> > >>     }
> > >> }
> > >>
> > >> I'm not saying I have the skills to implement this attribute (though
> I'd
> > >> happily try), I'm not even in a position to propose a draft RFC at
> this
> > >> stage, just throwing the idea out there to get a feel for what people
> > >> think
> > >> of the concept?
> > >>
> > >
> > > In my opinion it would be a fantastic addition to Core to be used by
> > > application frameworks with "hook philosophies" that hack this
> > > functionality on top of PHP with code generation or event dispatchers
> at
> > > the moment (Magento 2, Drupal, Neos, Wordpress and so on) makes this a
> > > potential future with wide adoption. If you'd get 2/3 votes for it is
> > > another topic.
> > >
> > > However, as functionality it could be provided as an extension first
> for a
> > > proof of concept. The ingredients are all there, it doesn't need to be
> in
> > > core:
> > >
> > > 1. Register an internal attribute, see my #[Deprecated] PR as an
> example
> > > https://github.com/php/php-src/pull/6521
> > >
> > > 2. Register a zend_observer as a first step, that detects
> > > functions/methods with a new #[Intercept] or whatever attribute you
> want
> > > and registers observer callbacks. See ext/zend_test
> > > https://github.com/php/php-src/blob/master/ext/zend_test/test.c or
> > > tideways/php-xhprof-extension:
> > >
> https://github.com/tideways/php-xhprof-extension/blob/master/tideways_xhprof.c#L30-L57
> > >
> > > 3. Use C API zend_call_function in the observer to call your
> interceptor.
> > >
> > > Cobbling this together as a Frankestein monster from existing code
> should
> > > be achievable even if you haven't worked with PHP core yet imho. This
> could
> > > replace the php-aop extension that isn't maintained anymore.
> > >
> > > Using a zend_obserer would only allow you to register a before and
> after
> > > hook, the alternative with a "$wrapped()" would be significantly more
> > > complex with the existing building blocks.
> > >
> > > Hooking into zend_exeute_ex would allow you to implement around
> handling,
> > > but at this point is not recommended anymore, because its incompatible
> with
> > > JIT and might be removed in the future.
> > >
> > >
> > >>
> > >> Regards,
> > >> Dave
>
> I've been toying (mostly mentally) with the idea of using attributes for
> guard rules on object properties and function parameters.  Benjamin gave me
> basically the same answer. :-)  It sounds like both that and AOP-attributes
> reduce to the same core problem: Allow an attribute to specify a caller
> that intercepts an action being taken (call method, write to property, hell
> maybe even read property although that seems less useful).
>
> So, what's the minimal surface area needed in C (either core or an
> extension) to allow the rest of that to be done in user space?
>
> --Larry Garfield
>
> --
> PHP Internals - PHP Runtime Development Mailing List
> To unsubscribe, visit: https://www.php.net/unsub.php
>
>

Reply via email to