[EVO] Twig

Agel_Nash поделился плагином, который позволяет поменять шаблонизатор MODX на Twig. Я решил посмотреть, что к чему, и сразу столкнулся с проблемами. Повозившись какое-то время, мне все же удалось запустить этот плагин, о чем и хочу поведать.

Установка

1. Скачиваем и устанавливаем develop-версию MODX Evo Custom отсюда: github.com/dmi3yy/modx.evo.custom/archive/develop.zip
2. Устанавливаем Composer. Здесь сразу скажу, что в Composer я ничего не смыслю и то, что мне вообще удалось им воспользовать уже для меня прогресс (:
Руководствуясь документацией, я выполнил на сервере команды:

curl -sS https://getcomposer.org/installer | php
mv composer.phar /usr/local/bin/composer

Composer установлен и работает:

user@modx-server:/var/www/twig$ composer --version
Composer version 1.0-dev (0ec86be5e988261e8b625ac696d566afa2c35faf) 2015-05-31 11:54:08

3. Теперь скачиваем и устанавливаем плагин отсюда: github.com/Pathologic/EvoTwig
Если ставить вручную, то нужно сразу отметить флажок «Плагин отключен», иначе админка перестанет работать.
4. Скачиваем и устанавливаем DocLister — иначе Composer будет ругаться на отсутствующие папки.
5. Возвращаемся к Composer. На сервере нужно перейти в папку /manager/composer и выполнить там команду:

composer install

После этого появится папка vendor и файл composer.lock
6. Можно включать плагин.

Как пользоваться

Переходим в папку assets/element/template и пишем там шаблоны (:
Здесь важно правильно именовать файлы:
  • tpl-3_doc-1.twig.html — этот файл будет загружен для страницы c id=1 и шаблоном id=3;
  • doc-3.twig.html — для страницы с id=1
  • tpl-3.twig.html — для всех страниц с шаблоном id=3;
С плагином я сразу положил пример, из которого понятно как использовать переменные страницы, переменные конфигурации, вызывать сниппеты и чанки:

Дальше я уже не вникал, потому что с Twig не знаком.
Судя по коду плагина должны также работать конструкции типа:

{{ '[*id*]' | modxParser }}

Но у меня не работают (вывод прекращается на такой конструкции), надеюсь Agel_Nash прояснит; ну и хотелось бы увидеть какой-нибудь более основательный пример с тем же DocLister и кешерами (:

Документация Twig по-русски здесь: x-twig.ru/

124 комментария

avatar
Попробовал c DocLister:

<?php
/*
 * runDL
 */
$params['api'] = 1;
$out = $modx->runSnippet((isset($_snippet) ? $_snippet : 'DocLister'),$params);
return json_decode($out, true);
?>



<ul>
{% for id,fields in runSnippet ('runDL',{parents:0}) %}
        <li>{% if id == modx.config.site_start %}
               <span style="color:red;">{{ id }}. {{fields.pagetitle}}</span>
            {% else %}
                {{ id }}. {{fields.pagetitle}}
            {% endif %}
        </li>
    {% endfor %}
</ul>

Сразу виден плюс, что можно отказаться во многих случаях от prepare-сниппетов, не говоря уже про всякий if. Наверное, можно вообще от сниппетов отказаться, загружать класс и вызывать его методы…
avatar
Я конечно не очень разбираюсь в теме, потому прошу меня простить. Время генерации страницы будет отличатся от генерации стандартного шаблона?
avatar
Я сам пока не в теме, поэтому сошлюсь на Евгения:
Я сегодня на одном проекте ради эксперимента отключил кеш MODX и подменил парсер на Twig. Теперь теги MODX обрабатываются только внутри сниппетов использующих класс DLTemplate, а за все остальное отвечает Twig. При этом:
— Увеличилась скорость загрузки страницы
— Проблема с кешем отпала сама собой (Twig кеширует скомпилированный шаблон + при помощи дополнения можно кешировать отдельные блоки, которые могут быть общими для всех страниц)
— Повысился уровень безопаности сайта, т.к. теперь нет необходимости экранировать MODX теги в данных поступающих от пользователя
— Решился вопрос с версионностью шаблонов (LoadElement был первым шагом)
— Упростился процесс редизайна проекта даже «на горячую».

Ну а чтобы не быть голословным, вот скорость обработки объемной страницы:
— MODX парсер со стандартным кешем: 0.3 сек
— MODX парсер без кеша: 0.9 сек
— Нативный PHP шаблонизатор без кеша MODX: 0.8 сек
— Twig со встроеным кешированием и без кеша MODX, но с кешированием отдельных блоков: 0.2 сек
То есть как минимум не хуже получится, но при этом возможностей и удобств гораздо больше. Мне вообще был интересен не столько Twig, сколько сама реализация подмены шаблонизатора (очень простая, как оказалось) — вместо Twig можно взять Smarty или тот же Fenom. Если пойти дальше, то, используя вместо сниппетов классы (типа как процессоры в Revo), можно будет вообще выкинуть хлам, который тянется еще с Etomite, но оставив привычную админку — как мне кажется.
Плохо только, что люди по ощущениям и DocLister-то не осилили, что уж говорить про это решение.
avatar
При правильном кешировании скорость загрузки сайта увеличивается. При неправильном остается практически без изменений.

Задача подмены шаблонизатора действительно простая после введения новых событий для плагинов. Что же касается smarty/fenom и прочего — я уже пояснял. Они существенно проигрывают twig-у по ряду пунктов во главе которых идет кеширование. А поскольку кеширование это проблема и MODX Evolution, то используя twig мы убиваем двух зайцев: получаем мощный шаблонизатор в комплекте с кешером от симфони, который можно использовать даже вне шаблонизатора, т.к. становится доступным $modx->cache.
$modx->cache->save('cache_id', 'my_data');
$modx->cache->contains('cache_id');
$modx->cache->fetch('cache_id');

Так что, что бы ни говорили про другие шаблонизаторы. Как бы я не любил smarty. Я считаю, что twig просто идеально дополняет Evo.
avatar
Чтобы сильно все не усложнять, я пока вызывал DocLister в стиле $modx->runSnippet. Т.е. даже если человек не знаком с twig, но знаком с MODX API. Он должен быстро понять что и как тут работет.

{{ runSnippet('DocLister', {
   'idType' : 'parents',
   'parents' : 62,
  'tpl' : '@CODE: <option value="[+url+]"[+selected+]>[+title+]</option>',
  'ownerTPL' : '@CODE: <form method="POST"><div class="feald" id="changeType"><select name="type" id="change-type-filter-reviews"><option value="0" disabled selected>Выберите категорию отзывов</option>[+dl.wrap+]</select></div></form>',
  'prepare': '\\ProjectName\\DLPrepare::defaultSelectType'
}) | raw }}

Из минусов такого подхода — в качестве prepare нельзя указывать замыкания. Хотя с другой стороны — это даже плюс, т.к. все prepare аккумулируются в одном классе со статичными методами.

Еще один момент — любой вызов сниппета по идеологии MODX возвращает строку с HTML кодом. И твиг ее отображает обычным текстом. Поэтому добавляем приписку raw.

Стоит обратить внимание, что MDOX парсер отключен. И если вы до сих пор ведете разработку в стиле «Сниппет берет ченк в котором вызывается сниппет использующий другой чанк, где еще подключен один чанк», то помимо raw потребуется добавить еще и modxParser. В итоге вызов будет примерно следующий.
{{  runSnippet('example', {'param': 'value'}) | modxParser | raw }}

В моих шаблонах таких вызовов нет, т.к. я стараюсь делать так, чтобы любой сниппет возвращал конечный html без вложенных не разобранных modx тегов.

Вот простой пример проверки авторизованности пользователя
{% if(modx.getLoginUserID('web')) %}
  <span class="subs"><a href="/[~124~]" title="Оставить отзыв">Оставить отзыв</a></span>
  <a class="all_clients" href="/[~61~]">Все клиенты</a>
{% else %}
  <span class="subs"><a href="/[~62~]" title="Все отзывы">Все отзывы</a></span>
{% endif %}


С условием все ясно — она подчиняется правилам нативного php, а вот про URL поясню. Замена URL происходит вне парсера MODX. Поэтому [~~] это единственный тег который вы можете безболезненно использовать по старой памяти. Но на мой взгляд это даже плюс — не нужно мудрить маршрутизацией.

После вызова DocLister'a с пагинатором, создается плейсхолдер pages. Раньше после вызова мы добавляли [+pages+], а сейчас
{{ modx.getPlaceholder('pages') | raw }}


Еще одна довольно распространенная ситуация — вывести pagetitle, если menutitle пуст. Тут 3 варианта:
— использовать PHX, а хорошие парни его не любят.
— использовать сниппет прослойку, который будет принимать и pagetitle и menutitle. Но если в значении одного из параметров встретиться тильда (`), то вызов видоизменится и на выходе вы получите не тот результат.
— использовать сниппет под задачу, в котором проврять данные в modx->documentObject. Но если где-то понадобится заменить pagetitle на longtitle или на значение какой-то ТВшки, то есть риск раздуть кол-во сниппетов и самим в них запутаться.
С нормальными шаблонизаторами эта проблема решается легко и просто
{{ documentObject.menutitle | default(documentObject.pagetitle) }}


Идем дальше — кеширование. Тут тоже все просто. Чтобы закешировать какой-то блок на определенно время, нужно сделать так:
{% cache 'map' {time: 86400} %}
  Карта: {{ runSnippet('mapBuild') | raw }} 
{% endcache %}


А чтобы закешировать навсегда для всех страниц (пока кеш не будет скинут) — так:
{% cache 'footer' {key: 'footer'} %}
  © {{ modx.runSnippet('copyright', { 'date': 2014, 'sep' : '‐' }) | raw }}. {{ modx.getConfig('site_name') }}.
{% endcache %}


Чтобы сделать кеш для конкретной страницы (актуально для меню, где шаблон пункта активной страницы отличается):
{% cache 'menu' {key: 'page' ~ documentObject.id} %}
{{ modx.runSnippet('DLBuildMenu', {
  'idType' : 'parents',
  'parents' : 0,
  'maxDepth' : 2,
  'TplMainOwner' : '@CODE: <div class="primary"><div class="bl_center"><ul>[+dl.wrap+]</ul></div></div>',
  'TplSubOwner' : '@CODE: <ul>[+dl.wrap+]</ul>',
  'TplOneItem' : '@CODE: <li class="epanded [+dl.class+]"><a href="[+url+]" title="[+e.title+]">[+title+]</a>[+dl.submenu+]</li>',
  'noChildrenRowTPL' : '@CODE: <li class="[+dl.class+]"><a href="[+url+]" title="[+e.title+]">[+title+]</a>[+dl.submenu+]</li>',
}) | raw }}
{% endcache %}
avatar
Спасибо большое, решение просто великолепное! Собрал сайт. Обновил до MODX Evo Custom v1.1b-d7.1.1 получаю ошибку:
fatal error line 125 ... replaceTemplateTwig $modx->documentContent = $tpl->render(array(
                'modx' => $modx,

сейчас дословно не помню — с другого компа без окружения пишу.
Не могу понять, в чем проблема. Накатил develop версию, но теперь она, похоже, слилась с обновленной сборкой.
avatar
Наверное подключали автозагрузчик в config.inc.php, поэтому ошибка. Сейчас исправлю исправил плагин, чтобы не было этого шаманства.
Комментарий отредактирован 2015-06-11 14:14:02 пользователем Pathologic
avatar
Здравствуйте! Подскажите пожалуйста, как подгрузить класс где обрабатывается prepare непосредственно из шаблона Twig, чтобы DocLister видел мой класс? Сейчас свой класс подгружаю через include_once в DocLister.abstract и в 'prepare' (в шаблоне Twig) указываю ссылку на статический метод
Комментарий отредактирован 2015-06-18 03:16:33 пользователем maximlit
avatar
как подгрузить класс где обрабатывается prepare непосредственно из шаблона Twig, чтобы DocLister видел мой класс
В assets/lib создать папки в соответствие с namespace класса, положить туда класс, и автозагрузчик все сам сделает.
avatar
Так не подгружается класс…
папка — ALLprepare
файл класса — allprepare.class.php
namespace класса ALLprepare
public static function combine(){.....}

опции runSnippet prepare:'\ALLprepare\ALLprepare::combine'
avatar
{{include_once('assets/lib/ALLprepare/allprepare.class.php') }}

пока так выхожу из положения…
avatar
Так не подгружается класс…
папка — ALLprepare
файл класса — allprepare.class.php
namespace класса ALLprepare
public static function combine(){.....}

опции runSnippet prepare:'\ALLprepare\ALLprepare::combine'
Комментарий отредактирован 2015-06-18 08:47:58 пользователем maximlit
avatar
Файл assets/lib/Pathologic/Prepare/Products.php — после его создания нужно в /manager/composer сделать composer update:

<?php namespace Pathologic\Prepare;
class Products {
    public static function prepare(array $data = array(), \DocumentParser $modx, $_DL, \prepare_DL_Extender $_extDocLister) {
        $data['rand'] = rand(5,25);
        return $data;
    }
}

Вызов:

{{ runSnippet('DocLister', {
        'idType' : 'parents',
        'debug' : 2,
        'parents' : 0,
        'tpl' : '@CODE: <h1>[+id+]. [+rand+]</h1>',
        'prepare': '\\Pathologic\\Prepare\\Products::prepare'
}) | raw }}

Результат:

49. 7
2. 21
1. 13
avatar
Спасибо, разобрался…
В целом, конечно, работа стала удобнее. Хотя бы даже возможностью практически на лету поменять не просто тему оформления, а все визуальное представление сразу. Но есть и минус…
Основной недостаток — невозможность (или я не разобрался как) передать параметр функции Twig {{runSnippet}).
Например, 3 вызова DocLister (&tpl=@CODE...) и с каждым вызовом один параметр (скажем &filter) меняется при прочих неизменных. Все что является шаблоном -@CODE, а порой это и 20 и 30 строк кратно укладывается в код. В macro этот вызов сниппета не упакуешь. В чанках в базе нет желания хранить, так как цель отвязать view.
Вариант решения — неизменяемый чанк подключать через @FILE. Почему то не работает…
Может знаете, как решить?
Комментарий отредактирован 2015-06-19 07:35:34 пользователем maximlit
avatar
Сам себе:
@FILE принимается DocLister'ом после правки 95 строки в DLtemplate.class Что-то с regex…
avatar
А если такой вариант — хранить не в чанках, а в статических свойствах класса?
avatar
О, об этом не подумал даже… Спасибо!
avatar
@FILE должен работать, судя по коду чанки должны храниться в assets/templates/ c расширением html. Мне вообще кажется, что лучше не сниппет вызывать, а загрузить классы DocLister и получать данные методом getDocs(), потом их выводить через Twig.
avatar
tpl:'@FILE: partial/chunk/mychunk.html'
В классе DLT эти условия видны, конечно…
проверил 3 раза с этим путем — не возвращал содержимое файла
Да, буду напрямую работать
Комментарий отредактирован 2015-06-19 08:51:57 пользователем maximlit
avatar
Вынужден приостановить использование Twig…
По непонятной мне причине не подхватываются параметры в вызове runSnippet. Именно parents:'[*id*]', '[*parents*]'.
Что касается getDocs() — все равно нужно изобретать контроллер (сниппет?)
avatar
А с чего бы им подхватываться, если эти [*id*] так тэгами и остаются — парсер же не работает. Нужно parents:documentObject.id и т.п.
avatar
Сниппет вызывается с фильтром | modxParser — другие теги в этом же вызове обрабатываются корректно, в частности, в tpl.
Проблема в том, что Twig, видимо, игнорирует * и ASC DESC, даже если они указаны как строка.
parents:documentObject.id — конечно, в первую очередь. Но {{}} внутри функции {{ }} вываливается в FatalError. Вхождения не предусмотрены. Если обернуть в '' то это просто строка… Это и есть проблема передачи параметров.
В Symfony контроллер скармливает переменные шаблонизатору, а здесь наоборот — шаблон указывает шаблонизатору — что и как обрабатывать. Так или я заблуждаюсь?
Комментарий отредактирован 2015-06-19 14:47:59 пользователем maximlit
avatar
Пусть есть такой сниппет:

//test
<?php
echo $input;
?>


<html>
<body>
    <h1>test</h1>
    {{ runSnippet('test', {
        'input' : '[*id*]',
}) | raw }}
</body>
</html>

Результат:

[*id*]

А если так:

<body>
    <h1>test</h1>
    {{ runSnippet('test', {
        'input' : documentObject.id,
}) | raw }}

То результат:

1


modxParser обрабатывает то, что вывела функция runSnippet, а не сам кусок шаблона с вызовом функции.
Вот еще пример с modxParser:

<html>
<body>
    <h1>test</h1>
    {% set pageid = '[*id*]' | modxParser %}
    {{pageid}}
</body>
</html>

Результат — тоже 1. Теперь убираем "| modxParser" и получаем снова [*id*] вместо 1.

Шаблонизатор, таким образом, просто выводит с обработками какими-то или без них.
avatar
В Symfony контроллер скармливает переменные шаблонизатору, а здесь наоборот — шаблон указывает шаблонизатору — что и как обрабатывать. Так или я заблуждаюсь?
Если я правильно понял вопрос, то ответ находится в коде плагина — вот скармливаются переменные в шаблон:

$modx->documentContent = $tpl->render(array(
	'modx' => $modx,
	'documentObject' => $modx->documentObject,
	'debug' => $debug,
	'_GET' => $_GET,
	'_POST' => $_POST,
	'_COOKIE' => $_COOKIE,
	'_SESSION' => $_SESSION
));
avatar
Безусловно, Вы правы…
В целом я так же рассуждаю. Сейчас ребутнул сервер — все работает. Пятница, однако.
avatar
Вроде все поставилось и скачалось в нужные папки. А на выходе в админке — во всех трех фреймах
Fatal error: Class 'Doctrine\Common\Cache\MemcacheCache' not found in D:\OpenServer\domains\twig.loc\assets\plugins\__autoload.php on line 14
avatar
Проверил на чистом сайте:
— поставил из репозитория доклистер и плагин;
— плагин по умолчанию оказался включен и админка сломалась :)
— зашел в папку /manager/composer, сделал composer install, все починилось и заработало (:
avatar
СТранно, я, правда, ставил руками — но вроде все удачно прошло — composer install успешно все папки поставил, в папке manager/composer/vendor появились нужные папки: agelxnash, asm89, composer, doctrine, twig, umpirsky — вроде бы все нормально. Но вот почему то класс Doctrine\Common\Cache\MemcacheCache не подцепился. На мой взгляд странновато выглядит разве что автоматически созданный файл manager\composer\vendor\composer\autoload_namespaces.php — вот тут бы Doctrine самое место, но почему-то его тут нет.


...
return array(
    'Umpirsky\\' => array($vendorDir . '/umpirsky/twig-php-function/src'),
    'Twig_' => array($vendorDir . '/twig/twig/lib'),
    'Asm89\\Twig\\CacheExtension\\' => array($vendorDir . '/asm89/twig-cache-extension/lib'),
);


Хотя, может не в том вопрос, надо попробовать еще раз из репозитория поставить на всякий случай.
avatar
Doctrine тут:

<?php

// autoload_psr4.php @generated by Composer

$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);

return array(
    'Doctrine\\Common\\Cache\\' => array($vendorDir . '/doctrine/cache/lib/Doctrine/Common/Cache'),
);
avatar
А где такой файл? Что-то нет такого нигде
avatar
/manager/composer/vendor/composer
avatar
Странно, у меня там:
autoload_classmap.php
autoload_files.php
autoload_namespaces.php
autoload_real.php
ClassLoader.php
installed.json

Больше ничего, похоже что-то где-то недосоздалось. Хотя папка doctrine есть, в ней все вроде на месте. Да и в installed.json написано, что установлено:
{
        "name": "doctrine/cache",
        "version": "1.5.x-dev",
        "version_normalized": "1.5.9999999.9999999-dev",
        "source": {
            "type": "git",
            "url": "https://github.com/doctrine/cache.git",
            "reference": "d7ee894b01a2ff73896bde4724f37cbecb8bb3fd"
        },
        "dist": {
            "type": "zip",
            "url": "https://api.github.com/repos/doctrine/cache/zipball/d7ee894b01a2ff73896bde4724f37cbecb8bb3fd",
            "reference": "d7ee894b01a2ff73896bde4724f37cbecb8bb3fd",
            "shasum": ""
        },
        "require": {
            "php": ">=5.3.2"
        },
        "conflict": {
            "doctrine/common": ">2.2,<2.4"
        },
        "require-dev": {
            "phpunit/phpunit": ">=3.7",
            "predis/predis": "~1.0",
            "satooshi/php-coveralls": "~0.6"
        },
        "time": "2015-11-02 18:36:25",
        "type": "library",
        "extra": {
            "branch-alias": {
                "dev-master": "1.5.x-dev"
            }
        },
        "installation-source": "source",
        "autoload": {
            "psr-4": {
                "Doctrine\\Common\\Cache\\": "lib/Doctrine/Common/Cache"
            }
        },
        "notification-url": "https://packagist.org/downloads/",
        "license": [
            "MIT"
        ],
        "authors": [
            {
                "name": "Roman Borschel",
                "email": "roman@code-factory.org"
            },
            {
                "name": "Benjamin Eberlei",
                "email": "kontakt@beberlei.de"
            },
            {
                "name": "Guilherme Blanco",
                "email": "guilhermeblanco@gmail.com"
            },
            {
                "name": "Jonathan Wage",
                "email": "jonwage@gmail.com"
            },
            {
                "name": "Johannes Schmitt",
                "email": "schmittjoh@gmail.com"
            }
        ],
        "description": "Caching library offering an object-oriented API for many cache backends",
        "homepage": "http://www.doctrine-project.org",
        "keywords": [
            "cache",
            "caching"
        ]
    }
avatar
Попробовал то же самое сделать через репозиторий — ситуация не поменялась — все тот же

Fatal error: Class 'Doctrine\Common\Cache\MemcacheCache' not found in D:\OpenServer\domains\twig.loc\assets\plugins\__autoload.php on line 14


скрин

По консоли вроде как все поставилось — скрин.. По папка тоже вроде порядок — скрин. А вот файлов psr-0 и psr-4 так и не появилось. Вот все, что есть.

Может какой-то конфликт у OpenServer-а с этим композером или с плагином?
avatar
Вполне может быть, поэтому я для разработки уже давно использую виртуальную машину с линуксом (: Вот, что у меня в папке composer, может поможет как-то: dl.dropboxusercontent.com/u/41241876/composer.rar
avatar
С этим работает вроде все (на днях надо будет потестить повнимательней). Наверно какая-то устаревшая версия composer идет вместе с OpenServer.

Ну и плюс обязательно в конце проверить, чтобы плагин __autoload вызывался самым первым, особенно до плагина replaceTemplateTwig -это, думаю, обязательно надо в шапку топика добавить :)
Комментарий отредактирован 2015-11-16 19:15:24 пользователем webber
avatar
Можно слить в один плагин; скорее всего Женя сделал отдельно, чтобы показать, как использовать автозагрузку.
Сообщество тему проигнорировало, поэтому как развивать плагин дальше не очень понятно. Так что если какие-то идеи появятся, пиши или высылай пул-реквесты (:
А кэшеры эти, кстати, можно использовать по аналогии и без твига, только создать простенькую обертку для вызова сниппетов.
avatar
Я использую 2 плагина. Просто знаю особенность о прядке. Первым всегда должен быть __autoload.

<?php
include_once(MODX_MANAGER_PATH."/composer/vendor/autoload.php");

$modx->doc = new modResource($modx);
$modx->tpl = DLTemplate::getInstance($modx);
$modx->fs = \Helpers\FS::getInstance();

switch(true){
	case function_exists('apc_cache_info'):{
		$modx->cache = new \Doctrine\Common\Cache\ApcCache();
		break;
	}
	case class_exists('Memcache'):{
		$modx->cache = new \Doctrine\Common\Cache\MemcacheCache();

		$memcache = new Memcache();
		$memcache->connect('localhost', 11211);
		$modx->cache->setMemcache($memcache);
		break;
	}
	case class_exists('Memcached'):{
		$modx->cache = new \Doctrine\Common\Cache\MemcachedCache();

		$memcached = new Memcached();
		$memcached->connect('localhost', 11211);
		$modx->cache->setMemcache($memcached);
		break;
	}
	case class_exists('SQLite3'):{
		$modx->cache = new \Doctrine\Common\Cache\SQLite3Cache(
			new SQLite3(MODX_BASE_PATH.'assets/cache/sqlite.db'), 'cache'
		);
		break;
	}
	default:{
		$modx->cache = new Doctrine\Common\Cache\FilesystemCache(
			MODX_BASE_PATH.'assets/cache/template/'
		);
	}
}
$modx->cache->setNamespace($modx->getConfig('site_name'));

$moduleDebug = false;
if(!is_ajax() && ($modx->isFrontend() && $modx->getLoginUserID('mgr')) || (($modx->isBackend() && isset($_GET['a']) && $_GET['a'] == 112) && $moduleDebug)){
	error_reporting(E_ALL);
	ini_set('display_errors', true);
}
if($modx->isFrontend() && $modx->getLoginUserID('web') && $modx->user instanceof \modUsers){
	$modx->user->edit((int)$modx->getLoginUserID('web'));
	if(!$modx->user->getID() || $modx->user->checkBlock()){
		$modx->user->logOut();
	}
}


А за ним уже плагин replaceTemplate
<?php
/**
 * @props &debug=Debug;list;true,false;true &modxcache=MODX cache;list;true,false;false &conditional=Conditional;list;true,false;true
 */
$debug = (isset($debug) && $debug == 'true') ? true : false;
$modxcache = (isset($modxcache) && $modxcache == 'true') ? true : false;
$conditional = (isset($conditional) && $conditional == 'true') ? true : false;
$cachePath = 'assets/cache/template/';

switch($modx->event->name){
	case 'OnWebPageInit':
	case 'OnManagerPageInit':
	case 'OnPageNotFound':{
		if(class_exists('Twig_Autoloader')){
			Twig_Autoloader::register();
			$loader = new Twig_Loader_Filesystem(MODX_BASE_PATH.'/assets/element/template/');
			$modx->twig = new Twig_Environment($loader, array(
				'cache' => MODX_BASE_PATH.$cachePath,
				'debug' => $debug
			));
			$modx->twig->addExtension(new Twig_Extension_Debug());

			$cacheProvider = new Asm89\Twig\CacheExtension\CacheProvider\DoctrineCacheAdapter($modx->cache);

			$modx->twig->addExtension(new Asm89\Twig\CacheExtension\Extension(
				new Asm89\Twig\CacheExtension\CacheStrategy\IndexedChainingCacheStrategy(array(
					'time' => new Asm89\Twig\CacheExtension\CacheStrategy\LifetimeCacheStrategy($cacheProvider),
					'key' => new Asm89\Twig\CacheExtension\CacheStrategy\GenerationalCacheStrategy($cacheProvider, new \AN\Twig\KeyGenerator(), 0)
				))
			));

			$PhpFunctionExtension = new Umpirsky\Twig\Extension\PhpFunctionExtension();
			$PhpFunctionExtension->allowFunctions(array(
				'count',
				'get_included_files',
				'filesize',
				'get_key',
				'intval',
				'plural'
			));

			$modx->twig->addExtension($PhpFunctionExtension);
			/**
			* {{ runSnippet('example') | modxParser }}
			* {{ '[*id*]' | modxParser }}
			*/
			$modx->twig->addFilter('modxParser', new Twig_SimpleFilter('modxParser', function($content) use($modx){
					$modx->minParserPasses = 2;
					$modx->maxParserPasses = 10;

					$out = $modx->tpl->parseDocumentSource($content, $modx);

					$modx->minParserPasses = -1;
					$modx->maxParserPasses = -1;
					return $out;
				})
			);

			/**
			* {{ makeUrl(20) }}
			* {{ makeUrl(20, {page: 2}) }}
			* {{ makeUrl(20, {}, false) }}
			* {{ makeUrl(20, {page: 2}, false) }}
			*/
			$modx->twig->addFunction(
				new Twig_SimpleFunction('makeUrl', function($id, array $args = array(), $absolute = true) use($modx){
					return $modx->makeUrl($id, '', http_build_query($args), $absolute ? 'full' : '');
				})
			);

			$modx->twig->addFunction(new Twig_SimpleFunction('runSnippet', array($modx, 'runSnippet')));
			$modx->twig->addFunction(new Twig_SimpleFunction('getChunk', array($modx->tpl, 'getChunk')));
			$modx->twig->addFunction(new Twig_SimpleFunction('parseChunk', array($modx->tpl, 'parseChunk')));

			$modx->twig->getExtension('core')->setNumberFormat(0, ",", " ");
		}else{
			include_once(MODX_BASE_PATH."assets/snippets/DocLister/lib/xnop.class.php");
			$modx->twig = new xNop;
		}
		$modx->useConditional = $conditional && !$debug;
		break;
	}
	case 'OnAfterLoadDocumentObject':{
		$ext = ($modx->twig instanceof xNop) ? '.html' : '.twig.html';

		switch(true){
			case file_exists(MODX_BASE_PATH.'assets/element/template/'.'tpl-'.$documentObject['template'].'_doc-'.$documentObject['id'].$ext):{
				$modx->docTemplate = 'tpl-'.$documentObject['template'].'_doc-'.$documentObject['id'];
				$documentObject['oldTemplate'] = $documentObject['template'];
				$documentObject['template'] = 0;
				break;
			}
			case file_exists(MODX_BASE_PATH.'assets/element/template/doc-'.$documentObject['id'].$ext):{
				$modx->docTemplate = 'doc-'.$documentObject['id'];
				$documentObject['oldTemplate'] = $documentObject['template'];
				$documentObject['template'] = 0;
				break;
			}
			case file_exists(MODX_BASE_PATH.'assets/element/template/tpl-'.$documentObject['template'].$ext):{
				$modx->docTemplate = 'tpl-'.$documentObject['template'];
				$documentObject['oldTemplate'] = $documentObject['template'];
				$documentObject['template'] = 0;
				break;
			}
			default:{
				$modx->docTemplate = 0;
			}
		}
		$modx->event->_output = $documentObject;
		break;
	}
	case 'OnLoadWebDocument':{
		if(!empty($modx->docTemplate)){
			if($modx->twig instanceof xNop){
				ob_start();
				include(MODX_BASE_PATH.'assets/element/template/'.$modx->docTemplate.'.html');
				$modx->documentContent = ob_get_contents();
				ob_end_clean();
			}else{
				$modx->minParserPasses = -1;
				$modx->maxParserPasses = -1;
				$tpl = $modx->twig->loadTemplate($modx->docTemplate.'.twig.html');
				$modx->documentContent = $tpl->render(array(
					'modx' => $modx,
					'documentObject' => $modx->documentObject,
					'debug' => $debug,
					'_GET' => $_GET,
					'_POST' => $_POST,
					'_COOKIE' => $_COOKIE,
					'_SESSION' => $_SESSION
				));
			}
		}
		break;
	}
	case 'OnCacheUpdate':{
		$modx->cache->deleteAll();
		break;
	}
	case 'OnWebPagePrerender':{
		if($debug || !$modxcache){
			$modx->documentObject['cacheable'] = 0;
		}
		break;
	}
}
avatar
вопрос возник:
sqlite.db растет постоянно, уже за 2,5 ГБ (за неделю).
при очистке кэша он не удаляется?
(всего ~2000 ресурсов)
Комментарий отредактирован 2016-06-16 18:03:28 пользователем RuSSeLL
avatar
Я думаю, что если слить не только плагины, но и итоговые папки manager/composer — то дело, возможно, пошло бы и живее с уже полностью рабочим вариантом «использование шаблонизатора Twig».

С другой стороны — сильно огорчаться тому, что мало интереса — тоже не стоит. На Рево ситуация такая же примерно — один все со Smarty везде носится, который кроме него никто не использует в MODx, второй Fenom интегрировал, но кроме комментов «Вау, круто, Василий у тебя получилось» тоже не сильно много эффекта получил :)

Так что, тут как говорится — пусть будет, точно не лишнее и для общего развития весьма полезное. Оно бывает так, что какие-то наработки-заготовки накапливаются, накапливаются — а потом все это выливается в какое-нибудь совсем неожиданное о полезное решение :)
avatar
за решение спасибо. но тут всплыл косяк при создании/редактировании менеджера:

PHP Fatal error:  Cannot redeclare generate_password() (previously declared in ../manager/composer/vendor/agelxnash/functions/src/utils.functions.php:53) in ../manager/processors/save_user.processor.php on line 497


по-быстрому решил просто убрав функцию в «композере», но все же…
  • arty
  • 0
avatar
Слегка обновил плагин.

Добавил для большего удобства переменные resource (поля документа и тв), config ($modx->config) и plh ($modx->placeholders).

А также возможность указывать нормальные имена шаблонов вместо давно напрягающих tpl-N: для этого в содержимом шаблона в админке нужно написать @FILE:templatename.tpl (расширение и путь к шаблонам вынес в настройки плагина).

AN\Functions убрал из composer.json.

Теперь осталось добавить поддержку твига в чанках DocLister и будет совсем хорошо (:
avatar
Пока руки так и не доходили. Отсюда вопрос: сам Твиг, его синтакис т.д., прост или сложен в изучении? сколько дней навскидку уйдет, чтобы начать пользоваться?
avatar
Не сложнее, чем городить огороды на phx или if (: Первый раз, конечно, придется часто смотреть в документацию, впрочем как и со скобочным парсером. Но потеря времени на изучение окупится, как минимум, экономией на всяких мелких сниппетах. Да и если взять тот же multiTV — берем данные из него как есть и выводим с любыми извратами, используя циклы, условия и кучу всяких функций.

Опять же, все данные при выводе экранируются, что особенно актуально при работе с пользователями.

Еще очень крутая фишка — наследование шаблонов. Вот, например, код шаблона простой внутренней страницы (тут, правда, не Twig, а Fenom, но принцип такой же):

{extends 'file:templates/main.tpl'}
{block 'fastfilter'}{/block}
{block 'crumbs'}
    {include 'file:chunks/crumbs.tpl'}
{/block}
{block 'content'}
    <h1>{$_modx->resource.longtitle ?: $_modx->resource.pagetitle}</h1>
    {$_modx->resource.content}
{/block}

Здесь я использовал шаблон главной страницы, но убрал из него блок fastfilter, а в блок crumbs вставил типа чанк с этими самыми crumbs. Ну и переопределил блок content — на главной там слайдеры и всякая фигня, а тут просто заголовок и текст. Остальные блоки не трогал вообще. А для страницы контактов я могу переопределить уже шаблон внутренней страницы:

{extends 'file:templates/page.tpl'}
{block 'content'}
    {parent}
    <!-- Тут будет вывод карты и формы обратной связи -->
{/block}
avatar
Звучит очень заманчиво. Всё, решено :) на следующем проекте буду использовать всё это.

P.S. Большая благодарность за развернутый ответ.
Комментарий отредактирован 2016-11-06 17:57:58 пользователем Aharito
avatar
повторю вопрос:
что делать с sqlite.db?
растет и растет, зараза :)
avatar
Ни разу не использовал sqlite. А с другими кэшерами как?
avatar
Навскидку, проблема может быть в том, что для очистки кэша используется метод deleteAll, который, судя по коду, не удаляет записи, а объявляет их устаревшими. Если это так, то можно попробовать заменить его на flushAll.
avatar
ок, спасибо, попробую

еще, часто в логах подобное
Комментарий отредактирован 2016-11-06 22:42:02 пользователем RuSSeLL
avatar
habrahabr.ru/post/204438/ — я так понимаю, что нужно писать свой класс, расширяющий SQLite3Cache, чтобы использовать транзакции в запросах.
avatar
не думаю, что актуально.
спасибо, ещё раз, за ответ :)
avatar
flushAll() не помог, но спасибо за желание помочь…
попробую другие кэшеры
avatar
Я проверил flushAll на файловом кэше, остаются только файлы скомпилированных шаблонов. Так что тут все на совести разработчиков используемых библиотек. Остается еще хардкорный вариант с очисткой кэша вручную.
avatar
Решил разобраться, что и как здесь кэшируется. Шаблоны твига кэшируются сами по себе в папке assets/cache/template. То, что кэшируется через {% cache %} кэшируется выбранным кэшером симфони. Все операции с кэшерами симфони выполняются одинаково и для очистки кэша используется метод flushAll().
В SQLite3Cache конечный вызов этого метода выглядит так:

protected function doFlush()
    {
        return $this->sqlite->exec(sprintf('DELETE FROM %s', $this->table));
    }

То есть выполняется запрос, который должен удалить все записи из базы cache.db. В консоли можно сделать вызов:

var_dump($modx->cache->flushAll());


Пока писал, решил не гадать, а поставить SQLite и посмотреть, что происходит. Отгадка на самом деле проста: habrahabr.ru/post/172085/
Так что проблема решается еще одним плагином на OnCacheUpdate (хотя не знаю, может лучше использовать для этого крон):

$handle = new SQLite3(MODX_BASE_PATH.'assets/cache/sqlite.db');
var_dump($handle->exec('VACUUM'));


Из статьи следует, что можно включить автоматическое сжатие (заодно и изменить режим журналирования, чтобы снизить вероятность возникновения ошибки database is locked), но я не знаю, где это нужно делать: в классе кэшера или может можно в любой момент запрос выполнить и оно будет работать постоянно.
avatar
Добавил ручной выбор кэшера и заменил метод deleteAll() на flushAll() при очистке кэша.

А также сделал экспериментальную поддержку Twig в чанках DocLister (в имени шаблона указывается @T_FILE, @T_CHUNK и т.д.; если отключить событие OnLoadWebDocument, запретив тем самым Twig в шаблонах страниц, то можно и @T_CODE по идее использовать). Кроме данных в массиве data, доступны также объекты modx и DocLister; то есть можно обходиться без prepare во многих случаях:

<div class="col-sm-6">
    <a class="mainlink" href="{{data['url']}}">
        <span class="title">{{data['pagetitle']}}</span>
        {% if data['introtext'] %}
            <span class="intro">{{data['introtext']}}</span>
        {% endif %}
        <img class="img-responsive" alt="{{data['pagetitle']}}" src="{% if data['tv.image'] %}
            {{ runSnippet('sgThumb',{'input':data['tv.image'],'options':'555x416'}) }}
        {% else %}
            data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7
        {% endif %}" width="555" height="416">
    </a>
</div>
avatar
Очень интересно.
avatar
Особенно интересно это, если делать каталоги, где нужно то всякие значки в карточку товара прилепить, то цену отформатировать, то еще что-нибудь. Вообще, многие вещи упрощаются, например, из сниппета sgController можно половину кода выкинуть и не нужны чанки sgOuterTpl, sgRowTpl. Или DLRequest, а то и FormLister — куча мелких чанков, которые нафиг не нужны.

Опять же наличие нормального кэша под рукой много дает возможностей.
Комментарий отредактирован 2016-11-08 08:00:33 пользователем Pathologic
avatar
Единственный вопрос, который навскидку возникает — а насколько быстро работают все эти вещи в исполнении Твиг?

Но с другой стороны, если неплохой механизм кеширования, то в общем-то какая разница…
avatar
В самом верху есть ответ; у меня тоже получается не медленее, чем с обычным парсером. По-хорошему нужно какой-нибудь сайт переделать и сравнить (:
avatar
Согласен с вами — чем мучить других вопросами, легче самому сесть за комп и провести эксперимент :) именно этим и займусь.

Может быть, не целый сайт (как обычно, времени нет), но хотя бы частично сравню.
Комментарий отредактирован 2016-11-08 12:32:21 пользователем Aharito
avatar
Я сейчас вообще отключил плагин и посмотрел на шаблон _blank — заметной разницы не ощущаю.
avatar
удалено…
Комментарий отредактирован 2016-11-08 22:40:46 пользователем 64j
  • 64j
  • 0
avatar
Странное дело с кешированием

Отключаю оба плагина __autoload и replaceTemplateTwig
Memory: 2 mb, MySQL: 0.0010 s, 0 request(s), PHP: 0.0290 s, total: 0.0300 s, document retrieved from cache.

Включаю.

Cacher: Files
Memory: 4 mb, MySQL: 0.0110 s, 4 request(s), PHP: 0.0490 s, total: 0.0600 s, document retrieved from cache.

Cacher: SQLite3
Memory: 2 mb, MySQL: 0.0110 s, 4 request(s), PHP: 0.0460 s, total: 0.0570 s, document retrieved from cache.

Cacher: Memcached
Memory: 2 mb, MySQL: 0.0040 s, 4 request(s), PHP: 0.0460 s, total: 0.0500 s, document retrieved from cache.

Cacher: Memcache
Memory: 4 mb, MySQL: 0.0110 s, 4 request(s), PHP: 0.0470 s, total: 0.0580 s, document retrieved from cache.

Cacher: APC
Memory: 4 mb, MySQL: 0.0030 s, 4 request(s), PHP: 0.0560 s, total: 0.0590 s, document retrieved from cache.

Мне кажется без кешеров работает быстрее))
Либо они себя покажут на многотонных проектах.
  • 64j
  • 0
avatar
А почему document retrieved from cache? В плагине ведь кэш MODX отключается.
avatar
данные после повторной загрузки страницы.
а вот почему так, даже в голову не приходило.
avatar
[^s^] на d1.7.6 у меня показывает database. С параметром debug еще есть особенность — если он выключен, то кэш шаблонов не обновляется.
avatar
хм… я на кастомной сборке 1.2-d8.1.5 пробовал. Может быть в этом причина?
А проверяю так же
Memory: [^m^], MySQL: [^qt^], [^q^] request(s), PHP: [^p^], total: [^t^], document retrieved from [^s^].
avatar
есть ещё сайт, хостится на регру, так там вообще бешенный кэш))
SQL: 4 — 0.0016 s PHP: 0.0032 s, total: 0.0048 s, from cache
avatar
С таким кэшем и кэш не нужен (: На самом деле какая-то ощутимая польза, я думаю, будет видна или на очень больших сайтах — там уже на очистке кэша можно выиграть; или если кэшировать какие-то медленные сниппеты.
avatar
либо вот ещё с другого сайта
Записи в БД
modx_site_content — 7103
modx_site_htmlsnippets — 140
modx_site_plugins — 31
modx_site_snippets — 167
modx_site_templates — 23
modx_site_tmplvar_contentvalues — 22155
modx_site_tmplvars — 57
Общий все базы 14.79 Mb

Кешированная страница
Memory: 2 mb, MySQL: 0.0057 s, 4 request(s), PHP: 0.0222 s, total: 0.0279 s, document retrieved from cache.

Не кешированная
Memory: 4 mb, MySQL: 0,0417 s, 21 request(s), PHP: 0,0946 s, total: 0,1363 s, document retrieved from database.
Комментарий отредактирован 2016-11-08 23:29:04 пользователем 64j
avatar
Это в 1-м случае с MODx-кешем, а во 2-м — с каким-то из тех, что на выбор с Твигом, правильно я понимаю?
avatar
Нет, оба случая с MODX-кэшем, только во втором случае он пустой.
Я ночью как-то упустил из вида, что не понятно, что в комментариях выше с чем сравнивается.
В первом комментарии получается сравнение страницы из кэша MODX (об этом говорит 0 запросов) с некэшированной страницей — толку тут от новых кэшеров никакого, потому что они пустые. Не хватает времени загрузки некэшированной страницы.
avatar
Ясно, попробую самостоятельно — мне очень нужна версионность шаблонов, сейчас как раз такой проект и заказчик. Ну и остальное тоже весьма интересно.
avatar
Мне кажется без кешеров работает быстрее))
Либо они себя покажут на многотонных проектах.
Не, ну здесь вы сравниваете ангстремы с нанометрами )) если страница грузится не за 0,03 а за 0,06 сек, думаю не много посетителей уйдут с сайта из-за того, что потеряли в своей жизни драгоценные 0,03 секунды ))

А если серьезно — на чем-то более тяжелом надо смотреть. 4 лишних запроса конечно смущают, но если это стабильно 4, независимо от страницы — то можно и потерпеть.
Комментарий отредактирован 2016-11-09 06:07:11 пользователем Aharito
avatar
родной кеш modx прекрасно справляется, первым делом нужно оптимизировать все скрипты, а уже только потом прибегать к использованию сторонних кешеров.
avatar
Понял, поэкспериментирую.
avatar
Так плагин и не подменяет родной кэш. Но если посмотреть в код плагина, то там видно, что для страниц кэш MODX отключается — это нужно только для корректной работы твига, иначе MODX закэширует голый html и о динамике можно будет забыть.

Поэтому и сравнение странное получилось, ну то есть если добавить в него вывод некэшированной страницы без плагина, то узнаем, сколько времени отъедает инициализация дополнительных классов (:
avatar
Была идея расширить класс DBAPI и кэшировать вообще все запросы (:
avatar
Имеется в виду, в этом плагине?
avatar
Нет, глобально. Плагином такое не сделать.
avatar
Да и не думаю, что это вообще нужно в Evo. Тут правильнее, как сказал 64j , оптимизировать скрипты, а не полагаться на волшебный кэш.

Но если все-таки хочется кэшировать в своих сниппетах-плагинах-модулях, то это легко делается через $modx->cache. Для примера вот сниппет, который будет кэшировать другие сниппеты:

<?php
$lifetime = isset($lifetime) ? $lifetime : null;
$out = '';
if (isset($key) && !empty($key) && isset($snippetName)) {
    if ($modx->cache->contains($key) {
        $out = $modx->cache->fetch($key);
    } else {
        $out = $modx->runSnippet($snippetName, $params);
        $modx->cache->save($key, $out, $lifetime);
    }
}
return $out;


И с помощью этого сниппета кэшируем вызовы сниппетов:

[!getCache?
&snippetName=`DLBuildMenu`
&key=`topmenu`
&parents=`0`
!]


Разница между таким вызовом и просто [[BuildMenu]] в том, что [[BuildMenu]] кэшируется в пределах страницы, а здесь мы закэшировали глобально, то есть один раз меню построится и на всех страницах будет браться из кэша.

Или есть, например, сниппет, который получает какие-то данные с другого сайта и выводит. Чтобы не вызывать тормоза, вызываем так:
[!getCache?
&snippetName=`KursValut`
&key=`kurs`
&lifetime=`3600`
!]

Результат будет закэширован на час.

Или вот посложнее пример: в меню нужно скрывать какие-то пункты от незалогиненных пользователей. Значит сниппет для вывода меню придется вызывать некэшированным [!MenuSnippet!], иначе работать не будет. Немного меняем сниппет getCache и проблема решена:

<?php
$lifetime = isset($lifetime) ? $lifetime : null;
$out = '';
if (isset($key) && !empty($key) && isset($snippetName)) {
    $key .= $modx->getLoginUserID() ? '_user' : '';
    //если нужно кэшировать в пределах страницы, то можно еще добавить $key .= '_page'.$documentObject['id']
    if ($modx->cache->contains($key) {
        $out = $modx->cache->fetch($key);
    } else {
        $out = $modx->runSnippet($snippetName, $params);
        $modx->cache->save($key, $out, $lifetime);
    }
}
return $out;
avatar
Очень полезные примеры.
Со знанием этих нюансов MODx-кеша, да еще с Твиг'ом, открываются возможности даже для достаточно сложных и нагруженных сайтов.
avatar
И это еще не все (: Вот как отключается кэш MODX:
github.com/Pathologic/EvoTwig/blob/master/assets/plugins/twig/replaceTemplateTwig.php#L184
То есть сейчас он отключается безусловно, но ничто ведь не мешает отключать/включать его по каким-то условиям. В результате в кэш MODX будет падать чистый html, который при этом потом не будет парситься — быстрее уже если только статические страницы сохранять (:
avatar
Сейчас как раз намечается достаточно сложный и нагруженный проект, буду тестить все это.
avatar
Тут правильнее, как сказал 64j, оптимизировать скрипты, а не полагаться на волшебный кэш.
самая первая оптимизация, это Wayfinder.
На странице очень часто выводят несколько меню и хлебные крошки.
Так в первом вызове wayfinder (конечно же кастомном) создаётся массив ($modx->wayfinderDocs) со всеми hidemenu=0, затем в каждом вызове аналогично Doclister… &tvList=`image,` выбираются tv, если требуется, ну и потом каждый вызов сниппета забирает свою часть массива. Это самый первый и основной выигрыш, поскольку один запрос в бд на все меню и по одному запросу при использовании TV.
А не так как сейчас, каждый вызов — запрос, каждый TV — запрос для каждого пункта меню.

DocLister, зачастую в prepare пихают запросы для получения какой либо информации, получается для каждого пункта дополнительные запросы. Всё можно оптимизировать просто доработав контроллер (дублировать тот что идёт по дефолту), и получив все ID документов (в функции getDocs), найти для них нужные TV, добавив их опять же в глобальный массив $modx->custom_attributes.
И в функции _render уже добавить
array_push($item, $modx->custom_attributes);
и они будут доступны как плейсхолдеры, либо уже в prepare можно с ними работать. Тут уже вариантов много)

Shopkeeper — убрать лишние запросы для получения не нужных TV для товаров в корзине, ну и содержимое корзины из базы. Это всё можно брать из сессии, и менять её при любом действии в SHK.

Ну и далее по списку)
avatar
Идеи интересные. Про prepare в DocLister — там для кэширования в памяти предусмотрены методы setStore/getStore, можно не делать запросы на каждый пункт.
avatar
@Pathologic Была идея расширить класс DBAPI и кэшировать вообще все запросы (:

А зачем? Mysql имеет query cache, зачем изобретать лишнюю прослойку?!
avatar
Больше гибкости и контроля над кэшем такое кэширование дает в теории, опять же пишут, что с query cache не все так хорошо, как кажется. В общем поэкспериментировать было бы интересно, а на практике не возникала у меня нужда в такой фишке. Хотя во фреймворках это есть, даже в Revo есть.
avatar
Не планируется ли расширения парсера шаблонов?
(первым напрашивается parent-3.twig.html)

К примеру, если используются вложенные урл и страница имеет адрес типа
catalog/category/product-1
то для неё используется шаблон по маске урл «catalog/category/%», "%/product%"

либо урл catalog/category-1
маска «catalog/%», «catalog/category%», «catalog/%[isfolder]»
только второй уровень «catalog/%[2]», «catalog/%[2:isfolder]»
третий уровень «catalog/%[3]»
свыше третьего «catalog/%[+3:isfolder]»

Конечно названия файлов не будут такими понятными, но можно подумать над синтаксисом,
alias{catalog;%[+3-isfolder]}.twig.tpl

isfolder;alias{catalog};parent{3}.twig.tpl
isfolder;tpl{3}.twig.tpl
id{1,2,3,4,5}.twig.tpl
parent{1,2,3,4,5};isfolder.twig.tpl

Если развивать дальше идею, можно и для чанков сделать подобное. Чанки в отдельной папке от шаблонов «chunk»
и так же могут подставляться в зависимости от названия.
К примеру один и тот же чанк, но на разных страницах может иметь разное содержимое.
leftmenu.twig.tpl
leftmenu-parent{3};alias{catalog}.twig.tpl
leftmenu-id{3}.twig.tpl
leftmenu-web.twig.tpl
leftmenu-web;group{1,2,3}.twig.tpl
leftmenu-mgr.twig.tpl
  • 64j
  • +2
avatar
Думаю, что такое легко делается средствами твига: x-twig.ru/tags/include/.
avatar
Еще раз обновил плагин: сделал настройку разрешенных в шаблонах функций PHP; убрал из автозагрузки создание объектов modResource и Helpers\FS; добавил фильтр для склонения слов:

<p>Цена: {{ resource.price }} {{ ['рубль','рубля','рублей'] | plural(resource.price)}}</p>


Когда появится время, создам еще один топик, в котором объединю все, что здесь накомментировали, и напишу новую инструкцию.

В целом, решение получилось со всех сторон простое и удобное, поэтому настоятельно рекомендую попробовать в деле. Профита будет куда больше, чем от той херни, которую изобретают сейчас в Evo:
github.com/modxcms/evolution/commit/bc93febc3d2446b31da19203cccbcba17f256483
github.com/modxcms/evolution/commit/efa787b2b8192db23a2bd5fa3587ba04ec261510
avatar
Использую этот замечательный плагин в нескольких проектах (в основном личных) в течении года — фантастически гибкое решение! Наследование, блоки, макросы и фильтры — к этому прилипаешь моментально.
Меняется сам подход к проектированию и реализации «фронтенда». Не скажу, что проще чем кликать в админке, но стройнее и гибче точно. Тема и все соответствующие компоненты храню в одной папке (благодаря LoadElements) — чанки и сниппеты + Twig.
На новый проект просто архивирую и переношу директорию и все! Единственное, сниппеты, которые импортируют настройки храню в базе — но таковых немного.

Пробовал использовать CMS на современных феймворках — October (Laravel + Twig),
Bolt CMS (Simphony + Twig) — не смог — с последними изменениями о дополнениями MODx удобнее и дружелюбнее. На мой взгляд.
Большое вам спасибо за реализацию, развитие и поддержку! И не только этого, но и других ваших дополнений для MODx.
Система реально становится «прямее»!
Комментарий отредактирован 2016-11-10 11:53:22 пользователем maximlit
avatar
Спасибо за отзыв!
avatar
Присоединяюсь полностью. Тоже немного «пощупал» October — пока не впечатлило. Да и нужды особой нет — с такими плюшками Эво хватит за глаза :) Благодарю Pathologic и Agel_Nash .
Комментарий отредактирован 2016-11-10 13:15:44 пользователем Aharito
avatar
Twig в том виде, в котором мы имеем — полностью заслуга Pathologic
avatar
На новый проект просто архивирую и переношу директорию и все!
Перенос файлов это еще пол дела.
Когда я все это затеивал, то стремился:
— добавить максимальную совместимость для работы с git
— сделать разработку сайтов на MODX Evo комфортнее в IDE

И я очень рад, что мое решение понравилось Pathologic и он начал его активно развивать. Более того, в свете последних улучшений парсера evo, шаблонизация средствами twig актуальна как никогда.
avatar
Да, безусловно ваши 2 пункта добавляют удобства, версионный контроль чанков/шаблонов с их разработкой в IDE — это супер.

Для меня как раз на текущем проекте это очень актуально — вместе с заказчиком в режиме Design in browser делаем прототип сайта. И отлеживать/откатывать/накатывать снова средствами MODx крайне неудобно.

А если приплюсовать остальные преимущества — решение, действительно, вышло логичное и достойное.
Комментарий отредактирован 2016-11-10 15:12:57 пользователем Aharito
avatar
Еще один трюк с кэшированием:
$e = $modx->event;
$key = $method.'_'.$identifier;
if ($e->name == 'OnBeforeLoadDocumentObject') {
	if ($modx->cache->contains($key)) {
		$documentObject = $modx->cache->fetch($key);
		$e->_output = $documentObject;
	}
}
if ($e->name == 'OnLoadWebDocument') {
	if (isset($modx->documentObject['_template'])) {
		$modx->documentObject['template'] = $modx->documentObject['_template'];
		$modx->documentContent = $modx->documentObject['_output'];
	}
}
if ($e->name == 'OnAfterLoadDocumentObject') {
	if (!$modx->cache->contains($key)) {
		if ($modx->documentObject['template']) {
			$modx->documentObject['_template'] = $modx->documentObject['template'];
			$modx->documentObject['template'] = 0;
			$q = $modx->db->select('content', $modx->getFullTableName("site_templates"), "id = '{$modx->documentObject['_template']}'");
			$modx->documentObject['_output'] = $modx->db->getValue($q);
		}
		$modx->cache->save($key,$modx->documentObject);
	}
	$e->_output = $modx->documentObject;
}

Здесь кэшируем documentObject и шаблон, чтобы перехитрить метод prepareResponse(). Результат:

При этом динамика в шаблоне не теряется. Зато теряется проверка прав на документ (: Но в общем-то можно допилить и это.
avatar
Вы молодцы!
Хотел спросить, как быть с макросами, что-то они не цепляются у меня, нужны правки в плагине?
(установлена версия из вашего репо)
avatar
вопрос снимается:
{% import "helpers.twig.html" as h %}

далее:
{{ h.svg('icon-phone') }}
avatar
Здравствуйте!
Возможно не по теме, но имеет прямое отношение к Twig Evo
Медленно, но верно решение и обвязка развивается, вернее развивает Pathologic
Видел обновление FormLister — теперь доступно использование в шаблонах Twig.
Но не могу разобраться, как использовать Объект и Методы в этом контексте?
Можете один наглядный пример?
avatar
Примеров как-то пока и нет хороших. В целом реализовано так: класс DLTemplate передает в шаблон modx (объект и DocumentParser) и data (массив значений), DocLister дополнительно передает DocLister (объект контроллера DocLister), а FormLister — FormLister (объект контроллера), messages (массив сообщений), errors (массив ошибок). Массив ошибок не очень подходит для использования в шаблоне, поэтому пришлось ввести дополнительный метод:

<select name="parent">
    <option></option>
    {% for parent in data.parents %}
    <option value="{{ parent.id }}" {% if data.parent == parent.id %}selected{% endif %}>{{ parent.pagetitle }}</option>
    {% endfor %}
</select>
{% set error = FormLister.getErrorMessage('parent') %}
{% if error %}<div class="error">{{ error | join (', ')}}</div>{% endif %}


В общем, компромиссное решение, когда можно использовать обычные чанки, а можно чанки с твигом мне не нравится, потому что реализация какая-то сомнительная, пусть даже и стало немного удобнее.
avatar
Здравствуйте!
Столкнулся с необычным явлением:
В шаблоне Twig одинаково обрабатываются теги
{{ resource.content | raw }} == [*content*]
Сниппеты тоже отрабатывают как из под парсера MODx
Сборка 1.2-d9.0.4, replaceTemplateTwig c Gita
??
комментарий был удален
комментарий был удален
комментарий был удален
комментарий был удален
комментарий был удален
комментарий был удален
комментарий был удален
комментарий был удален
комментарий был удален
комментарий был удален
комментарий был удален
комментарий был удален
avatar
Я не партийный, я просто сочувствующий :)
avatar
+100500 за беспартийность. «Главное голосовать против и выбирать тех, кто не победит» ©Я :)))) чтобы показать власти, что не всё так радужно…
комментарий был удален
комментарий был удален
комментарий был удален
комментарий был удален
комментарий был удален
комментарий был удален
комментарий был удален
комментарий был удален
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.