Recently, there has been a lot of discussion in the MODX English-speaking community on the topic “how do we live on”. Everyone is discussing the future (in a few years, I suppose) major version 3, but for now we are improving the current one with our additions.
A fresh event that I would like to share with a wide audience is the release of the new version of pdoTools with the
Fenom template engine , which will allow you to completely get rid of the clutter of tags in terms of chunks and rewrite them in simple and understandable template language.
The procedure does not require changes to the site, just upgrade pdoTools to version 2.0 and you can use the
new syntax . The best part is that MODX tags are great with Fenom and work together without any problems. A simple example for the seed:
{if $parent == 3} [[!pdoMenu?parents=`0`]] {else} [[!pdoResources?parents=`1,2,3`]] {/if}
Under the cut a huge amount of information about the pdoTools parser, which I have never collected in one place.
')
So, the pdoTools parser is a separate class that is specified in the MODX system settings and intercepts the processing of tags on the page.
In older versions of the component, the inclusion of the parser had to be confirmed during installation, but from version 2.1.1-pl it is enabled by default. If, for some reason, it does not suit you - remove the system settings
- parser_class - the name of the parser class
- parser_class_path - parser class path
By default, MODX does not have these settings, they are needed only to connect a third-party parser, as in our case.
Principle of operation
pdoParser can be used in two cases:
- When rendering a chunk with a snippet, this happens always and in all snippets using pdoTools, regardless of the system settings.
- when rendering the page - only if the parser is enabled in the system settings.
Chunk processing
In the pdoTools class, there are 2 methods for this, very similar to those in the modX class:
- getChunk - complete chunk processing, can use native MODX parser
- parseChunk - only replacing placeholders with values, modParser is not called
The main feature of these methods is that the
_loadChunk protected method is used to load the chunk, which can not only load the chunk from the database, but also turns arbitrary strings into it.
Chunk options
So, both pdoTools methods support the following types of chunk names:
@INLINE or @CODE
One of the most popular options is to specify the body of the chunk right on the page. For example:
[[!pdoResources? &parents=`0` &tpl=`@INLINE <p>{{+id}} - {{+pagetitle}}</p>` ]]
In such an indication there is a feature that many people do not think about - all placeholders inside the chunk will be processed by the parser
before calling the snippet.
That is, if you call a snippet on the page like this:
[[!pdoResources? &parents=`0` &tpl=`@INLINE <p>[[+id]] - [[+pagetitle]]</p>` ]]
and in the memory of the system, placeholders
[[+id]]
or
[[+pagetitle]]
, then the already processed chunk will come to the snippet and you will get the same lines on the page, like:
15 - 15 - 15 -
Just the same values that some other snippet put before. That is why in the example we have such unusual placeholders -
{{+}}
instead of
[[+]]
. The system parser does not touch them, and pdoTools replaces them with normal ones during operation.
You can use braces as a frame for placeholders in all pdoTools chunks - it will turn them into
[[+]]
when it is loaded.
For the same reason, calls to snippets and filters in INLINE chunks will never work for you.
This will not work like this :
[[!pdoResources? &parents=`0` &tpl=`@INLINE <p>[[+id]] - [[+pagetitle:default=` `]]</p>` ]]
And so - no problem
[[!pdoResources? &parents=`0` &tpl=`@INLINE <p>{{+id}} - {{+pagetitle:default=` `}}</p>` ]]
Remember this nuance when using inline chunks.
@FILE
Many people accuse MODX of not being able to store chunks in files and force them to work with the database once again. This is inconvenient for the version control system, and slower.
Since version 2.2, MODX
offers to use static elements for these purposes , but for a number of reasons, this method may still be less convenient than direct work with files.
pdoTools opens this feature when specifying @FILE:
[[!pdoResources? &parents=`0` &tpl=`@FILE resources/mychank.tpl` ]]
For security reasons, you can only use files with the
html and
tpl extensions, and only from a specific, predefined directory. The default is:
/assets/elements/chunks/
.
You can specify your own directory for files using the
&tplPath
parameter:
[[!pdoResources? &parents=`0` &tpl=`@FILE resources/mychunk.tpl` &tplPath=`/core/elements` ]]
The file will be downloaded from the
/core/elements/resources/mychunk.tpl
file from the site root.
@TEMPLATE
This type of chunk allows you to use system templates (i.e. modTemplate objects) to form the output.
[[!pdoResources? &parents=`0` &tpl=`@TEMPLATE Base Template` ]]
If an empty template is specified and in the selected records there is a
template
field with id or template name, the record will be wrapped in this template:
[[!pdoResources? &parents=`0` &tpl=`@TEMPLATE` ]]
This is such an analogue of the
renderResources snippet.
When displaying a template, you can also specify a set of parameters (as with snippets):
[[!pdoResources? &parents=`0` &tpl=`@TEMPLATE Base Template@MyPropertySet` ]]
Then the values from this set will be inserted into the template.
Ordinary chunks
This is the default mode that loads the chunk from the database:
[[!pdoResources? &parents=`0` &tpl=`MyChunk` ]]
Similarly, parameter sets are supported:
[[!pdoResources? &parents=`0` &tpl=`MyChunk@MyPropertySet` ]]
These ways of loading chunks work
in all native pdoTools snippets and in all others that use the methods pdoTools
getChunk
and
parseChunk
.
GetChunk method
The declaration of this method looks like this:
getChunk(string $chunkName, array $properties, bool $fastMode = false)
The method loads the specified chunk (following the instructions of @BINDING, if any) and completely reverses it, replacing all placeholders with the values passed ($ properties parameter).
The third parameter,
fastMode
cuts out all the remaining raw placeholders so that there are no unnecessary tags on the page. If this is not done, the parser will try to recursively parse these tags (up to 10 iterations by default), which can lead to slower work.
A recursive parser is one of the advantages of MODX and specially left tags are very often found in the logic of the system snippets. Therefore,
fastMode
disabled by default and you need to use it only if you are sure of what you are doing.
The pdoTools parser will not call the system parser if it could independently disassemble all placeholders. If there are any calls to filters or snippets in the chunk, then the work is transferred to modParser, which requires additional time for processing.
ParseChunk method
And this method is declared like this:
parseChunk(string $name, array $properties, string $prefix = '[[+', string $suffix = ']]')
It also creates a chunk from the specified name, parsing @BINDING, if it exists, and then simply replaces placeholders with values, without any special processing.
This is the easiest and fastest way to design data in chunks.
Page processing
If pdoParser is enabled in the settings, then it is also called to process the entire page when outputting it to the user.
When using this parser, all MODX chunks and add-ons are processed a little faster. Only "a little" because it does not assume the conditions and filters, processing only unpretentious tags, such as
[[+id]]
and
[[~15]]
. However, it does it faster than modParser, because it does not create unnecessary objects.
In addition to the possible increase in speed, you also get new opportunities for convenient data output from various resources.
Tags fastField
At the end of 2012, a small plugin
was introduced to the public with the addition of new tags to the MODX parser, which then grew into
the fastField component .
It adds to the system the processing of additional placeholders, such as
[[#15.pagetitle]]
.
With the permission of the author , this functionality is already included in pdoParser, and even slightly expanded.
All fastField tags start with
#
and then contain either the
id of the desired resource , or the name of the global array.
Displaying the usual resource fields:
[[#15.pagetitle]] [[#20.content]]
TV resource settings:
[[#15.date]] [[#20.some_tv]]
Fields of miniShop2 products:
[[#21.price]] [[#22.article]]
Arrays of resources and goods:
[[#12.properties.somefield]] [[#15.size.1]]
Superglobal arrays:
[[#POST.key]] [[#SESSION.another_key]] [[#GET.key3]] [[#REQUEST.key]] [[#SERVER.key]] [[#FILES.key]] [[#COOKIE.some_key]]
You can specify any fields in the arrays:
[[#15.properties.key1.key2]]
If you do not know what values are inside the array - just specify it and it will be printed in full:
[[#GET]] [[#15.colors]] [[#12.properties]]
FastField tags can be combined with MODX tags:
[[#[[++site_start]].pagetitle]] [[#[[++site_start]]]]
Fenom Template
Support for the Fenom template engine
appeared in pdoTools since version 2.0 , after which it began to require PHP 5.3+.
It works much faster than the native modParser, and if you rewrite your chunk so that it does not contain a single MODX tag, then modParser will not run at all. In this case, of course, the simultaneous operation of both old tags and new ones in one chunk is
allowed .
The following system settings affect the processing of the template engine:
- pdotools_fenom_default - enables processing of pdoTools chunks via Fenom. Enabled by default.
- pdotools_fenom_parser - includes the template processing of all site pages. That is, not only chunks, but also templates.
- pdotools_fenom_php - includes support for PHP functions in the template engine. A very dangerous feature, as any manager will gain access to PHP directly from the chunk.
- pdotools_fenom_modx - adds the system variables
{$modx}
and {$pdoTools}
to Fenom templates. It is also very dangerous - any manager can manage MODX objects from the chunks. - pdotools_fenom_options - JSON string with an array of settings according to official documentation . For example:
{"auto_escape":true,"force_include":true}
- pdotools_fenom_cache - caching of shared templates. It makes sense only for complex chunks on work sites, disabled by default.
So, by default, Fenom is enabled to work only in chunks that pass through pdoTools. It is completely safe and system managers do not get any additional features, except for a more convenient syntax and high speed.
The inclusion of
pdotools_fenom_parser allows you to use the Fenom syntax directly in the content of documents and page templates, but there is one nuance - the template engine may react incorrectly to curly brackets, which are very fond of MODX.
In such cases, the author recommends using the {
ignore } tag.
If you plan to enable Fenom globally for the entire site, you need to check whether it works fine on all pages.
Syntax
First I advise you to read the
official documentation , and then we will look at the syntax with reference to MODX.
All variables from snippets are passed to the chunk as it is, so rewriting old chunks with the new syntax is a real pleasure.
MODX | Fenom |
[[+ id]] | {$ id} |
[[+ id: default = `test`]] | {$ id?: 'test'} |
[[+ id: is = ``: then = `test`: else =` [[+ pagetitle]] `]] | {$ id == ''? 'test': $ pagetitle} |
To use more complex entities, pdoParser provides a utility variable
{$ _modx} , which gives safe access to some variables and methods of the system.
MODX | Fenom |
[[* id]] | {$ _modx-> resource.id} |
[[* tv_param]] | {$ _modx-> resource.tv_param} |
[[% lexicon]] | {$ _modx-> lexicon ('lexicon')} |
[[~ 15]] | {$ _modx-> makeUrl (15)} |
[[~ [[* id]]]] | {$ _modx-> makeUrl ($ _ modx-> resource.id)} |
[[++ system_setting]] | {$ _modx-> config.system_setting} |
In addition, variables are available to you:
{$ _modx-> config} - system settings
{$_modx->config.site_name} {$_modx->config.emailsender} {$_modx->config['site_url']} {$_modx->config['any_system_setting']}
{$ _modx-> user} - an array of the current user. If it is authorized, then the data from the profile is added:
{if $_modx->user.id > 0} , {$_modx->user.fullname}! {else} . {/if}
{$ _modx-> context} - an array with the current context
{$_modx->context.key}
{$ _modx-> resource} - an array with the current resource, you have already seen this in the examples above
{$_modx->resource.id} {$_modx->resource.pagetitle} {$_modx->makeUrl($_modx->resource.id)}
{$ _modx-> lexicon} - an object (not an array!)
modLexicon , which can be used to load arbitrary dictionaries:
{$_modx->lexicon->load('ms2gallery:default')} ms2Gallery: {$_modx->lexicon('ms2gallery_err_gallery_exists')}
A separate function
{$_modx->lexicon()}
is responsible for the output of records.
Placeholders with a dot
Fenom uses a point to access the value of the array, while MODX usually extracts placeholders from the arrays. Accordingly, for the tags [[+ tag.sub_tag]] there are no analogues in Fenom.
Therefore, for such placeholders you need to use the second service variable -
{$ _pls} :
{$_pls['tag.subtag']}
Output snippets and chunks
The variable
{$_modx}
is actually a simple and safe
microMODX class.Therefore, snippets and chunks are called like this:
{$_modx->runSnippet('!pdoPage@PropertySet', [ 'parents' => 0, 'showLog' => 1, 'element' => 'psoResources', 'where' => ['isfolder' => 1], 'showLog' => 1, ])} {$_modx->getPlaceholder('page.total')} {$_modx->getPlaceholder('page.nav')}
As you can see, the syntax almost completely repeats PHP, which opens up new possibilities. For example, you can specify arrays, instead of JSON strings.
By default, all snippets are cached, but you can add them
!
in front of the name - like in MODX tags.
If the native MODX method is used to call the snippet, then pdoTools is started to output chunks, and you can use all its features:
{$_modx->getChunk('MyChunk@PropertySet')} {$_modx->parseChunk('MyChunk', [ 'pl1' => 'placeholder1', 'pl2' => 'placeholder2', ])} {$_modx->getChunk('@TEMPLATE Base Template')} {$_modx->getChunk('@INLINE : {$_modx->config.site_name} ')} {$_modx->getChunk( '@INLINE : {$var}', ['var' => ''] )} {$_modx->getChunk(' @INLINE : {$_modx->runSnippet("pdoResources", [ "parents" => $parents ])} : {$_modx->getPlaceholder("total")} ', ['parents' => 0] )}
The examples above are a little crazy, but they work for themselves.
Caching control
In the
{$ _modx} object, a modX :: cacheManager service is available, which allows you to set an arbitrary time for caching snippets to be called:
{if !$snippet = $_modx->cacheManager->get('cache_key')} {set $snippet = $_modx->runSnippet('!pdoResources', [ 'parents' => 0, 'tpl' => '@INLINE {$id} - {$pagetitle}', 'showLog' => 1, ])} {set $null = $_modx->cacheManager->set('cache_key', $snippet, 1800)} {/if} {$snippet}
You can view this cache in
/core/cache/default/
, in the example it is saved for 30 minutes.
set $null = ...
needed so that
cacheManager->set
does not
cacheManager->set
1 (that is, true) to the page.
And you can also run system processors (if you have enough rights):
{$_modx->runProcessor('resource/update', [ 'id' => 10, 'alias' => 'test', 'context_key' => 'web', ])}
Authorization check
Since there is no object with the user in
{$_modx}
, the methods for checking authorization and access rights are moved directly to the class:
{$_modx->isAuthenticated()} {$_modx->hasSessionContext('web')} {$_modx->hasPermission('load')}
Remaining methods
These methods should be familiar to all MODX developers, so I will simply show them with examples:
{$_modx->regClientCss('/assets/css/style.css')} {$_modx->regClientScript('/assets/css/script.js')} {$_modx->sendForward(10)} {$_modx->sendRedirect('http://yandex.ru')} {$_modx->setPlaceholder('key', 'value')} {$_modx->getPlaceholder('key')} {if $res = $_modx->findResource('url-to/doc/')} {$_modx->sendRedirect( $_modx->makeUrl($res) )} {/if}
Template extension
Using the Fenom template engine allows you to include some chunks (templates in others) and even expand them.
For example, you can simply load the contents of the chunk:
{include ' '} modTemplate {include 'template: '} {include 'chunk@propertySet'} {include 'template:Name@propertySet'}
Read more about {
include } in the official documentation.
A much more interesting feature is the {
extends } of templates, it requires the
pdotools_fenom_parser system settings
enabled .
We write the basic template "Fenom Base":
<!DOCTYPE html> <html lang="en"> <head> {include 'head'} </head> <body> {block 'navbar'} {include 'navbar'} {/block} <div class="container"> <div class="row"> <div class="col-md-10"> {block 'content'} {$_modx->resource.content} {/block} </div> <div class="col-md-2"> {block 'sidebar'} Sidebar {/block} </div> </div> {block 'footer'} {include 'footer'} {/block} </div> </body> </html>
It includes the usual chunks (in which, by the way, the usual MODX placeholders from the
Theme.Bootstrap component) and defines several
{block}
blocks that can be expanded in another template.
Now we write "Fenom Extended":
{extends 'template:Fenom Base'} {block 'content'} <h3>{$_modx->resource.pagetitle}</h3> <div class="jumbotron"> {parent} </div> {/block}
So you can write one basic template and expand it with children.
Likewise, you can write and expand chunks, just note that you need to specify the prefix
template: for working with modTemplate, but not for chunks - they work by default in all
{include}
and
{extends}
.
Performance testing
Create a new site and add 1000 resources to it like this in a console script:
<?php define('MODX_API_MODE', true); require 'index.php'; $modx->getService('error','error.modError'); $modx->setLogLevel(modX::LOG_LEVEL_FATAL); $modx->setLogTarget(XPDO_CLI_MODE ? 'ECHO' : 'HTML'); for ($i = 1; $i <= 1000; $i++) { $modx->runProcessor('resource/create', array( 'parent' => 1, 'pagetitle' => 'page_' . rand(), 'template' => 1, 'published' => 1, )); }
Then we create 2 chunks:
modx
and
fenom
with the following contents, respectively:
<p>[[+id]] - [[+pagetitle]]</p>
and
<p>{$id} - {$pagetitle}</p>
And we add two console test scripts. For native MODX parser
<?php define('MODX_API_MODE', true); require 'index.php'; $modx->getService('error','error.modError'); $modx->setLogLevel(modX::LOG_LEVEL_FATAL); $modx->setLogTarget(XPDO_CLI_MODE ? 'ECHO' : 'HTML'); $res = array(); $c = $modx->newQuery('modResource'); $c->select($modx->getSelectColumns('modResource')); $c->limit(10); if ($c->prepare() && $c->stmt->execute()) { while ($row = $c->stmt->fetch(PDO::FETCH_ASSOC)) { $res .= $modx->getChunk('modx', $row); } } echo number_format(microtime(true) - $modx->startTime, 4), 's<br>'; echo number_format(memory_get_usage() / 1048576, 4), 'mb<br>'; echo $res;
And for pdoTools:
<?php define('MODX_API_MODE', true); require 'index.php'; $modx->getService('error','error.modError'); $modx->setLogLevel(modX::LOG_LEVEL_FATAL); $modx->setLogTarget(XPDO_CLI_MODE ? 'ECHO' : 'HTML'); $pdoTools = $modx->getService('pdoTools'); $res = array(); $c = $modx->newQuery('modResource'); $c->select($modx->getSelectColumns('modResource')); $c->limit(10); if ($c->prepare() && $c->stmt->execute()) { while ($row = $c->stmt->fetch(PDO::FETCH_ASSOC)) { $res .= $pdoTools->getChunk('fenom', $row);
Since pdoTools understands both syntaxes, there are 2 tests for it - in the MODX tag mode, and in the Fenom mode.In scripts there is an indication limit = 10, then in the table I give numbers with its increase:Limit | MODX | pdoTools (MODX) | pdoTools (Fenom) |
ten | 0.0369s 8.1973mb | 0.0136s 7.6760mb | 0.0343s 8.6503mb |
100 | 0.0805s 8.1996mb | 0.0501s 7.6783mb | 0.0489s 8.6525mb |
500 | 0.2498s 8.2101mb | 0.0852s 7.6888mb | 0.0573s 8.6630mb |
1000 | 0.4961s 8.2232mb | 0.1583s 7.7019mb | 0.0953s 8.6761mb |
And now, let's complicate the chunks a bit - let's add to them the generation of links for the resource and the output menutitle
: <p><a href="[[~[[+id]]]]">[[+id]] - [[+menutitle:default=`[[+pagetitle]]`]]</a></p>
and
<p><a href="{$_modx->makeUrl($id)}">{$id} - {$menutitle ?: $pagetitle}</a></p>
Limit | MODX | pdoTools (MODX) | pdoTools (Fenom) |
ten | 0.0592s 8.2010mb | 0.0165s 7.8505mb | 0.0346s 8.6539mb |
100 | 0.1936s 8.2058mb | 0.0793s 7.8553mb | 0.0483s 8.6588mb |
500 | 0.3313s 8.2281mb | 0.2465s 7.8776mb | 0.0686s 8.6811mb |
1000 | 0.6073s 8.2560mb | 0.4733s 7.9055mb | 0.1047s 8.7090mb |
As you can see, chunk processing via pdoTools is faster in all cases.At the same time, it is noticeable that the Fenom chunks have some minimum for the start, which is due to the need to compile the template.Conclusion
Let's summarize the features of the pdoTools parser:- Quick work
- Loading chunks from various sources, including files
- FastField tag support
- Fenom template engine support
- Pattern Inheritance
- Template extension
- Secure access to advanced MODX features
At the moment, pdoTools has been downloaded more than 40,000 times from the official repository and over 10,000 from the modstore.pro repository , which allows us to hope for the wide distribution of new template technologies in MODX.Thanks a lot to haborayer aco for a great template engine!