Extending x:template
When you work with a template engine there will come the time you want to extend the engine's features. To do so x:template provides a very flexible way to bring in your own ideas without touching the actual files. This makes the engine extendable while still having the possibility to update it.
Configuration
x:template configures itself through a configuration file which is located in xml/config.xml. If you want to extend x:template, you will have to create your own config XML file. But first you have to understand how this configuration works.
Config interpretors
Since there are so many things which must be configured and even more which could be configured there is no fixed config structure which could handle this. So x:template works with a very flexible way to use config files: Config interpretors. Config interpretors are PHP classes which are registered for a certain tag name. When the config file is parsed, different interpretors are created depending on the tag names. This is how the process works:
- The configuration XML file is loaded
- The engine iterates over the first level nodes
- The corresponding class name is looked up in the list of registered interpretors depending on the current tag name
- If a class name was found, a new instance of this class will be created. This instance receives the current node und will be stored under the name declared in the name-attribute in the parsing confing instance.
- If no interpretor was found, this node will be skipped.
- Go back to step 1 with the next node
This is an extract of the config file. There is only one fixed config interpretor which is the interpretor to register new interpretors. So the first thing which is done in the built in file, is registering all built in interpretors.
<?xml version="1.0" encoding="utf-8" ?> <x:config xmlns:x="http://xtemplate.net/xmlns.html"> <x:interpretors name="interpretors"> <interpretor name="autoloader">XTemplate\Core\ConfigInterpretors\Autoloader</interpretor> <interpretor name="overload">XTemplate\Core\ConfigInterpretors\Overload</interpretor> <interpretor name="config">XTemplate\Core\ConfigInterpretors\Config</interpretor> <interpretor name="settings">XTemplate\Core\ConfigInterpretors\Settings</interpretor> <interpretor name="parsers">XTemplate\Core\ConfigInterpretors\Parsers</interpretor> <interpretor name="selectors">XTemplate\Core\ConfigInterpretors\Selectors</interpretor> </x:interpretors> </x:config>
Creating a custom config file
x:template is designed to be used in a development team. So it would't be a good idea to modify certain settings of the system config file. There could be other developers which need features you remove or change. To solve this issue you can create your own config files which extends the default one.
Your config could look like this:
<?xml version="1.0" encoding="utf-8" ?> <x:config extends="../../../xml/config.xml" xmlns:x="http://xtemplate.net/xmlns.html"> <x:settings name="foo"> <bar>Hello World!</bar> </x:settings> </x:config>
As you can see: To extend a config file, you create a new XML file add a extends-attribute to the root node of the file which contains the path to the file you wish to extend relatively to your custom file.
To use your custom configuration, you have to load it. This is done by the general XTemplate\Engine-class. Every view instance which is created has a certain instance of this class. If you don't set an instance manually, the default one is used automatically. The built in view classes receive an engine instance with the last constructor argument.
<?php /** * x:template - PHP based template engine * Copyright (c) 2011 - 2012 by Tobias Pohlen <tobias.pohlen@xtemplate.net> * * Released under the GPL License. * * @author Tobias Pohlen * @ignore */ require_once "../../../Engine.php"; require_once "view1.php"; // Create a new engine instance $objEngine = new XTemplate\Engine("custom_config1.xml"); // Create a new view instance and assign the engine instance $objView = new View1($objEngine); // Render the view echo $objView; ?>
Built in interpretors
This is a list of built in interpretors:
Tag name | Description |
---|---|
x:interpretors |
This interpretor registers new interpretors. If you want to create your own one, you have to use this to introduce it to the engine. To do so, you have to add node to this interpretor node: scripts/7_extending_xtemplate/x_interpretors.xml
<!-- To register custom config interpretor, you have to add this snippet to your config file. {name} A name for this configuration node. It doesn't matter. Just use anything which is not used yet {tagname} The tagname you want to use for your interpretor {classname} The classname of your interpretor. Please read the chapter about creating the class --> <x:interpretors name="{name}"> <interpretor name="{tagname}">{classname}</interpretor> </x:interpretors> |
x:overload |
This interpretor allows you to overwrite any class of the engine except the core classes. So you can easily add new features to the engine. scripts/7_extending_xtemplate/x_overload.xml
<!-- Add this snippet to your config file to overwrite certain classes. {name} A not taken name {engine_classname} The engine class you wish to overwrite {your_classname} The class which shall overwrite the engine class --> <x:overload name="{name}"> <overload name="{engine_classname}">{your_classname}</overload> </x:overload> |
x:config |
This interpretors allows you to use nested configurations. You can add a domain-attribute to a nested configuration to use certain settings only under a certain domain. scripts/7_extending_xtemplate/x_config.xml
<!-- To use some settings only on certain domains, you have to add this snippet to your configuration file {name} A random name which is not used yet {domain} The name of the domain you want to make these settings available to --> <x:config domain="{domain}" name="{name}"> <!-- You can use any defined config interpretor here --> </x:config> |
x:settings |
This interpretors allows you to store any information you want in an array structure. The XML tree in the node is converted into an array and can be access via array operator. |
x:parsers |
This interpretor allows you to register certain string parsers which will be called after every view is rendered. An example will come later in the tutorial. |
x:selectors |
This interpretor allows you to register custom CSS selectors which can be used in your select statements. An example will come later in the tutorial. |
Custom interpretors
To create a custom interpretor, you have to create a new PHP class which extends XTemplate\Core\ConfigInterpretor. Your class has to implement the protected parse-method which is called automatically when the interpretor is created. This method is your space to what ever you want with the received datas. The node is given as instance of XTemplate\Core\XML which extends the php built in SimpleXMLElement-class. To access this node you can use $this->objXML. You also get an instance of the XTemplate\Config-class which parses the configuration file. If you have to throw an exception, you can use XTemplate\Core\Exceptions\ConfigException.
This is what a config interpretor could look like. This interpretor sets the HTTP content type to the value defined int the config file:
<?php /** * x:template - PHP based template engine * Copyright (c) 2011 - 2012 by Tobias Pohlen <tobias.pohlen@xtemplate.net> * * Released under the GPL License. * * @author Tobias Pohlen * @ignore */ require_once "../../../Engine.php"; class CustomInterpretor extends XTemplate\Core\ConfigInterpretor { /** * Interprets the config node */ protected function parse() { header("Content-type: ". (string) $this->objXML->children()->content_type); } } ?>
Replacing x:template classes
You might be uncomfortable with the behavior of some x:template classes which are used internally such as the ElementList. x:template allows you to overwrite these classes to bring in your own ideas. Everything you have to do is, adding a new entry to the x:overload entry of your custom config file.
<?xml version="1.0" encoding="utf-8" ?> <x:config extends="../../../xml/config.xml" xmlns:x="http://xtemplate.net/xmlns.html"> <x:overload name="my_overload"> <overload name="XTemplate\ElementList">MyElementList</overload> </x:overload> </x:config>
In this tutorial the custom class doesn't contain any extra functionality. But you of course can add everything you want.
<?php /** * x:template - PHP based template engine * Copyright (c) 2011 - 2012 by Tobias Pohlen <tobias.pohlen@xtemplate.net> * * Released under the GPL License. * * @author Tobias Pohlen * @ignore */ // Load the x:template engine require_once "../../../Engine.php"; /** * A custom element list which can contain some custom functionallity * * @author Tobias Pohlen * @version $Id$ * @package xtemplate * @ignore */ class MyElementList extends XTemplate\ElementList {} ?>
This is the corresponding controller and the result:
<?php /** * x:template - PHP based template engine * Copyright (c) 2011 - 2012 by Tobias Pohlen <tobias.pohlen@xtemplate.net> * * Released under the GPL License. * * @author Tobias Pohlen * @ignore */ require_once "../../../Engine.php"; require_once "view1.php"; require_once "MyElementList.php"; // Create a new engine instance $objEngine = new XTemplate\Engine("overload_el.xml"); // Create a new view instance and assign the engine instance $objView = new View1($objEngine); // Get the classname of the selector result. It should be "MyElementList" echo get_class($objView["div"]); ?>
Parsers
x:template gives to the possibility to perform certain actions before or after every view is rendered. There are some built in parses e.g. to remove the xid-attribute which are not supposed to be in the HTML document. You can add your own parsers, too. At first you have to create a new class which inherits form XTemplate\Parser\Base.
<?php /** * x:template - PHP based template engine * Copyright (c) 2011 - 2012 by Tobias Pohlen <tobias.pohlen@xtemplate.net> * * Released under the GPL License. * @ignore */ require_once "../../../Engine.php"; /** * This post rendering parser removes all xid attributes from the rendered template. * * @author Tobias Pohlen * @version $Id$ * @package xtemplate * @ignore */ class MyParser extends XTemplate\Parsers\Base { /** * Removes the XID attributes * * @throws XTemplate\Exceptions\ParsingException */ protected function parse() { // Display "Hello World" in the first h1 node of every template $this->objTemplate->getElementsByTagName("h1")->item(0)->nodeValue = "Hello World"; } } ?>
To add this parser to your system, you have to add the following entry to your custom config file. The position-attribute describes whether the parser shall be launched before or after the template is rendered. You type pre or post. This is how your config file could look like:
<?xml version="1.0" encoding="utf-8" ?> <x:config extends="../../../xml/config.xml" xmlns:x="http://xtemplate.net/xmlns.html"> <x:parsers name="my_parsers"> <parser position="post">MyParser</parser> </x:parsers> </x:config>
This is how your custom parser, your controller and the result could look like:
<?php /** * x:template - PHP based template engine * Copyright (c) 2011 - 2012 by Tobias Pohlen <tobias.pohlen@xtemplate.net> * * Released under the GPL License. * @ignore */ require_once "../../../Engine.php"; /** * This post rendering parser removes all xid attributes from the rendered template. * * @author Tobias Pohlen * @version $Id$ * @package xtemplate * @ignore */ class MyParser extends XTemplate\Parsers\Base { /** * Removes the XID attributes * * @throws XTemplate\Exceptions\ParsingException */ protected function parse() { // Display "Hello World" in the first h1 node of every template $this->objTemplate->getElementsByTagName("h1")->item(0)->nodeValue = "Hello World"; } } ?>
<?php /** * x:template - PHP based template engine * Copyright (c) 2011 - 2012 by Tobias Pohlen <tobias.pohlen@xtemplate.net> * * Released under the GPL License. * * @author Tobias Pohlen * @ignore */ require_once "../../../Engine.php"; require_once "view2.php"; require_once "MyParser.php"; // Create a new engine instance $objEngine = new XTemplate\Engine("adding_parsers.xml"); // Create a new view instance and assign the engine instance $objView = new View2($objEngine); echo $objView; ?>
As you can see: You cannot use CSS selectors in the parsers.
Creating CSS selectors
This might be the most interesting part of the tutorial: How to create your own CSS selectors which can be used in the select statements. Before you begin, you should consider reading the W3C XPath documentation. This will help you allot when. Of course you can always take a look at the already created selectors in the Selectors/ -subfolder of the engine.
Basically there are three kinds of selectors:
Select kind | Meaning | Class to extend |
---|---|---|
Path selectors | These are selectors which are used to describe the actual path in the DOM document. Some examples:
div.foo
span#bar |
PathSelectorBase |
Attribute selectors | These are selectors which are used to select node which match a certain attribute pattern. This works
quite automated. You can only add new operators to the engine. Some examples:
div.foo[bar='foobar']
div#bar[foo^='foobar'] |
AttributeSelectorBase |
Selector conjunctions | These are selectors which are used to combine two selectors in a certain way. Some examples:
div.foo + div.bar
div#bar > span |
ConjunctionSelectorBase |
It doesn't matter which selector you want to implement, you will always receive an instance of XTemplate\Selectors\SelectorModel. This object contains all xpath information about the current select state. You can add further information via these two methods:
Method | Meaning |
---|---|
pushPath([string] $path) | This method allows you to add something to the path part of the current xpath. Example:
// Current stack: ["/div", "/span[0]"]
$objModel->pushPath("/div"); // Stack: ["/div", "/span[0]", "/div"] // Result: /div/span[0]/div |
pushAttribute([string] $attribute) | This method allows you to add something to the attribute part of the current xpath. Example:
// Current stack : ["@id=0"]
$objModel->pushAttribute("@class='test']
// Stack: ["@id=0", "@class='test'"]
// Result: /div/span[0]/div[@id=0 and @class='test']
|
At this point I will show you how to create a path selector as example. If you want to know how the other selectors differ from this example, look at the already implemented ones.
This path selector will select all elements which have a name attribute like the selector argument. The selector operator shall be '$'. Example: input$test will select all inputs with the name 'test'.
The selector class looks like this:
<?php /** * x:template - PHP based template engine * Copyright (c) 2011 - 2012 by Tobias Pohlen <tobias.pohlen@xtemplate.net> * * Released under the GPL License. * @ignore */ /** * This selector allows you to select tags with their names. e.g.: * * 'div$test' * * @author Tobias Pohlen * @version $Id$ * @package xtemplate * @ignore */ class MySelector extends XTemplate\Selectors\PathSelectorBase { /** * Selects the element * * @param string $strOperator The select operator (e.g. '$') * @param string $strArgument Everything behinde the operator * @param XTemplate\Selectors\SelectorModel $objModel * @throws XTemplate\Selectors\Exceptions\SelectorException */ public function performSelector($strOperator, $strArgument, XTemplate\Selectors\SelectorModel $objModel) { $objModel->pushAttribute("@name='". $strArgument ."'"); } } ?>
To registrate the selector, you have to add this to your custom configuration:
<?xml version="1.0" encoding="utf-8" ?> <x:config extends="../../../xml/config.xml" xmlns:x="http://xtemplate.net/xmlns.html"> <x:selectors name="my_selectors"> <path operator="$">MySelector</path> </x:selectors> </x:config>
Now you can use the selector in your view class:
<?php /** * x:template - PHP based template engine * Copyright (c) 2011 - 2012 by Tobias Pohlen <tobias.pohlen@xtemplate.net> * * Released under the GPL License. * * @author Tobias Pohlen * @ignore */ // Load the x:template engine require_once "../../../Engine.php"; /** * @author Tobias Pohlen * @version $Id$ * @package xtemplate * @ignore */ class View4 extends XTemplate\ComfortView { const TEMPLATE = "template2.html"; /** * Render the template and the sections */ protected function _render() { $this['$foo']->setAttribute("value", "foobar"); $this['input$bar[type="text"]']->setAttribute("value", "foo foo"); } } ?>
<h1>Custom selector</h1> <input type="text" name="foo" /> <input type="text" name="bar" /> <form> <p>Lorem ipsum...</p> <input type="text" name="foo" /> </form>