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']);
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
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
--
PHP Internals - PHP Runtime Development Mailing List
To unsubscribe, visit: https://www.php.net/unsub.php