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 >> >