|
php.net | support | documentation | report a bug | advanced search | search howto | statistics | random bug | login |
[2009-09-15 23:07 UTC] mjs at beebo dot org
Description:
------------
A suggestion: it would be useful if there was a
registerObjectMethods() that worked in a similar way to
registerPHPFunctions() except that it took an object upon which
methods could be called. i.e. something like
class Greet {
public function byName($name) {
return "Hello, $name";
}
}
$xml = DOMDocument::loadXML("<root/>");
$xsl = DOMDocument::loadXML("... <xsl:value-of
select="php:call('byName', 'Michael')"/> ...");
$proc = new XSLTProcessor();
$proc->registerObjectMethods(new Greet());
$proc->importStyleSheet($xsl);
echo $proc->transformToXML($xml);
PatchesPull RequestsHistoryAllCommentsChangesGit/SVN commits
|
|||||||||||||||||||||||||||
Copyright © 2001-2025 The PHP GroupAll rights reserved. |
Last updated: Thu Nov 06 04:00:02 2025 UTC |
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 *));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?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.