Full Article

How to Register and Use Custom PHP Functions Inside XSL

This article is not about justifying the use of PHP inside XSL. It is about how to do so if you need to or just want to. The case that I use as an example can be done easily in XSL without the use of a PHP function.

The title stresses custom PHP functions but you can also use this approach to enable the use of built-in PHP functions. A custom PHP function must be defined and registered before it is called by an XSL page template. A built-in function only has to be registered.

A custom PHP function can be defined and registered using an extension or an event. An event is more suitable for a function that is to be used by a single page or a small number of pages. An extension might be more suitable for a function (or functions) that will be used by all (or most all) pages. You be the judge. You can use both approaches but you must avoid trying to re-register a function in an event that has already been registered by an extension.

I will describe the event approach first. The first step is to define an event and attach it to the page (or pages) for which you will need the PHP function. Then you will need to edit the event file (workspace/events/youreventfile.php) to include the function definition and the registration code. Take a look at the example below which registers two custom functions (phptest and another_function) and one built-in function (time):

              
require_once(TOOLKIT . '/class.event.php');
Class eventregister_php_functions extends Event {
    const ROOTELEMENT = 'register-php-functions';
    public $eParamFILTERS = array();
    public static function about() {
        return array(
                 'name' => 'Register PHP Functions',
                 'author' => array(
                        'name' => 'Carson Sasser',
                        'website' => 'http://localhost/symphony-2',
                        'email' => 'see my contact page'),
                 'version' => '1.0',
                 'release-date' => '2010-10-09T01:29:08+00:00',
                 'trigger-condition' => 'action[register-php-functions]');  
    }
    public static function documentation() {
        return '';
    }
    public function load() {
        $functions = array('phptest', 'another_function', 'time');
        Frontend::instance()->Page()->registerPHPFunction($functions);
    }
    protected function __trigger() {
    }       
}
// Functions to be registered for use in XSL page templates (don't forget to add
// each function's name to the functions array above):
function phptest($path){
    $output = '';
    $contacts = $path[0]->childNodes;
    foreach ($contacts as $contact) {
        if ($contact->nodeName == '#text') continue;
        $output .= ucfirst($contact->nodeName).' ';
        $output .= $contact->getAttribute('id');
        $output .= '<br />';
        $info = $contact->childNodes;
        foreach ($info as $item) {
            if ($item->nodeName == '#text') continue;
            $output .= "<div style='padding-left:30px'>";
            $output .= ucfirst($item->nodeName).': ';
            $output .= $item->nodeValue;
            $output .= '</div>';
        }
    }
    return $output;
}
function another_function() {
    return "This is to demonstrate that you can register and use multiple functions.";
}

            

The function definitions are placed below and outside the event class. The function registration code is placed inside the event's load function. Although I deleted a lot of code that is superfluous to this type of event, these are the only changes you must make. Any needed built-in functions can also be registered here. When you have more than one function to register, the function names can be placed in an array as in this example, or you can simply repeat the register statement for each function.

The first function (phptest) defined above shows how you can access the XML DOM in a PHP function. When called by XSL with an XPath node as the argument, PHP receives it as a DOMElement Object contained in a single element array. That is, $path[0] is a DOMElement Object that provides access to all its child nodes, grandchild nodes...

Note the tests on nodeName == '#text'. This is necessary because Symphony includes these text nodes when it loads the page XML into the DOMDocument for processing by the XSL. They apparently come from the indent spaces or tabs in the XML string. If you are not averse to hacking the core code you can avoid including these tests by replacing the first statement below (in class.xsltprocess.php at or about line 65) with the second statement. So far, I have found no adverse effects from making this change.

              
$xml->loadXML($args[$xml_arg]);
$xml->loadXML($args[$xml_arg], LIBXML_NOBLANKS);

            

Now take a look at the XML and XSL:

              
<data>
    <contacts>
        <contact id="37">
            <name>John Doe</name>
            <email>john@gmail.com</email>
            <phone>749-621-3498</phone>
        </contact>
        <contact id="44">
            <name>Mickey Mouse</name>
            <email>mickey@hotmail.com</email>
            <phone>845-294-1274</phone>
        </contact>
        <contact id="51">
            <name>Homer Simpson</name>
            <email>homer@simpson.com</email>
            <phone>783-451-6329</phone>
        </contact>
    </contacts>
</data>

            
                
<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
    xmlns:php="http://php.net/xsl">
<xsl:import href="../utilities/master.xsl"/>
<xsl:template match="data">
    <div class="content">
        <div class="left_pane">
            <xsl:value-of select="php:function('phptest', contacts)" disable-output-escaping="yes"/><br />
            <xsl:value-of select="php:function('another_function')" />
            <br /><br />Unix timestamp: 
            <xsl:value-of select="php:function('time')"/>
        </div>
    </div>
</xsl:template>
</xsl:stylesheet>

            

You can see that phptest is called with the contacts node. This gives phptest access to the whole document below the data node. (Note the form of the PHP calls and that you must include a reference to the PHP namespace in the xsl:stylesheet element.) The built-in "time" function is called which displays the Unix timestamp. This is simply to demonstrate the registration and use of built-in functions along with custom functions.

Here is the output:

              
Contact 37
    Name: John Doe
    Email: john@gmail.com
    Phone: 749-621-3498
Contact 44
    Name: Mickey Mouse
    Email: mickey@hotmail.com
    Phone: 845-294-1274
Contact 51
    Name: Homer Simpson
    Email: homer@simpson.com
    Phone: 783-451-6329

This is to demonstrate that you can register and use multiple functions.

Unix timestamp: 1286741271

            

Here is an example of using an extension to define and register PHP functions:

              
class extension_register_php_functions extends Extension {
    public function about() {
        return array(
            'name' => 'Register PHP Functions',
            'version' => '0.1',
            'release-date' => '2010-10-08',
            'author' => array(
                'name' => 'Carson Sasser',
                'website' => 'http://tech.carsonsasser.com/',
                'email' => 'see my contact page'
            )
        );
    }
    public function getSubscribedDelegates(){
        return array(
            array(
            'page' => '/frontend/',
            'delegate' => 'FrontendOutputPreGenerate',
            'callback' => 'registerFunctions'
            )
        );
    }
    public function registerFunctions($context){
        $context['page']->registerPHPFunction('hello_world');
    }
}
// Functions to be called by XSL page templates:
function hello_world($path){
    return "Hello World!";
}

            

The principal difference in using extensions or events to define and register PHP functions is that an extension will be executed before any page is loaded regardless of the need for that function (or functions) in generating that page, while an event is executed only by those pages to which the event is attached.

This Symphony Forum discussion provided the impetus for this article. Andrew Shooner's EXSL Function Manager appears to be another way to register PHP functions.

Comments for this article

Will Nielsen
09 Nov 10, 4:28pm

Carson,

Thank you for this. It is a big big help in getting around some of the limitations in xsl.

Will

VG
10 Nov 10, 1:46am

Thank you very much, wiseolman.

"This Symphony Forum discussion provided the impetus for this article."

Indeed, this article represents the response to my dilemma about php functions registration.

Carson 15 Nov 10, 6:08pm

You are both quite welcome. Glad it was helpful.

Make a comment:

*Required inputs. Your email address will not be published.