Newsgroups: php.internals Path: news.php.net Xref: news.php.net php.internals:113519 Return-Path: Delivered-To: mailing list internals@lists.php.net Received: (qmail 59394 invoked from network); 14 Mar 2021 18:32:21 -0000 Received: from unknown (HELO php-smtp4.php.net) (45.112.84.5) by pb1.pair.com with SMTP; 14 Mar 2021 18:32:21 -0000 Received: from php-smtp4.php.net (localhost [127.0.0.1]) by php-smtp4.php.net (Postfix) with ESMTP id AC3951804B8 for ; Sun, 14 Mar 2021 11:25:39 -0700 (PDT) X-Spam-Checker-Version: SpamAssassin 3.4.2 (2018-09-13) on php-smtp4.php.net X-Spam-Level: X-Spam-Status: No, score=-2.1 required=5.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF,FREEMAIL_FROM,HTML_MESSAGE, RCVD_IN_DNSWL_NONE,RCVD_IN_MSPIKE_H3,RCVD_IN_MSPIKE_WL,SPF_HELO_NONE, SPF_PASS autolearn=no autolearn_force=no version=3.4.2 X-Spam-Virus: No X-Envelope-From: Received: from mail-ot1-f49.google.com (mail-ot1-f49.google.com [209.85.210.49]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange ECDHE (P-256) server-signature RSA-PSS (4096 bits) server-digest SHA256) (No client certificate requested) by php-smtp4.php.net (Postfix) with ESMTPS for ; Sun, 14 Mar 2021 11:25:39 -0700 (PDT) Received: by mail-ot1-f49.google.com with SMTP id 75so6043688otn.4 for ; Sun, 14 Mar 2021 11:25:39 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=mime-version:references:in-reply-to:from:date:message-id:subject:to; bh=UpFk9eRNDv7el8rkL1KRKgPyBobEbuxq9JmoILXAnpQ=; b=PbdSGDpEAxJkpz/VSdT1vwqNcudx8wZZtzaEHl0RLYWEZ5eZFQWhPSoNEReQZ7Koj7 mdWYEi1dDauyr7e09kz3oSe5e9PjjUN3bXkYxIEUUgVKL8spuonl+CgsDHzOBTWygW0a ImlOjHS1rsaJOtZwYtTyV57X1AqTz0ZQBJXn67uhNN8TK7nxdyhxwDAaqjPnwMU1E5uG HC5g+YR2df4C8mrhms9HF5397n4pniYdeAHEk7d7Awvn88KIohDqU/J+/HJaESPtXtuI zt9d3vppN68sslTUShuTdBFEytSjh1SrQYMWW3kVZFuHPRzlAdIdmhz23ZEWvJUqF/t6 ZoxQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:mime-version:references:in-reply-to:from:date :message-id:subject:to; bh=UpFk9eRNDv7el8rkL1KRKgPyBobEbuxq9JmoILXAnpQ=; b=Z/NaDR8COwCuMBB+yjTP7VOnANgxFACPfYlGgRgWW72S9RTIgdJ4FhfLRvKcaLMvk9 1iaSGQEIqxuv1rX4APf0Ob45AMABDDB3/LMcZnB25vZM7l0tuEGgAUjAZ3KfJj7rJejc oqfVaahmHG49iuU6x8gqrbcTjG+zJA+/jJo6esaV/ZOAhKVC6QhuaWrfbbYrHIQLsyKe DDqDSYHMkmfetQTYr7Xm/kMsA0xP3H+vQ2cerglt/1LajjkWex3c9mKF9QA2UShrHiMr bJYF46+FP8I0DnpRdmkw6gF9p4tKlXIQThPKJQyedr7jn46ASzNYEkg7LRzYeY/TgIV4 IHAA== X-Gm-Message-State: AOAM5323u3hz4deiCssRoGTQhfH5C5nm703saB62P4LTB3/Sqrj5XUmp cp5ndOcMAJnjVPVAgKsygOhXUotC10WLrMu+BtKgQLyOKCU= X-Google-Smtp-Source: ABdhPJy4nKlgcfGIM+t0Cxs06KdIO/i7MX62dvyddPDbOIsSRGvlKvaCQvjJaoVExKDU+zkzOsgUaCuh4o4uy6wvBFs= X-Received: by 2002:a9d:6a0a:: with SMTP id g10mr11286692otn.236.1615746335790; Sun, 14 Mar 2021 11:25:35 -0700 (PDT) MIME-Version: 1.0 References: In-Reply-To: Date: Sun, 14 Mar 2021 18:25:25 +0000 Message-ID: To: php internals Content-Type: multipart/alternative; boundary="00000000000064596205bd83422d" Subject: Re: [PHP-DEV] Built-in decorator attribute? From: davidgebler@gmail.com (David Gebler) --00000000000064596205bd83422d Content-Type: text/plain; charset="UTF-8" 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 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 > > wrote: > > > > > > > > > > > On Sat, Mar 13, 2021 at 5:51 PM David Gebler > > > 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 > > --00000000000064596205bd83422d--