off the stack

Using TypoScript outside of Typo3

2012-07-02 | typo3, php

TypoScript is the main configuration language for the Typo3 Content Management System. Within Typo3 it is used for various things such as declaring what should be used to build the HTML of a page or declaring which "content columns" should be available to the CMS users for content entry.

For more information about the syntax and how TypoScript is integrated within Typo3, here some resources:

This document is about using TypoScript outside of Typo3 though.

Why

While I have been quite positive about TypoScript in general since my first steps in Typo3 (and think of it as one of the best parts of it actually), quite a few people I worked with were not.

Thinking about this little configuration language and its place within Typo3, as well as my own and other's experiences, it seems to me that it could be more an issue of the surrounding (eco-) system though.

Therefore I would like to show the basic steps that would be required to use it in a different setting.

How

Get the source

In order to get it working we need - of course - the sources of the parser. Unfortunately, the parser - even though it is quite small on itself - requires the "t3lib_div" class in order to work which on itself depends on quite a few other parts of typo3.

We have two options here. Download the whole source of Typo3 or just get the parser class and stub the rest.

Due to the fact that the download is quite large and we actually just need one file I will go for option 2 here.

wget -c -O class.t3lib_tsparser.php "http://git.typo3.org/TYPO3v4/Core.git?a=blob_plain;f=t3lib/class.t3lib_tsparser.php;hb=HEAD"

Stub

From here it is actually quite simple. First we define a few constants needed by the parser for line endings and tabulator characters. Then we define a stub t3lib_div class with a few functions used by the parser.

<?php
# Define constants used by the parser.
define('TAB', chr(9));
define('LF', chr(10));
define('CR', chr(13));
define('CRLF', CR . LF);

# Define a t3lib_div class with the functions used by the parser.
class t3lib_div {
    # Used by the built in functions which can be applied to values.
    # The parser doesn't need all the functionality of this function
    # and therefore the filtering and limiting is left out.
    static function trimExplode($sep, $string) {
        return array_map("trim", explode($sep, $string));
    }
    # Used by the parser to report parse errors/warnings
    const SYSLOG_SEVERITY_WARNING = E_USER_WARNING;
    static function sysLog($message, $key, $severity = 0) {
        trigger_error("[$key] $message", $severity);
    }
}

The defined constants and functions are the bare minimum to parse TypoScript, handle errors while parsing and use the few built-in "TSparser functions" to manipulate values. However, it is not enough to provide own TSparser functions or to support TypoScript includes.

In order to support includes one would have to call t3lib_tsparser::checkIncludeLines($typoscript) before parsing which will return a new TypoScript string with includes added. This, however, requires a few more t3lib_div functions. Supporting extra TSparser functions would require messing with Typo3-specific globals and adding another stub function to our t3lib_div.

Add conditions

The TypoScript parser supports conditions which are evaluated at parse-time and can be used to adjust the results according to your rules.

In order to use them, we have to supply a condition object which has a match method. This match method will receive the whole condition string (such as [dayofweek = 0] && [hour <= 12]). The match method should return true if the condition matches and false in case it does not.

We are not required to supply a match object. In case we don't, the parser acts as if all conditions are false.

<?php
# ...
class MyConditions {
    function match($condition) {
        return $condition == "[RANDOM]" && rand(0, 1);
    }
}

Try

Now we are ready to try out the parser.

<?php
# Load the parser class
require_once "class.t3lib_tsparser.php";

# Define constants used by the parser.
define('TAB', chr(9));
define('LF', chr(10));
define('CR', chr(13));
define('CRLF', CR . LF);

# Define a t3lib_div class with the functions used by the parser.
class t3lib_div {
    # Used by the built in functions which can be applied to values.
    # The parser doesn't need all the functionality of this function
    # and therefore the filtering and limiting is left out.
    static function trimExplode($sep, $string) {
        return array_map("trim", explode($sep, $string));
    }
    # Used by the parser to report parse errors/warnings
    const SYSLOG_SEVERITY_WARNING = E_USER_WARNING;
    static function sysLog($message, $key, $severity = 0) {
        trigger_error("[$key] $message", $severity);
    }
}

class MyConditions {
    function match($condition) {
        return $condition == "[RANDOM]" && rand(0, 1);
    }
}

$typoscript = "
foo = BAR
foo.bar {
    baz = FOO
    list = 0,1,2,3
}

[RANDOM]
    foo.baz = BAR
[ELSE]
    foo.baz = BAZ
[END]

foo.bar.list := removeFromList(0)
foo.bar.list := addToList(4)
foo.bar.list := prependString(0,)
foo.bar.list := appendString(,5-)
foo.bar.list := removeString(-)
foo.bar.list := replaceString(,|-)
";

$match_obj = new MyConditions();
$parser = new t3lib_tsparser();
$parser->parse($typoscript, $match_obj);
print_r($parser->setup);
print_r($parser->errors);

Running this should result in something like this:

$ php test.php 
Array
(
    [foo] => BAR
    [foo.] => Array
        (
            [bar.] => Array
                (
                    [baz] => FOO
                    [list] => 0-1-2-3-4-5
                )

            [baz] => BAR
        )

)
Array
(
)

And occasionally foo.baz set to BAZ.