Собственные модификаторы Fenom

Продолжаю разработку новой версии modstore.pro и хочу поделиться простым способом подключения своих модификаторов.

Обычный способ все знают: нужно создать сниппет, который будет принимать переменные $input и $options и вызвать его в чанке вот так:
{$variable | snippet}
Но
  • Сниппеты бывают совсем небольшие и создавать их на каждый чих не хочется
  • Вызов сниппета, это всё-таки дополнительная нагрузка
  • Лично я уже полностью перешел на файлы, и создавать запись в БД для сниппета, только ради его связи с файлом мне совсем не хочется
То есть, я хочу просто вызывать некий код в качестве модификатора. И в этом мне поможет событие pdoToolsOnFenomInit, которое появилось в pdoTools 2.6.0 и мало кто это заметил.

Создаём плагин для события pdoToolsOnFenomInit, и пишем в нём:
<?php
/** @var modX $modx */
switch ($modx->event->name) {
    case 'pdoToolsOnFenomInit':
        /** @var Fenom $fenom
            Мы получаем переменную $fenom при его первой инициализации и можем вызывать его методы. 
            Например, добавим модификатор вывода имени домена сайта из произвольной ссылки.
        */
        $fenom->addModifier('website', function ($input) {
            if (!$url = parse_url($input)) {
                return $input;
            }
            $output = str_replace('www.', '', $url['host']);

            return strtolower($output);
        });
        break;
}
Теперь в любом чанке можно вызывать модификатор website. Так как этот модификатор является внутренним методом Fenom и загружается один раз при его инициализации — работает он быстрее любого сниппета.

<div class="site">
	<span>Сайт дополнения</span>
	<a href="{$package.link}" target="blank">{$package.link | website}</a>
</div>

Таким образом ссылка на страницу дополнения становится гораздо аккуратнее.

Если вы разрабатываете свой пакет, которому нужны собственные модификаторы, то лучше не захламлять плагин и вызывать метод из основного класса:
switch ($modx->event->name) {
    case 'pdoToolsOnFenomInit':
        $Extras = $modx->getService('Extras', 'Extras', MODX_CORE_PATH . 'components/extras/model/');
        if ($Extras) {
            /** @var Fenom $fenom */
            $Extras->addFenomModifiers($fenom);
        }
    break;
}
Если модификаторов много и вам они нужны в отдельных файлах — можно прописать в своём методе логику загрузки их оттуда. Это в любом случае, гораздо удобнее, чем создавать кучу сниппетов.

Кстати говоря, таким образом можно включить еще много чего — смотрите документацию Fenom. За добавление события pdoToolsOnFenomInit благодарим Володю Володина.
Василий Наумкин
14 января 2017, 05:49
modx.pro
28
5 460
+14

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

Дмитрий
14 января 2017, 12:42
0
Лично я уже полностью перешел на файлы, и создавать запись в БД для сниппета, только ради его связи с файлом мне совсем не хочется
Василий, я кажется чего-то не понимаю… Можно же создавать сниппеты без записи в БД…
    Василий Наумкин
    14 января 2017, 13:07
    0
    Это как, покажи?

    Сниппет — в любом случае запись в таблице modSnippet.
      Дмитрий
      14 января 2017, 13:12
      0
      Эмм… Ну, как-то так:
      {$_modx->runSnippet('@FILE:snippets/getRating.php',[
                                              'id' => $_modx->resource.id,
                                              'from' => 1,
                                              'to' => 5,
                                              'tpl' => '@FILE:chunks/infocenter/rating.row.tpl',
                                              'tplOuter' => '@FILE:chunks/infocenter/rating.outer.tpl'
                                          ])}
      Код сниппета:
      <?php
      if ($page = $modx->getObject('modResource',$id)){
          //всякая магия
          return $output
      }
      else {
          return;
      }
      В таблице ничего похожего на getRating нет…
        Василий Наумкин
        14 января 2017, 13:23
        0
        Ну а теперь вызови этот файл в качестве модификатора.
          Дмитрий
          14 января 2017, 13:31
          -1
          что-то упало все. Даже с измененными параметрами…
          Но это не важно. Я просто хотел обратить внимание на неточность о БД и сниппетах. Ведь сниппет не всегда модификатор, поэтому можно спокойно хранить в файлах свои сниппеты… Разве нет?
            Василий Наумкин
            14 января 2017, 13:34
            0
            Сам ты неточность.

            Сниппет — это объект modSnippet, он хранится в БД, наследуется от modScript и имеет массу особенностей, которых нет у php файла. Например, при запуске его код кэшируется в директорию core/cache/includes/elements/modsnippet/.

            То, что pdoTools умеет динамически создавать сниппеты из файлов, не делает эти файлы сниппетами.
            Давай ты теорию сначала подучишь, а потом будешь поправлять, окей?
              Дмитрий
              14 января 2017, 13:40
              +3
              ок. Говорил же, что кажется чего-то не понимаю.
                Сергей Шлоков
                16 января 2017, 08:47
                +1
                На самом деле у сниппетов одна очень важная отличительная особенность — это параметры по умолчанию. Поэтому сниппеты можно вызвать без параметров. А вот вызывать файловый «сниппет» нужно всегда с параметрами. В остальном различия несущественные. Ну и понятно, что кэшировать файл нет необходимости.
                Кстати, сниппеты кэшируются в 2 директории — core/cache/includes/elements/modsnippet/ и core/cache/scripts/elements/modsnippet/.
    Алексей
    16 января 2017, 07:27
    +2
    Было бы классно подхватывать все сниппеты-модификаторы из директории, к примеру из core/components/pdotools/modifiers/*.php автоматом.
      Иван Климчук
      02 февраля 2017, 21:27
      +1
      Об этом написано в заметке, в плагине такая загрузка делается в 3-5 строк. Считайте домашним заданием ;)
        Саша Туманов
        20 апреля 2020, 18:07
        0
        Вот тоже интересно. Хотелось бы хранить код модификаторов в файловой системе, причем не прибегая к штатной возможности создания статических файлов.
        Почему-то конструкция предлагаемая Василием для вызова плагинов из файловой системы, в случае с модификаторами не работает.
        if ($pdoTools = $modx->getService('pdoTools')) {
            $pdoTools->runSnippet('@FILE plugins/my_plugin.php', $scriptProperties);
        }
        Подозреваю, что дело в событии на которое навешивается плагин, но не уверен. Подскажите пожалуйста, как правильно подтягивать код модификаторов из файлов
      Андрей Коробков
      13 февраля 2017, 01:29
      0
      Мне очень часто требуются различные модификаторы — писал все в сниппеты, в основном одна строка — return блабла, и складываю в одну категорию. Это несомненно поможет, спасибо
        Александр
        08 апреля 2017, 04:03
        0
        Подскажите, как в таких модификаторах можно работать с $modx?
        В примере ниже модификатор mTest работает.
        А использование модификатора ch1Count или ch2Count прерывает парсинг, и оставшаяся часть страницы не выводится.
        <?php
        /** @var modx $modx */
        switch ($modx->event->name) {
            case 'pdoToolsOnFenomInit':
                /** @var Fenom $fenom */
                $fenom->addModifier('ch1Count', function ($input) {
                    /** @var modx $modx */
                    $ids = $modx->getChildIds($input);
                    return count($ids);
                });
        
                $fenom->addModifier('ch2Count', function ($input) {
                    /** @var modx $modx */
                    $modx = $this->modx;
                    $ids = $modx->getChildIds($input);
                    return count($ids);
                });
        
                $fenom->addModifier('mTest', function ($input, $props) {
                    return serialize($props);
                });
        
                break;
        }
        В логах вебсервера тишина.
        В то же время, всё работает, если использовать в качестве модификатора сниппет, например chCount
        <?php
         /** @var modx $modx */
        $ids = $modx->getChildIds($input);
        return count($ids);
          Александр
          08 апреля 2017, 04:48
          0
          Как-то сразу не сообразил. Вот решение:
          <?php
          /** @var modx $modx */
          switch ($modx->event->name) {
              case 'pdoToolsOnFenomInit':
                  /** @var Fenom $fenom */
                  $fenom->addModifier('chCount', function ($input) use ($modx) {
                      /** @var modx $modx */
                      $ids = $modx->getChildIds($input);
                      return count($ids);
                  });
          
                  break;
          }
          Дмитрий
          05 ноября 2018, 21:14
          0
          Прошу не кидать в меня чем попало, я новичок в MODX
          Может реализация для знающих понятна, но могли бы Вы по полочкам расписать реализацию для самых новичков.

          Например, у меня есть снипет «lcfirst»:
          <?php
          $str = $input;
          echo mb_lcfirst($str); // какой-То Текст
          
          function mb_lcfirst($str) {
              return mb_strtolower(mb_substr($str, 0, 1)) . mb_substr($str, 1);
          }
          Я вызвать его в чанке вот так:
          {$variable | lcfirst}
          Как переделать, где и какие создавать файлы и тд.? В общем прошу все подробнее расписать на моем примере.

          Заранее, благодарю!
            Сергей Шлоков
            06 ноября 2018, 14:29
            2
            +2
            1. Создаёте плагин с любым именем и следующим содержанием
            <?php
            switch ($modx->event->name) {
                case 'pdoToolsOnFenomInit':
                    /** @var Fenom $fenom */
                    $fenom->addModifier('lcfirst', function ($input) {
                       return mb_strtolower(mb_substr($input, 0, 1)) . mb_substr($input, 1);
                    });
            
                    break;
            }
            2. Отмечаете событие «pdoToolsOnFenomInit» на второй вкладке.
            3. Сохраняете.
            4. Пользуетесь.
              Дмитрий
              11 ноября 2018, 05:04
              0
              Благодарю, все получилось.
            Евгений
            11 марта 2020, 12:31
            0
            на опенсервере все работало нормально, но при переносе на хостинг получил ошибку

            tack trace:
            #0 /home/mh/mh/www/core/components/pdotools/model/pdotools/pdotools.class.php(552) : eval()'d code(11): modScript->{closure}('size')
            #1 /home/mh/mh/www/core/components/pdotools/vendor/fenom/fenom/src/Fenom/Render.php(215): pdoTools->{closure}(Array, Object(Fenom\Render))
            #2 /home/mh/mh/www/core/components/pdotools/vendor/fenom/fenom/src/Fenom/Render.php(215): Closure->__invoke(Array, Object(Fenom\Render))
            #3 /home/mh/mh/www/core/components/pdotools/vendor/fenom/fenom/src/Fenom/Render.php(229): Fenom\Render->display(Array)
            #4 /home/mh/mh/www/core/components/pdotools/model/pdotools/pdotools.class.php(580): Fenom\Render->fetch(Array)
            #5 /home/mh/mh/www/core/components/pdotools/model/pdotools/pdopar in /home/mh/mh/www/core/cache/includes/elements/modplugin/32.include.cache.php on line 48
            [11-Mar-2020 12:17:24 Europe/Moscow] PHP Fatal error:  Uncaught ArgumentCountError: Too few arguments to function modScript::{closure}(), 1 passed in /home/mh/mh/www/core/components/pdotools/vendor/fenom/fenom/src/Fenom/Template.php(487) : eval()'d code on line 360 and exactly 2 expected in /home/mh/mh/www/core/cache/includes/elements/modplugin/37.include.cache.php:5
            Stack trace:
            #0 /home/mh/mh/www/core/components/pdotools/vendor/fenom/fenom/src/Fenom/Template.php(487) : eval()'d code(360): modScript->{closure}(Array)
            #1 /home/mh/mh/www/core/components/pdotools/vendor/fenom/fenom/src/Fenom/Render.php(215): Fenom\Template->{closure}(Array, Object(Fenom\Template))
            #2 /home/mh/mh/www/core/components/pdotools/vendor/fenom/fenom/src/Fenom/Render.php(215): Closure->__invoke(Array, Object(Fenom\Template))
            #3 /home/mh/mh/www/core/components/pdotools/vendor/fenom/fenom/src/Fenom/Template.php(492): Fenom\Render->display(Array)
            #4 /home/mh/mh/www/core/components/pdotools/vendor/fenom/fenom/src/Fenom/Template.php(487) : eval()'d code(7): Fenom\Template->display(Array)
            #5 /ho in /home/mh/mh/www/core/cache/includes/elements/modplugin/37.include.cache.php on line 5

            php 7.2 fpm nginx
              Евгений
              11 марта 2020, 13:45
              0
              Разобрался, дело в несоблюдении сигнатуры, если модификатор определен с двумя параметрами,

              $fenom->addModifier('toflat', function ($input, $key) {
              то и вызов должен быть как {$input | toflat:'key'} иначе если вызвать {$var1 | toflat} то получим ошибку выше.
              R2m0x94 (Vasily)
              09 сентября 2020, 17:27
              +1
              Мне понравилось лёгкость использования. Например если редактируемый кусок html очень длинный в разработке и надо на выходе в браузер уже отдать сдутый html, как вариант использовать блочный модификатор {deflate}{/deflate}, что даёт легкость в читаемости кода и облегчает прогруз на фронтенде.
              $fenom->addBlockFunction('deflate', function (array $params, $content) use ($modx) {
                  $filters = [
                    '/<!--([\s\S]*?)-->/'                                               => '', // Remove HTML Comments (breaks with HTML5 Boilerplate)
                    '/(?<!\S)\/\/\s*[^\r\n]*/'                                          => '', // Remove comments in the form /* */
                    '/\>[^\S ]+/s'                                                      => '>',
                    '/[^\S ]+\</s'                                                      => '<',
                    '/([\t ])+/s'                                                       => ' ',
                    '/^([\t ])+/m'                                                      => '',
                    '/([\t ])+$/m'                                                      => '',
                    '~//[a-zA-Z0-9 ]+$~m'                                               => '',
                    '/[\r\n]+([\t ]?[\r\n]+)+/s'                                        => "\n",
                    '/\>[\r\n\t ]+\</s'                                                 => '><',
                    '/}[\r\n\t ]+/s'                                                    => '}',
                    '/}[\r\n\t ]+,[\r\n\t ]+/s'                                         => '},',
                    '/\)[\r\n\t ]?{[\r\n\t ]+/s'                                        => '){',
                    '/,[\r\n\t ]?{[\r\n\t ]+/s'                                         => ',{',
                    '/\),[\r\n\t ]+/s'                                                  => '),',
                    '/<!--(.*)-->/isU'                                                  => '',
                    '/\r/'                                                              => '', 
                    '/\n/'                                                              => '', 
                    '/\>\s+/'                                                           => '>', 
                    '~([\r\n\t ])?([a-zA-Z0-9]+)=\"([a-zA-Z0-9_\\-]+)\"([\r\n\t ])?~s'  => '$1$2=$3$4', 
                  ];
                  $content = preg_replace(array_keys($filters), array_values($filters), $content);
                  return $content;
              });
                Авторизуйтесь или зарегистрируйтесь, чтобы оставлять комментарии.
                20