Re: DOMXPath / XSLTProcessor function callbacks

From: Date: Mon, 16 Oct 2023 21:19:02 +0000
Subject: Re: DOMXPath / XSLTProcessor function callbacks
References: 1 2  Groups: php.internals 
Request: Send a blank email to internals+get-121340@lists.php.net to get a copy of this message
Hi Frederik Sorry for the resend... I accidentally replied to you only without including the list the first time. On 15/10/2023 21:37, Frederik Bosch wrote: > Dear Niels, > > First of all, thanks for all your hard work already on the DOM and SimpleXML extensions. I have > been following your work in PHP-SRC, great! I am the author of this XSL 2.0 Transpiler in PHP > package (https://github.com/genkgo/xsl). It is indeed possible to use workarounds for closures or > object methods. I am relying on them in my package. > > My suggestion for the future would be to add the following method. > > public XSLTProcessor::registerFunctionsNS(string $namespace, array|ArrayAccess $functions): > void > > Then a user can register functions like this. > > $xsltProcessorOrDomXpath->registerFunctionsNS('urn:my.namespace', > array('upper-case', 'strtoupper', 'pow' => fn ($a, $b) => > $a[0]->textContent ** $b[0]->textContent, 'other' => [$obj, 'method']); > Interesting suggestion. So you want to be able to use something like my.namespace:function(...) as I understand it. I'm not sure that adding this is that much more beneficial though (complexity vs benefit trade-off). I assume this is motivated by the fact that you can then use third party libraries while having to worry less about name clashes? Let's say we add non-namespace registerFunction(string $name, callable $callback): void, you can then still use a convention of using a prefix, thus _kinda_ achieving the same. In any case, this is going to be hard to support in combination with the underlying library (libxslt). That's because the function namespace registration is process-wide, so this cannot be changed at runtime and certainly not for ZTS SAPIs. > The registered functions should use the same methodology as php:function(). Hence, string > casting of arguments is something the library user should do. I would leave registerPHPFunctions as > is, and maybe discourage it in favor of the method above. What if both are called? I think it would > be most clear if the registerFunctionsNS method throws InvalidArgumentException when > http://php.net/xsl or > http://php.net/xpath is passed as namespace. > > Cheers, > Frederik Cheers Niels > > > On 13-10-2023 00:39, Niels Dossche wrote: >> I'm looking to extend the functionality of calling PHP functions from within the >> DOMXPath or XSLTProcessor classes. >> >> In case you're unfamiliar here's a quick rundown. >> The DOMXPath class allows you to execute XPath queries on a DOM tree to lookup certain >> nodes satisfying a filter. >> PHP allows the user to execute function callbacks within these. For example (from the >> manual): >>    $xpath->query('//book[php:functionString("substr", title, 0, 3) = >> "PHP"]'); >> This will read the title element's text content, call substr on it, and then compare >> the output against "PHP". >> You can not only call builtin functions, but also user functions. >> >> To be able to call PHP functions, you need to use DOMXPath::registerPhpFunctions() >> (https://www.php.net/manual/en/domxpath.registerphpfunctions.php). >> You either pass in NULL to allow all functions, or pass in which function names are allowed >> to be called. >> >> Similarly, XSLTProcessor has the same registerPhpFunctions() method. >> For XSLT it's mostly used for performing arbitrary manipulations on input data. >> Normally the output of the function is put into the resulting document. >> >> >> So what's the problem? >> The current system doesn't allow you to call closures or object methods. >> There are tricks you can do with global variables and global functions to try to work >> around this, but that's quite cumbersome. >> >> There are two feature requests for this on the old bugtracker: >>    - https://bugs.php.net/bug.php?id=38595 >>    - https://bugs.php.net/bug.php?id=49567 >> >> It's not hard to implement support for this, the question is just what API we should >> go with. >> Based on what I've read, there are at least two obvious options: >> >> >> OPTION 1) Extend registerPHPFunctions() such that you can pass in callables >> >> ``` >> // Adapted from https://bugs.php.net/bug.php?id=38595 >> $xslt->registerPHPFunctions(array( >>     'functionblah', // Like we used to >>     'func2' => fn ($x) => ..., >>     'func3' => array($obj, 'method'), // etc >> )); >> ``` >> >> Example: Using php:function("func3") inside XPath/XSLT in this case will result >> in calling method on $obj. >> Similarly func2 will call the closure, and functionblah in the snippet just allowlists >> calling functionblah. >> >> It's a backwards compatible solution and a natural extension to the current method. >> It may be hard to discover this feature compared to having a new API though. >> >> Furthermore, once you pass in function names to registerPHPFunctions(), you're >> restricting what can be called. >> For example: imagine you want to call both ucfirst() and $obj->method(), so you pass in >> an entry like func3 in the above example. >> Now you have to pass in ucfirst to registerPHPFunctions() too, because >> registerPHPFunctions() acts as an allowlist. May be a bit inconvenient. >> >> >> OPTION 2) Add new methods to register / unregister callables >> >> This may be the cleaner way to go about it on first sight, but there's a potential BC >> break when new methods clash in user-defined subclasses. >> >> Question here is: what about the interaction with registerPHPFunction? >> What if both registerPHPFunction() and the register method add something with the same >> name? >> What if registerPHPFunction() didn't allowlist a function but the register method >> added it, may be a bit confusing for users. >> The interaction may be surprising. >> >> >> >> Please let me know your thoughts. >> >> Cheers >> Niels >> >

« previous php.internals (#121340) next »