Req #49567 [Opn->Csd]: DOM/XSL: Suggestion: registerObjectMethods()
| From: | nielsdos@php.net | Date: | Sat, 13 Jan 2024 12:44:53 +0000 |
| Subject: | Req #49567 [Opn->Csd]: DOM/XSL: Suggestion: registerObjectMethods() | ||
| References: | 1 | Groups: | php.bugs |
| Request: | Send a blank email to php-bugs+get-246255@lists.php.net to get a copy of this message | ||
Edit report at https://bugs.php.net/bug.php?id=49567&edit=1
ID: 49567
Updated by: nielsdos@php.net
Reported by: mjs at beebo dot org
Summary: DOM/XSL: Suggestion: registerObjectMethods()
-Status: Open
+Status: Closed
Type: Feature/Change Request
Package: XSLT related
Operating System: OS X
PHP Version: 5.3.0
-Assigned To:
+Assigned To: nielsdos
Block user comment: N
Private report: N
New Comment:
The fix for this bug has been committed.
If you are still experiencing this bug, try to check out latest source from https://github.com/php/php-src and re-test.
Thank you for the report, and for helping us make PHP better.
Something very similar to this was implemented in https://wiki.php.net/rfc/improve_callbacks_dom_and_xsl
Previous Comments:
------------------------------------------------------------------------
[2017-03-16 18:51:11] tom at r dot je
This would be really useful, at the moment I've needed to use an incredibly ugly hack to keep
track of all instances by hash to workaround this and call functions on the correct instance.
It would be better if it was possible to do something like this:
```php
$xpath->registerPhpFunction('foo', function() {
});
```
That way, you could register any callback and give it a specific name. This would be more flexible
that the suggested
registerObjectMethods()
For anyone finding this wanting a workaround here's an ugly hack that works:
```
<?php
class MyClass {
private static $instances = [];
public function runXpath() {
$document = new \DomDocument();
$xpath = new \DomXPath();
$xpath->registerNamespace('php', 'http://php.net/xpath');
$xpath->registerPhpFunctions();
$hash = spl_object_hash($this);
//Register this instance
self::$instances[$hash] = $this;
//Pass the object hash as part of the XPath query so it gets passed
//to the static method callFunction
$results = $xpath->query('//[php:function("\MyClass::callFunction",
"theFunctionIReallyWantToCall" "' . $hash . '", "arg1",
.)]'; }
}
public static function callFunction($functionName, $instanceHash, ...$args) {
//The static function has been called but we want to call a function
//on the correct instance
//Locate the instance by its hash
$instance = self::$instances[$instanceHash];
//now call the function on the instance with the remaining args
return call_user_func($instance, $functionName, $args);
}
public function theFunctionIReallyWantToCall($arg1, \DomElement $element) {
//do whatever with access to $this and class variables.
}
}
```
This is ugly as hell but until this 8 year old feature request is implemented it's the best way
to solve it.
------------------------------------------------------------------------
[2009-09-25 08:33:26] chregu@php.net
Thanks for your work. Here's the right place for the patches, but you
could also send it to me or to php-internals, so that it won't get
forgotten :)
chregu
------------------------------------------------------------------------
[2009-09-23 09:10:33] mjs at beebo dot org
Thanks for your reply.
Yes, you can register exactly one object. If you call
registerPHPFunctions() twice, the functions/methods registered will be
those of the second object.
If there's a function of the same name, it won't get called--
essentially if you have php:function('foo') in your XSL, foo() is
either considered as a function in global scope (if you registered an
array of strings, or nothing at all), or a method name in the "scope"
of the registered object (if you registered an object).
This is not the most flexible approach but I think it's a simple and
straightforward enhancement that requires no extra syntax on either
the PHP or XSL side. Also, anything more advanced is beyond my
extension-writing capabilities!
I've made a few changes to the patch, and am working on a few more
little things that don't affect the syntax. Is here the right place
to post it when I'm done?
------------------------------------------------------------------------
[2009-09-22 13:30:58] chregu@php.net
Thanks for your work. I didn't test it out, but looking at the code 2
questions pop up:
You can register exactly one object?
and
What happens, if there's a function with that name? eg. 'add' in your
test?
Not sure, if I'm happy how it is right now, but it certainly goes into
the right direction.
------------------------------------------------------------------------
[2009-09-22 07:14:34] mjs at beebo dot org
Had a look at how the xsl extension works, and had a shot at
implementing this myself--the patch is below.
Instead of adding a new function I enhanced registerPHPFunctions() so
that it can take an object. It works like this: if an object is
provided (say $foo), then <xsl:value-of select="php:function('quux',
44)"/> in the XSL results in a call to $foo->quux(44).
i.e. in PHP:
$foo = new Foo();
$processor->registerPHPFunctions($foo);
In XSL:
<xsl:value-of select="php:function('quux', 44)"/>
Two of the tests in ext/xsl failed, but they fail without this patch
also.
The patch is:
Index: ext/xsl/tests/xsltprocessor_registerPHPFunctions-method.phpt
===================================================================
--- ext/xsl/tests/xsltprocessor_registerPHPFunctions-method.phpt
(revision 0)
+++ ext/xsl/tests/xsltprocessor_registerPHPFunctions-method.phpt
(revision 0)
@@ -0,0 +1,38 @@
+--TEST--
+Checks XSLTProcessor::registerPHPFunctions($obj) where the
"functions"
+exposed in the XSL file are methods of $obj.
+--SKIPIF--
+<?php
+if (!extension_loaded('xsl')) {
+ die("skip\n");
+}
+?>
+--FILE--
+<?php
+
+class Foo {
+ public function greet($name) {
+ return "Hello, $name";
+ }
+ public function add($i, $j) {
+ return $i + $j;
+ }
+}
+
+$xml = new DOMDocument();
+$xml->loadXML("<root/>");
+$xsl = new DOMDocument();
+$xsl->load(dirname(__FILE__) . "/phpmethod.xsl");
+
+$proc = new XSLTProcessor();
+$proc->importStylesheet($xsl);
+
+$foo = new Foo();
+
+$proc->registerPHPFunctions($foo);
+
+echo $proc->transformToXml($xml);
+--EXPECTF--
+<result greet="Hello, Clem" add="7"/>
+--CREDITS--
+Michael Stillwell <mjs@beebo.org>
\ No newline at end of file
Index: ext/xsl/tests/phpmethod.xsl
===================================================================
--- ext/xsl/tests/phpmethod.xsl (revision 0)
+++ ext/xsl/tests/phpmethod.xsl (revision 0)
@@ -0,0 +1,15 @@
+<xsl:stylesheet
+ version="1.0"
+ xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+ xmlns:php="http://php.net/xsl"
+ exclude-result-prefixes="php">
+ <xsl:output omit-xml-declaration="yes"/>
+<xsl:template match="/">
+ <result>
+ <!-- pass a single string argument -->
+ <xsl:attribute name="greet"><xsl:value-of
select="php:function('greet', 'Clem')"/></xsl:attribute>
+ <!-- pass two integer arguments -->
+ <xsl:attribute name="add"><xsl:value-of
select="php:function('add',
3, 4)"/></xsl:attribute>
+ </result>
+</xsl:template>
+</xsl:stylesheet>
\ No newline at end of file
Index: ext/xsl/php_xsl.h
===================================================================
--- ext/xsl/php_xsl.h (revision 288545)
+++ ext/xsl/php_xsl.h (working copy)
@@ -55,6 +55,7 @@
HashTable *node_list;
php_libxml_node_object *doc;
char *profiling;
+ zval *object_ptr;
} xsl_object;
void php_xsl_set_object(zval *wrapper, void *obj TSRMLS_DC);
Index: ext/xsl/xsltprocessor.c
===================================================================
--- ext/xsl/xsltprocessor.c (revision 288545)
+++ ext/xsl/xsltprocessor.c (working copy)
@@ -308,11 +308,11 @@
fci.function_name = &handler;
fci.symbol_table = NULL;
- fci.object_ptr = NULL;
fci.retval_ptr_ptr = &retval;
fci.no_separation = 0;
+ fci.object_ptr = intern->object_ptr ? intern->object_ptr :
NULL;
/*fci.function_handler_cache = &function_ptr;*/
- if (!zend_make_callable(&handler, &callable TSRMLS_CC)) {
+ if ((intern->registerPhpFunctions != 3) &&
!zend_make_callable(&handler, &callable TSRMLS_CC)) {
php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to
call handler %s()", callable);
} else if ( intern->registerPhpFunctions == 2 &&
zend_hash_exists(intern->registered_phpfunctions, callable,
strlen(callable) + 1) == 0) {
@@ -320,6 +320,9 @@
/* Push an empty string, so that we at least have an
xslt result... */
valuePush(ctxt, xmlXPathNewString(""));
} else {
+ if (intern->object_ptr) {
+ fci.object_ptr = intern->object_ptr;
+ }
result = zend_call_function(&fci, NULL TSRMLS_CC);
if (result == FAILURE) {
if (Z_TYPE(handler) == IS_STRING) {
@@ -794,6 +797,7 @@
zval *id;
xsl_object *intern;
zval *array_value, **entry, *new_string;
+ zval *obj_ptr;
int name_len = 0;
char *name;
@@ -823,6 +827,12 @@
zend_hash_update(intern->registered_phpfunctions,
name, name_len + 1, &new_string, sizeof(zval*), NULL);
intern->registerPhpFunctions = 2;
+ } else if (zend_parse_parameters_ex(ZEND_PARSE_PARAMS_QUIET,
ZEND_NUM_ARGS() TSRMLS_CC, "o", &obj_ptr) == SUCCESS) {
+ intern = (xsl_object *)zend_object_store_get_object(id
TSRMLS_CC);
+ zend_hash_clean(intern->registered_phpfunctions);
+ intern->object_ptr = obj_ptr;
+ Z_ADDREF_P(obj_ptr);
+ intern->registerPhpFunctions = 3;
} else {
intern = (xsl_object *)zend_object_store_get_object(id
TSRMLS_CC);
intern->registerPhpFunctions = 1;
Index: ext/xsl/php_xsl.c
===================================================================
--- ext/xsl/php_xsl.c (revision 288545)
+++ ext/xsl/php_xsl.c (working copy)
@@ -84,6 +84,10 @@
zend_hash_destroy(intern->registered_phpfunctions);
FREE_HASHTABLE(intern->registered_phpfunctions);
+ if (intern->object_ptr) {
+ Z_DELREF_P(intern->object_ptr);
+ }
+
if (intern->node_list) {
zend_hash_destroy(intern->node_list);
FREE_HASHTABLE(intern->node_list);
@@ -127,6 +131,7 @@
intern->node_list = NULL;
intern->doc = NULL;
intern->profiling = NULL;
+ intern->object_ptr = NULL;
zend_object_std_init(&intern->std, class_type TSRMLS_CC);
zend_hash_copy(intern->std.properties, &class_type-
>default_properties, (copy_ctor_func_t) zval_add_ref, (void *) &tmp,
sizeof(zval *));
------------------------------------------------------------------------
The remainder of the comments for this report are too long. To view
the rest of the comments, please view the bug report online at
https://bugs.php.net/bug.php?id=49567
--
Edit this bug report at https://bugs.php.net/bug.php?id=49567&edit=1