DOMXPath / XSLTProcessor function callbacks
| From: | Niels Dossche | Date: | Thu, 12 Oct 2023 22:39:32 +0000 |
| Subject: | DOMXPath / XSLTProcessor function callbacks | ||
| Groups: | php.internals | ||
| Request: | Send a blank email to internals+get-121286@lists.php.net to get a copy of this message | ||
Hi internals
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