Cowburn — Find me on GitHub and StackOverflow.

Using PHP Functions in XPath Expressions

Disclaimer: this article expects familiarity with using the DOM1 extension and XPath2 expressions.

The (currently undocumented now documented3) DOMXPath::registerPHPFunctions() method is available as of PHP 5.3.0 (it was added to the code base back in December 2006) and allows the use of PHP functions (and static methods) within XPath queries to complement the normal set of XPath functions2.

Description

void DOMXPath::registerPHPFunctions ([ string|array <var>$restrict</var>] )

Enables the use of PHP functions as XPath functions.

Parameters

restrict
Use this parameter to only allow certain functions to be called from XPath; it can be either a string (a function name) or an array of function names.

Return Values

No value is returned.

Examples

Note: The following examples load a sample XML document called book.xml with the following contents:

<?xml version="1.0" encoding="UTF-8"?>
<books>
    <book>
        <title>PHP Basics</title>
        <author>Jim Smith</author>
        <author>Jane Smith</author>
    </book>
    <book>
        <title>PHP Secrets</title>
        <author>Jenny Smythe</author>
    </book>
    <book>
        <title>XML basics</title>
        <author>Joe Black</author>
    </book>
</books>

Example #1: Call a PHP function in XPath with php:functionString()

This example demonstrates the basic use of DOMXPath::registerPHPFunctions() by replicating the substring() XPath function. The first thing that needs to be done is to register the php namespace with the associated URI http://php.net/xpath. Don't question it, it just needs to be done!

Next, we call DOMXPath::registerPHPFunctions() on our object. If no arguments are used, as in this example, then the range of functions allowed to be called is not restricted—you can call any4 function from XPath. I would always advise restricting the functions which can be called, see example 2.

Within our XPath query, we use php:functionString() which allows us to name a function and provide some parameters (or indeed, no parameters) to be passed to that function. There are two flavours which can be used here: php:functionString() which passes an XML node/attribute as a string and php:function() (see example 3) which passes an array of XML node/attribute objects (in DOMElement / DOMAttr / etc. form) to the function . In this example the PHP function substr() is called, passing along the book's title (in string form), an offset of 0 and length of 3. This returns the first 3 characters of the book's title which is then compared to the string PHP in order to filter our list of books down to those having titles starting with PHP.

<?php
$dom = new DOMDocument;
$dom->load('book.xml');

$xpath = new DOMXPath($dom);

// Register the php: namespace (required)
$xpath->registerNamespace("php", "http://php.net/xpath");

// Register PHP functions (no restrictions)
$xpath->registerPHPFunctions();

// Call substr function on the book title
$nodes = $xpath->query('//book[php:functionString("substr", title, 0, 3) = "PHP"]');

echo "Found {$nodes->length} books starting with 'PHP':\n";
foreach ($nodes as $node) {
    $title  = $node->getElementsByTagName("title")->item(0)->nodeValue;
    $author = $node->getElementsByTagName("author")->item(0)->nodeValue;
    echo "$title by $author\n";
}
?>

The example will output something like:

Found 2 books starting with 'PHP':
PHP Basics by Jim Smith
PHP Secrets by Jenny Smythe

Example #2: Restricting the functions available to XPath

To restrict the functions made available to XPath, provide either a string containing the name of the single function that you wish to allow or an array of strings containing function names as the restrict parameter (note: static methods can also be used, e.g. "Classname::method"). If functions were added with restrict and a function is called in XPath which is not one of them, an E_WARNING will be raised stating Not allowed to call handler 'function()' (where function is the name of the function that cannot be called).

<?php
$dom = new DOMDocument;
$dom->load('book.xml');

$xpath = new DOMXPath($dom);

// Register the php: namespace (required)
$xpath->registerNamespace("php", "http://php.net/xpath");

// Register PHP functions (no restrictions)
$xpath->registerPHPFunctions("strtoupper");

// Get first book's title in uppercase
$title = $xpath->evaluate('php:functionString("strtoupper", //book[1]/title)');
echo $title;

// Try a function not in our restrictions list
$fail = $xpath->evaluate('php:functionString("strtolower", //book[1]/title)');

?>

The example will output something like:

PHP BASICS
Warning: DOMXPath::evaluate() [domxpath.evaluate]: Not allowed to call handler 'strtolower()'. in example2.php on line 18

Example #3: Passing DOM objects using php:function()

Up to now, the examples have both used php:functionString(). As mentioned above, instead of passing a string value to the PHP function it is possible to pass along an array of DOM* objects to manipulate them as you please by using php:function().

<?php
$dom = new DOMDocument;
$dom->load('book.xml');

$xpath = new DOMXPath($dom);

// Register the php: namespace (required)
$xpath->registerNamespace("php", "http://php.net/xpath");

// Register PHP functions (no restrictions)
$xpath->registerPHPFunctions("example3");

function example3($nodes) {
    // Return true if more than one author
    return count($nodes) > 1;
}
// Filter books with multiple authors
$books = $xpath->query('//book[php:function("example3", author)]');

echo "Books with multiple authors:\n";
foreach ($books as $book) {
    echo $book->getElementsByTagName("title")->item(0)->nodeValue . "\n";
}
?>

The example will output something like:

Books with multiple authors:
PHP Basics

Summary

Just to quickly summarise everything, here is a quick run-down. In PHP, make sure to register the php namespace (with the URI http://php.net/xpath) and then register your PHP functions (whether core, extensions or user-defined) or static methods with DOMXPath::registerPHPFunctions(). In XPath, use php:functionString() or php:function() to call the PHP function.

If you have made use of this feature, or want to know more, then do feel free to comment. Thanks for reading.

Footnotes

  1. http://php.net/dom
  2. http://schlitt.info/opensource/blog/0704_xpath.html
  3. The documentation page, is available at http://php.net/domxpath.registerphpfunctions
  4. In truth, some functions are not suitable such as those that return non-scalar values (and cannot be cast to one) which XPath will not understand.