Newsgroups: php.internals Path: news.php.net Xref: news.php.net php.internals:121329 Return-Path: Delivered-To: mailing list internals@lists.php.net Received: (qmail 86278 invoked from network); 15 Oct 2023 19:37:45 -0000 Received: from unknown (HELO php-smtp4.php.net) (45.112.84.5) by pb1.pair.com with SMTP; 15 Oct 2023 19:37:45 -0000 Received: from php-smtp4.php.net (localhost [127.0.0.1]) by php-smtp4.php.net (Postfix) with ESMTP id 688311804BC for ; Sun, 15 Oct 2023 12:37:44 -0700 (PDT) X-Spam-Checker-Version: SpamAssassin 3.4.2 (2018-09-13) on php-smtp4.php.net X-Spam-Level: X-Spam-Status: No, score=-2.1 required=5.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF,SPF_HELO_NONE,SPF_PASS, T_SCC_BODY_TEXT_LINE autolearn=no autolearn_force=no version=3.4.2 X-Spam-ASN: AS207843 2.58.165.0/24 X-Spam-Virus: No X-Envelope-From: Received: from mta3.mail.genkgo.net (mta3.mail.genkgo.net [2.58.165.21]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange ECDHE (P-256) server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by php-smtp4.php.net (Postfix) with ESMTPS for ; Sun, 15 Oct 2023 12:37:43 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=genkgo.nl; s=ge-k4; h=Content-Transfer-Encoding:Content-Type:In-Reply-To:From:References :To:Subject:MIME-Version:Date:Message-ID:Sender:Reply-To:Cc:Content-ID: Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc :Resent-Message-ID:List-Id:List-Help:List-Unsubscribe:List-Subscribe: List-Post:List-Owner:List-Archive; bh=vXGotSZ6tFCYs1XB/e6fX4WjWBqJXQrLov0/5rQsXR4=; b=Dp5c9FE8KCHuQYzknzteo4Jq3f DerFHsr9xVjf62lSnfyIYbCYe+t3odMgCHkmBCQQJ20poSW8Ufl2Hvu4LMRe5S81a1YZkGDMxXhW1 bqrF6zFzSxYYI0kJ60S7u4LLAHJE2GA/zToa4GlVyWPB6Y0lWweFYPA+b1i/30/tNFbPP+mDgOhKI 3EfXPlqfhnZmtNpYmKFDC5V40ZvExK9ScqdMsIuJNbLt3q1NkEVQsZ2jlnxiUl7Re0f6P2fhtB1gu nSrfefVLaaf5kiUSeZQgfVENGN+yu3GdZdwEibZ4V6rAZS/Wiq7pV7ZKc0Ydg7kpnZifIDHagMQV4 938jEWkg==; Received: from 81-205-95-38.fixed.kpn.net ([81.205.95.38] helo=[192.168.2.14]) by mta3.mail.genkgo.net with esmtpsa (TLS1.3) tls TLS_AES_128_GCM_SHA256 (Exim 4.96) (envelope-from ) id 1qs6vl-0000To-2v; Sun, 15 Oct 2023 19:37:42 +0000 Message-ID: Date: Sun, 15 Oct 2023 21:37:41 +0200 MIME-Version: 1.0 User-Agent: Mozilla Thunderbird Content-Language: en-US, nl-NL To: Niels Dossche , PHP internals References: <0e6ee882-472f-43e1-822e-c156d9048e0c@gmail.com> In-Reply-To: <0e6ee882-472f-43e1-822e-c156d9048e0c@gmail.com> Content-Type: text/plain; charset=UTF-8; format=flowed Content-Transfer-Encoding: 7bit Subject: Re: DOMXPath / XSLTProcessor function callbacks From: f.bosch@genkgo.nl (Frederik Bosch) 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 >