Exploring MODx From the Inside: How MODx Snippets Work

How MODx Snippets Work

Snippets are containers for bits of PHP code to provide dynamic content. They are used for menus and other lists of documents, form generation, validation and processsing and user management functions. Like the <?php...?> structure, they allow PHP code to be inserted anywhere in the HTML structure of templates, chunks, TVs and document content. Unlike inline PHP, since snippets are taken from the database and processed by the MODx parser, the PHP code contained in snippets is processed through the PHP eval() function.

Snippet tags take two forms. Cached snippets are called with [[ ... ]] tags, while uncached snippets are called with [! ... !] tags. Snippets can be processed within PHP code using the $modx->runSnippet() function. This function takes two parameters, the snippet name and an array containing the snippet's parameters.

$modx->runSnippet('SnippetName', array('param1'=>'value1', 'param2'=>'value2'));

Cached and Uncached Snippets

When a snippet is called with cached tags, the first time the page is requested the document will be parsed, snippets will be evaluated and their output inserted where the tags are positioned, then the entire document is cached. Further requests for the same page will have the entire page taken from cache and served (mostly) as-is.

This would be a problem if, for example, the snippet generates a form and also processes the form on submission. Submitting a form causes the visitor's browser to request the form's "action" attribute URL with accompanying POST data from the form's fields. If the action URL is a request for the same page (which is very common if the page also contains the PHP code for processing the form's submitted POST values) since the page is taken whole from the cache, the snippet tags aren't there, and the form's input POST never gets processed. Another problem with this would come up when the snippet's output depends on variable external circumstances. If the snippet's output is cached along with the rest of the document, its output will never be changed, no matter how the external condition changes. We'll see this more clearly a bit further on when we analyze the creation of a new snippet.

This is where cached snippet tags come into play. Now, it is possible to get much the same effect by simply setting the entire document to be uncached. But in that case, every time the page is requested it will have to be fetched from the database and generated from the beginning. Calling a snippet uncached gives us the best of both worlds. When the parser comes upon uncached snippet tags, it simply converts them to ordinary cached snippet tags, then continues without evaluating the snippet code. The document will be cached with the snippet tags still unprocessed. The document then gets one more pass through the parser for a bit of post-processing, at which point the now-uncached snippet is detected and processed. The completed document is sent off to the server. The next time the document is requested, it is taken from the cache instead of the database. It gets a pass through the parser for post-processing (skipping the whole fetch-from-the-database and page generation part), where the snippet is evaluated and its output inserted. In this way, a snippet's dynamic functionality is preserved without losing the advantage of not having to make database calls and generate the entire document from scratch.

Because of the way documents are processed in the Evolution parser, particularly the strict order in which elements are evaluated, this cached/uncached distinction has a second benefit. If a snippet has another snippet as the value of any of its parameters (more about snippet parameters later), it will fail. The main, outer snippet is detected and immediately sent to the eval() function, complete with its parameter value still set to snippet tags. The PHP eval() function has no idea what that is, and tries to use the inner snippet tags as the actual value of that parameter, which is meaningless to the snippet code. What the snippet code needs is the value of that inner snippet. So the question is, how to tell the MODx parser to evaluate the inner snippet and insert its value into the outer snippet's parameter before evaluating the outer, main snippet? The trick is to call the outer snippet uncached, and the inner snippet cached. Now, the outer snippet just gets its tags converted, but it isn't evaluated at this time. The inner snippet is processed next, and its output inserted in place. On the next pass through the parser, the outer snippet is finally processed, but now its parameter value is something it can use. This is a rather awkward hack and sometimes doesn't work as expected, but it's usually better than the alternative of using wrapper snippets with lots of runSnippet() function calls. Of course, MODx 2.0 Revolution handles snippet processing differently and this problem doesn't even appear in the first place.

Snippet Parameters

There are three ways to feed parameters to snippets. The first is to use the Properties tab when creating the snippet. This is used for any parameter values that will be global across all documents using the snippet. A common use of it is to specify the location of the external files a snippet may require, such as images, javascript or included .php files.

The second, most common way, is to include the parameters in a name=value style inside the snippet tags. These mimic the form of an HTML query string:

[[SnippetName? &parameter1=`value1` &parameter2=`value2`]]

The entire content of the snippet tags must be on one line. They can be separated with spaces, but there cannot be any newlines.

The value of the parameters cannot include ? (question mark) or = (equal sign) or & (ampersand) since these are used as delimiters for the parameters themselves. How to work around these restrictions will be discussed later.

Note that the values are enclosed in backticks. These are NOT single quotes! The backticks aren't required unless the value has spaces in it, but it's a good habit to get into. Also, the leading parameter doesn't require the & if it immediately follows the ? at the end of the snippet's name, with no space between them. Again, it's a good habit to get into. Most beginner errors with snippets are caused by mistakes with one of these issues.

The third way is to use an external config file. This file is a standard .php file, with the parameters defined as PHP variables:

<?php
// parameter set for MySnippet
$parameter1 = 'value1';
$parameter2 = 'value2';
?>

When using an external config file, the snippet would have one parameter, indicating the name of the config file to use. The snippet's code would then include that file (if the parameter is set in the snippet tags).

Often a large snippet is broken into the snippet code and one or more included external .php files. One major advantage to this is keeping the site cache file as small as possible, since snippet code in the database is cached there, by using a snippet with one line to include the bigger .php file. When this is the case, the first (Properties setting) option is very useful for allowing the path to the snippet's files to be defined in the one place.

In all three cases, the parameters and their values are available within the snippet code as ordinary PHP variables.

There are two ways to handle parameters that need to include the reserved characters =, &, and ?. This may occur in snippets like feed readers, where a URL with these characters must be passed as a parameter. One way would be to use a wrapper snippet, calling our feed reader snippet via the $modx->runSnippet() function:

$modx->runSnippet('FeedReader', array('url'=>'http://www.feedsource.com?feed=myfeed&rows=10');

The other way requires the use of replacements, commonly something like |xe| for equal sign, |xa| for ampersand, and |xq| for question marks.

[!FeedReader? &url=`http://www.feedsource.com|xq|feed|xe|myfeed|xa|rows|xe|10`!]

Then the snippet code converts these into the proper characters.

if (isset($url)) $params['url'] = str_replace(array('|xq|','|xe|','|xa|'), array('?','=','&'), $url);

Installing Snippets

Many snippets are simply small blocks of PHP code, and are installed with one step, in the Elements -> Manage Elements section of the Manager, in the Snippets tab. Click the New Snippet link, and the form for creating a new snippet will be displayed.

Create Snippet

Fill in the name and description, and copy/paste the snippet's code into the code field. The snippet is now ready to use; add the snippet's tags in your template, a document's content, a chunk; wherever you want the snippet's output to appear on your page.

Other snippets are more complex, some even approaching full-blown web applications in their own right. These snippets will have PHP code that needs to be pasted into a new snippet's code field, but they will also have external files that need to be uploaded to your site. Usually these files will be uploaded to your site's assets/snippets directory, in their own subdirectory. The snippet should contain documentation on how to install the snippet, and what parameters it uses. Most of the major snippets also have entire support sites, wiki documentation, and of course the forums to help in using them.

Comment On This Article

Do you find something unclear? Did I miss something or get something wrong? What do you like or not like about this chapter? Please do not ask for help here; use the forums if you need help.

If you have trouble reading the code, click on the code itself to generate a new random code.
Sun November 29, 2009, 01:22:48
No, why should it be? It's stored as raw text, and isn't even recognized as PHP code until it's passed to the PHP eval() function.
Fri November 13, 2009, 11:33:38
Isn't storing php code in the database a bad practice?