Fenom VS Smarty

Недавно Михаил попросил меня написать сравнительный обзор Fenom и Smarty. Честно сказать, я феномом не пользовался, и хотя сейчас уделил время на какое-то его изучение, все-таки не смогу выдать какое-то сильно аргументированное заключение, просто потому что много в феном мог и не увидеть (хотя оно там может даже и есть). Тем не менее, я приведу несколько примеров использования Smarty, которые довольно часто используются на практике, но которых я не увидел в Феноме, а те, кто хорошо знает Феном, пусть поправят меня или подтвердят.

Для начала следует отметить, что все-таки эти инструменты хоть и схожие, но изначально судя по всему ориентированы на разные цели. Феном (в рамках pdoTools) на родные MODX-сущности (чанки, шаблоны и т.п.), а Смарти (в том числе и в рамках modxSmarty) на работу с файлами. Потому, еще раз, их немного сложно сравнивать. Ну да ладно, это все лирика. Разберем лучше на реальных примерах.

Одновременно несколько директорий шаблонов (скинов).
Что в первую очередь искал в феноме, но не увидел — это работа с несколькими директориями шаблонов. Тут стоит оговориться, что я имею ввиду разделы в файловой системе, но это может быть и какое-то другое групповое отличие. В любом случае, идея в том, чтобы можно было не просто одним чанком расширить другой чанк, а одну группу чанков сменить другой группой чанков, чтобы получить совершенно новое оформление. Приведу пример, как это работает в Смарти. К примеру, у нас есть вот такая структура joxi.ru/KAxeRO7c4n0JGr
Здесь у нас есть основной полноценный шаблон (скин сайта) shopmodx с кучей шаблон-файлов. Так вот, если я хочу какие-то шаблон-файлы основного скина переопределить (полностью или частично), мне не обязательно где-то что-то модифицировать. Мне достаточно Смарти добавить еще одну (а можно и не одну) директорию шаблонов, и смарти при вызове шаблонов будет перебирать все эти директории, пока не найдет запрошенный шаблон.



На самом деле ряд моментов я опущу, так как все это такие тонкости, которые на конкретных задачах рассматриваются. Но вот сегодня дополнительно тестировал Феном и столкнулся с парой неприятных для меня моментов:

1. Вызов произвольных функций.
К примеру, в Смарти я легко могу написать {print_r($array)}, или {array_sum([1,3,4,6,7])}. Вот в Феном я этого не смог сделать. Ошибка. Вопрос: он вообще это умеет делать?

2. Доступ к объекту $modx
Вот это вообще тема… Довольно часто я в шаблонах обращаюсь к API MODX. Например, {if $modx->user->id} это проверка наличия пользователя. Если пользователь в админке авторизовался, то он и во фронте будет виден и все политики для него будут работать. А вот {if $modx->user->isAuthenticated()} будет себя иначе вести, так как этот метод проверяет, авторизован ли пользователь именно в этом контексте (по умолчанию web), а так как авторизовавшись в админке пользователь авторизован только в контексте mgr, то во фронте при такой проверке пользователя не будет.
Другой пример {if $modx->resource->Parent->published}, то есть я в шаблоне просто хочу проверить опубликован ли родитель текущего документа или нет. И вот в Феном я этого просто так не могу сделать (на самом деле могу, но это далее). То есть у меня просто нет объекта {$modx}. Есть объект {$_modx}, но это вообще мало отношения к $modx имеет, так как это кастомный тонкий клиент между шаблоном и реальным объектом $modx. К слову, вот это у меня больше всего вопросов и вызвало. Зачем прятать объект $_modx->modx? Зачем он приватный? Ведь так мы теряем полноценный доступ к API MODX. Для секурности? Но полноценной секурности здесь нет, ибо ничто мне не мешает обратиться к $modx через объект {$_modx->lexicon->modx} или {$_modx->cacheManager->modx}, а вот головной боли мне это серьезно добавляет и урезает творческую реализацию.

В общем, надо будет еще все это как следует погонять. Но так или иначе, Феном все-таки тоже весьма не плохо, хотя мне и не получается пока на нем разгуляться.
Fi1osof
04 декабря 2015, 18:44
modx.pro
4
6 685
+7

Комментарии: 45

Илья Уткин
04 декабря 2015, 21:48
+3
Нужно просто в настройках включить использование php в Fenom и доступ к объекту modx. Тогда эти два момента разрешатся.

Это для безопасности — вдруг на сайте есть контент-менеджер, который может править чанки.
    Fi1osof
    04 декабря 2015, 21:52
    +1
    Спасибо! ОК, уже легче дышется :)
    Если вдруг кто тоже не сразу нашел: joxi.ru/82QV3dNu1DovgA

    Это для безопасности — вдруг на сайте есть контент-менеджер, который может править чанки.
    Ну, как я и сказал, есть как минимум {$_modx->cacheManager->modx}, хотя и можно это тоже перекрыть. А второе: админка в MODX — это уже зона риска огромная, так что нефиг туда вообще никого лишнего пускать :)
      Василий Наумкин
      04 декабря 2015, 21:56
      +2
      А вот это я пропустил, спасибо — отключу в новых версиях обязательно.

      Суть в том, что пользоваться {$_modx} можно давать кому угодно, он только для чтения, грубо говоря. Тогда как через {$modx} можно легко убить всю систему. По этой же причине и php функции отключены по умолчанию.
        Fi1osof
        04 декабря 2015, 22:00
        0
        Не, если в конфигах включается, то ОК. Я просто пропустил.

        И вот такой вопрос: Есть два чанка fenom1 и fenom2. В документе пишу [[$fenom]] и ок, на странице все выводится. Но если я в чанке fenom1 пишу {extends 'fenom2'}, то он расширяет второй чанк, но на странице выводится только содержимое чанков (ничего более от шаблона). Можно как-то расширять другие чанки или что я не так делаю? Шаблоны корректно расширяются.
          Василий Наумкин
          04 декабря 2015, 22:18
          0
          Ты пишешь только
          {extends 'fenom2'}

          Или прям расширяешь его?
          {extends 'fenom2'}
          
          {block 'some_name'}
          	Тут то, что расширили
          {/block}
          А то недавно Иван Климчук заметил, что без переопределения хотя бы одного блока ничего не работает и дописал это в доки.
            Fi1osof
            04 декабря 2015, 22:25
            0
            Нет, я именно расширяю с переопределением блоков.
            joxi.ru/a2XVypGuy0Bzjr
            joxi.ru/ZrJVWD0u13PO4r

            По всякому игрался, результат один и тот же.
              Василий Наумкин
              04 декабря 2015, 22:35
              +1
              Хм. А чанк fenom3 как выглядит?
                Fi1osof
                04 декабря 2015, 22:38
                0
                sdfsdf3
                {block 'test'}
                dfdf666
                
                {var $test = '222222'}
                {/block}
                  Василий Наумкин
                  04 декабря 2015, 22:50
                  0
                  Не вижу никакого криминала, вроде всё должно быть ок.

                  Посмотри, может где-то какие-то ошибки в логах есть? По идее, всё должно расширяться без проблем — сам этим пользуюсь постоянно на разных проектах.
                    Василий Наумкин
                    04 декабря 2015, 22:56
                    +1
                    Всё, увидел этот баг на тестовом сайте.

                    Чанк с расширением нужно вызывать некэшируемым:
                    [[!$fenom1]]
                    Или можно так:
                    {$_modx->getChunk('fenom1')}
                    Тогда никаких белых страниц, всё работает.
                      Fi1osof
                      05 декабря 2015, 16:02
                      0
                      Это не бага, все логично. Просто MODX отрабатывает окончательно весь код документа (вместе с кодом шаблона) уже на уровне modResponse::outputContent(). Немного модифицируем код:
                      public function outputContent(array $options = array()) {
                              if (!($this->contentType = $this->modx->resource->getOne('ContentType'))) {
                                  if ($this->modx->getDebug() === true) {
                                      $this->modx->log(modX::LOG_LEVEL_DEBUG, "No valid content type for RESOURCE: " . print_r($this->modx->resource->toArray(), true));
                                  }
                                  $this->modx->log(modX::LOG_LEVEL_FATAL, "The requested resource has no valid content type specified.");
                              }
                               
                      
                              if (!$this->contentType->get('binary')) {
                                  $this->modx->resource->_output= $this->modx->resource->process();
                                  $this->modx->resource->_jscripts= $this->modx->jscripts;
                                  $this->modx->resource->_sjscripts= $this->modx->sjscripts;
                                  $this->modx->resource->_loadedjscripts= $this->modx->loadedjscripts; 
                              
                                  /*
                      		Здесь добавим вывод текущего контента и прервем обработку 
                         		*/
                                  print $this->modx->resource->_output;
                                  exit;
                                  /* collect any uncached element tags in the content and process them */
                                  $this->modx->getParser();
                      Обновляем страницу и получаем такой результат: joxi.ru/VrwoaO9IKVPyXr
                      То есть это весь код шаблона и страницы, и в нем код чанка fenom1 как есть, вместе с расширением {extended}. То есть расширение уже становится не чанка, а по сути шаблона. В общем, контекст не тот уже.
                      Здесь надо мыслить как с этим поступить. Самое правильное, как мне кажется — это даже в рамках шаблона отрабатывать чанки сразу. Или договориться о том, что нельзя использовать кешируемые чанки с расширением других чанков.

                      К слову, Смарти не позволяет декларирование {extends} иначе как в начале кода. А тут он де факто получается внутри.
                        Василий Наумкин
                        05 декабря 2015, 16:04
                        0
                        Лично я ничего с этим делать не планирую.

                        Вызов Fenom чанков через Fenom синтаксис работает без проблем — лучше так и делать =)
                        Fi1osof
                        05 декабря 2015, 16:34
                        0
                        ОК.
        Василий Наумкин
        05 декабря 2015, 09:41
        +1
        Всё, заменил lexicon и cacheManager с публичным свойством $modx на свои, ограниченные.

        Теперь, насколько я вижу, из {$_modx} до modX добраться не выйдет. Спасибо, что указал на эту уязвимость!
          Fi1osof
          05 декабря 2015, 16:31
          +1
          Василий, извини, что поддаюсь на провокацию, но не могу не наделать пакостей)) Это просто к тому, что нет смысла что-то подобное запрещать тем, кто имеет доступ к редактированию чего-то в админке. Это все ограничивает в творчестве, но все равно не сильно увеличивает безопасность.
          Вот я отключил в настройках доступ к $modx и выполнение php. ОК. А теперь я хоть в чанке, хоть в контенте документа (не имея доступа к чанкам и т.п.) пишу вот такое:
          {var $opt = $_modx->config}
          {$opt.pdotools_fenom_modx = 1}
          {$opt.pdotools_fenom_php = 1}
          {$_modx->cacheManager->set('config', $opt, 0, [
              "cache_key" => "system_settings/",
          ])}
          Что я здесь делаю, не трудно догадаться: я просто перетираю кеш системных настроек MODX-а с новыми значениями настроект. И при повторном заходе на страницу (с перезаписанными настройками), я уже могу делать все, что угодно, включая {$modx->user->sudo = 1} {$modx->user->save()}. Сорри, но это ппц какая дыра в безопасности.
            Василий Наумкин
            05 декабря 2015, 16:59
            0
            Круто, богатая у тебя фантазия! Придётся отключать cacheManager совсем.

            Хотя нет, не отключать, а просто запретить указывать такие настройки. Выложил новую версию, в которой параметр $options у cacheManager::set() не используется. Итого, кэш получать можно любой, а сохранять только в default.

            Твой ход =)
              Fi1osof
              05 декабря 2015, 17:09
              0
              Предвидел я это. Про то и говорю: ты замахаешься все отключать, это все будет ограничивать разработчиков, но все равно где-то дыры будут. Потом где-то регистрацию сделают и человек что-то подобное в своем имени укажет или еще где-то. Проблема тут в том, что откуда бы код не взялся, он в итоге собирается в единое и потом на уровне modResponse отрабатывается как единый шаблон. Это та же проблема, по которой MODX фильтрует MODX-теги в запросах. Вот та же головная боль возникает и здесь. Возможно тут надо подумать немного в другом направлении, как вариант — не отрабатывать все как феном-код на конечном этапе modResponse, хотя это мне и кажется в рамках текущей парадигмы маловероятным. Для сравнения у меня Смарти отрабатывается только на уровне Смарти, не обрабатывая потом его шаблонизатором содержимого чанков, документа и т.п. То есть в контент можно писать смарти-теги, но они будут просто как текст для него. Тут получается и в творчестве не ограничен (на уровне смарти-шаблонов можно писать что угодно) и секурней, потому как вряд ли у кого-то постороннего будет доступ к смарти-шаблонам, а в контент пусть что угодно пишет.
                Василий Наумкин
                05 декабря 2015, 17:14
                0
                Проблема тут в том, что откуда бы код не взялся, он в итоге собирается в единое и потом на уровне modResponse отрабатывается как единый шаблон.
                Это отключено по умолчанию. Так же как и доступ в php и modX.

                А доступ в {$_modx} включен по умолчанию и он должен быть безопасным. Сегодня ты очень сильно помог в этой задаче. Надеюсь, что больше никаких уязвимостей при конфигурации по умолчанию не найдётся.

                Если же разработчик (не редактор) хочет — он может смело всё включить. Но он должен это сделать сам, понимая, чем рискует.
                  Fi1osof
                  05 декабря 2015, 17:16
                  +1
                  ОК. Если в процессе на что-то наткнусь, дам знать.
      Андрей
      05 декабря 2015, 00:50
      0
      Василий, не подскажешь, что я делаю не так?
      Вызов в шаблоне:

      {$_modx->runSnippet('temp')}

      Сниппет:

      <?php
      $output = $modx->getChunk('testChunkCommon', array('some_placeholder' => 'some_placeholder'));
      return $output;

      Содержимое testChunkCommon.tpl:

      1: {$some_placeholder}
      2: {$_pls['some_placeholder']}
      3: {$_modx->getPlaceholder('some_placeholder')}
      4: [[+some_placeholder]]

      Результат:
      1:
      2:
      3:
      4: some_placeholder
        Мордынский Николай
        05 декабря 2015, 02:11
        0
        testChunkCommon.tpl у тебя чанк, а обращаешься к testChunkCommon
          Андрей
          05 декабря 2015, 02:40
          0
          И? Я не правильно обращаюсь?
          Василий Наумкин
          05 декабря 2015, 09:39
          0
          Не знаю, у меня вот так всё работает:
          {$_modx->getChunk('@INLINE {$some_placeholder}', [
          	'some_placeholder' => 'some_placeholder'
          ])}
          Значит, проблем с обработкой плейсхолдеров Fenom в чанке нет.

          Если я неправильно понимаю, чего ты хочешь добиться — покажи свою проблему на чистом тестовом сайте.
            Андрей
            05 декабря 2015, 11:08
            0
            modx.pro/development/7162-example-fenom/#comment-50656

            Зато все значения выводятся при вызове сниппета [[!temp]]

            Тестовый сайт:
            s3660.h2.modhost.pro/test.html
            user: s3660
            pass: a0VU4iiGhkmb
              Василий Наумкин
              05 декабря 2015, 11:12
              0
              $output = $modx->getChunk('testChunk', array('some_placeholder' => 'value'));
              Смешно.

              С каких это пор сам MODX научился работать с Fenom?
              $pdo = $modx->getService('pdoTools');
              $output = $pdo->getChunk('testChunk', array('some_placeholder' => 'value'));
                Андрей
                05 декабря 2015, 11:27
                0
                Спасибо, я не совсем понимал логику работы парсера.
                  Василий Наумкин
                  05 декабря 2015, 11:32
                  +1
                  MODX не знает ничего про Fenom, только pdoTools с ним может работать. Соотвественно, и обрабатывать чанк нужно не через методы MODX, а через методы pdoTools.
                    Андрей
                    05 декабря 2015, 11:34
                    0
                    Помню, что в документации написано было, «Эти способы загрузки чанков работают во всех родных сниппетах pdoTools и во всех других, которые используют методы pdoTools getChunk и parseChunk.» Но почему-то решил, что когда в системных настройках указать joxi.ru/VrwWGlWfKKxvMr, то это распространиться и на обычные чанки.

                    P.S. Вот теперь fenom будет у меня повсюду :)
                      Василий Столейков
                      05 декабря 2015, 20:53
                      0
                      Спасибо за это разъяснение! Буду уже везде pdoTools использовать для работы с чанками.
                      Но всё же это не решает проблему с сниппетами, которые писал не я, например с Office или HybridAuth — у них точно такая же проблема с плейсхолдерами.
            Воеводский Михаил
            05 декабря 2015, 14:11
            0
            Николай, обзор интересный, спасибо. Но получился неполным из-за незамеченных тобой системных настроек, на которые Илья уже указал.

            Ждать вторую, более развернутую часть сравнения?
              Fi1osof
              05 декабря 2015, 17:03
              +1
              Не за что.

              Более развернутую? Вряд ли. При включенных $modx и php в рамках самого MODX API и php никаких отличий не может быть. А по поводу отличий на уровне самой шаблонизации я в топике писал:


              На самом деле ряд моментов я опущу, так как все это такие тонкости,
              То есть какие-то моменты я уже начал писать, но потом стер, так как все-таки под разные задачи создавались эти модули. Феном уступает в некоторых моментах (пару примеров все-таки ниже приведу), но зато он добавляет логику на уровне самих MODX-шаблонов и чанков. modxSmarty мощнее в плане самой шаблонизации, но не отрабатывает смарти-теги в самих MODX-шаблонах и чанках (хотя техническая возможность на самом деле есть, и если есть интерес к этому, это можно обсудить отдельно).

              По поводу более расширенных возможностей Смарти:
              Пример 1. Скрываемые блоки hide.
              В основном шаблоне мы пишем
              {block banner hide}<div class="banner">
              	{$smarty.block.child}
              </div>{/block}

              Во-первых, я не увидел в Феноме возможность указывать дочерние блоки. То есть можно в расширяющем шаблоне в блоке прописать {parent} и туда выведется родительский блок, но нельзя указать {child}, чтобы туда вывелось содержимое дочернего блока.
              Во-вторых, интересен блок hide (его можно использовать и без $smarty.block.child, он от него не зависит) — если в расширяющем шаблоне нет этого блока или содержимое блока будет пустым, то и родительский блок не выводится. В нашем случае, если в расширяющем шаблоне не будет определен сам баннер, то и базовое обрамление тоже не будет выведено.

              Пример 2. Инклюды с кешированием.
              Тоже этого не увидел в Феноме.
              Всем известно, что MODX кеширует конечный код для каждой страницы в отдельности. То есть даже если у вас кешируемый чанк, то на каждой отдельной странице при первом заходе он будет отрабатываться. Но если вещи, которые вообще не меняются на всем сайте (например, подвал). В Смарти я могу вот так легко сделать: {include $template cache_id=«footer»} и этот шаблон будет вызван только один раз, а все остальные вызовы будут уже из кеша.
              Андрей Копп
              05 декабря 2015, 22:31
              0
              habrahabr.ru/post/169525/ — если брать на заметку анализ производительности, то Fenom решает.
                Fi1osof
                05 декабря 2015, 22:36
                0
                Это обсуждали еще в прошлой статье. При повторном заходе разница не существенная.
                Fi1osof
                09 декабря 2015, 15:22
                0
                По феному вскрылся серьезный минус, касающийся производительности. modx.pro/help/7278/#comment-51096
                Василий, это будет исправлено?
                  Андрей
                  10 декабря 2015, 11:24
                  0
                  github.com/fenom-template/fenom/blob/10c8109bb545afc6ab599aee273e6978f2789ae6/docs/ru/mods/date_format.md — не работает модификатор даты, как в примере. То есть формат меняет без проблем, но если я попытаюсь вывести в таком виде "{$ts|date_format:"-1 day"}"
                  то выведет "-1 day"
                    Антон Фомичёв
                    10 декабря 2015, 16:34
                    0
                    в таком виде "{$ts|date_format:"-1 day"}"
                    А зачем кавычки вокруг фигурных скобок?
                      Fi1osof
                      10 декабря 2015, 17:12
                      0
                      Кавычки судя по всему нужны все-таки, только тогда уж одинарные в таком виде "{$ts|date_format:'-1 day'}"
                        Андрей
                        10 декабря 2015, 17:47
                        0
                        та кавычки здесь не при чем, я в комменте выделил для наглядности. Суть в другом
                        {$ts|date_format:"%Y/%m/%d %H:%M:%s"} — работает
                        {$ts|date_format:"-1 day"} — не работает
                          Fi1osof
                          10 декабря 2015, 17:52
                          0
                          Кавычки при чем. Мы же все-таки на форуме программистов, и каждый символ имеет значение.
                          ОК, если у вас там нет ковычек, тогда уже вопрос к самому феному.
                      Илья Уткин
                      10 декабря 2015, 19:27
                      1
                      0
                      В документации, видимо, ошибка. В коде не совсем так.

                      Попробуйте
                      {var $ts = date('Y-m-d H:i:s')." -1 day"}
                      {$ts|date:"%Y/%m/%d %H:%M:%s"}
                    Андрей
                    10 декабря 2015, 23:18
                    0
                    Не парсится, в феноме конкатенация через тильду идёт ~. Однако при правильной записи выдаёт текущую дату. Чтобы модификатор прямо к строке добавлять, нужно чтобы дата в опрделенном формате была, не помню в каком точно.

                    Вобщем так работает
                    {set $date = strtotime('+10 days')}
                    {$date|date_format:'%d %b %Y'}
                      Андрей
                      11 декабря 2015, 00:17
                      0
                      Вот пару моментов, может пригодится кому-то

                      1) date() в феноме модификатор и первый аргумент тут дата или timestamp, а второй — формат

                      2) При вызове модификатора date_format, %s (в нижнем регистре) возвращает timestamp. Нужно юзать %S

                      Варианты записи:
                      {set $time = time()}
                      {set $date = date($time,'Y-m-d H:i:s') ~~ '+10 days'}
                      //Или
                      {set $date = $time|date_format:'%Y/%m/%d %H:%M:%S' ~~ '+10 days'}
                      
                      {$date|date_format:'%Y/%m/%d %H:%M:%S'}
                        Илья Уткин
                        11 декабря 2015, 02:29
                        0
                        Кстати, конкатенацию делать необязательно. Попробуйте так:
                        {var $date = date('Y-m-d H:i:s -1 \d\a\y')}
                        {$date|date:"%Y/%m/%d %H:%M:%s"}
                          Илья Уткин
                          11 декабря 2015, 02:33
                          +1
                          Ах, да, это же не PHP… У меня вот так заработало:
                          {var $date = date(time(), 'Y-m-d H:i:s -1 \d\a\y')}
                          {$date|date:"Y/m/d h:m:s"}
                        Авторизуйтесь или зарегистрируйтесь, чтобы оставлять комментарии.
                        45