Устанавливаем Twig 3.* в Bitrix


Первым делом устанавливаем и подключаем composer в 1C-Bitrix.

Далее, для установки twig 3.*, переходим в /local/php_interface и в консоли выполняем команду

$ composer require twig/twig

Документация twig

После того как шаблонизатор Twig установлен, его нужно инициализировать.

Создаем файл /local/php_interface/include/twig.php, и прописываем в нем

<?php

namespace Vik {

  use \Bitrix\Main\Loader;
  use Bitrix\Main\Config\Option;

  IncludeModuleLangFile(__FILE__);

  class Twig
  {
    private static $twigEnvironment;

    public static function initialize($documentRoot = '', $cachePath = '/bitrix/cache/twig')
    {
      if (empty($documentRoot))
        return false;

      $cachePath = $documentRoot . $cachePath;

      //собираем шаблонизатор
      $loader = new \Twig\Loader\FilesystemLoader($_SERVER["DOCUMENT_ROOT"]);
      self::$twigEnvironment = new \Twig\Environment($loader, array(
        'auto_reload' => true,
        'autoescape' => false,
        'cache' => $cachePath,
        'debug' => false));

      //печатаем массив на странице
      self::$twigEnvironment->addExtension(new \Vik\Twig\Debug());
      //подключаем функции
      self::$twigEnvironment->addExtension(new \Vik\Twig\Bitrix());
      //пользовательские функции
      if (file_exists($_SERVER["DOCUMENT_ROOT"] . '/local/php_interface/twigCustomFunctions.php')
        || file_exists($_SERVER["DOCUMENT_ROOT"] . '/bitrix/php_interface/twigCustomFunctions.php')) {
        self::$twigEnvironment->addExtension(new \Vik\Twig\TwigCustomFunctions());
      }

      //подключаем обработчик шаблонизатора к битриксу
      $GLOBALS['arCustomTemplateEngines']['twig'] = [
        'templateExt' => ['twig', 'html.twig'],
        'function' => 'TwigEngine'
      ];
    }

    public static function renderTemplate($templateFile, $context = [])
    {
      return self::$twigEnvironment->render($templateFile, $context);
    }

    public static function clearCacheFiles()
    {
      self::$twigEnvironment->clearCacheFiles();
    }
  }
}

namespace {
  function TwigEngine($templateFile, $arResult, $arParams, $arLangMessages, $templateFolder, $parentTemplateFolder, $template)
  {
    print \Vik\Twig::renderTemplate($templateFile, array(
      'params' => $arParams,
      'result' => $arResult,
      'langMessages' => $arLangMessages,
      'template' => $template,
      'templateFolder' => $templateFolder,
      'parentTemplateFolder' => $parentTemplateFolder,
    ));

    $component_epilog = $templateFolder . "/component_epilog.php";
    if (file_exists($_SERVER["DOCUMENT_ROOT"] . $component_epilog)) {
      $component = $template->__component;
      $component->SetTemplateEpilog(array(
        "epilogFile" => $component_epilog,
        "templateName" => $template->__name,
        "templateFile" => $template->__file,
        "templateFolder" => $template->__folder,
        "templateData" => false,
      ));
    }
  }
}

Создаем файл /local/php_interface/include/debug.php, и прописываем в нем

<?php

namespace Vik\Twig {

  use Twig\TwigFunction;

  final class Debug extends \Twig\Extension\AbstractExtension
  {
    public function getFunctions(): array
    {
      // dump is safe if var_dump is overridden by xdebug
      $isDumpOutputHtmlSafe = \extension_loaded('xdebug')
        // false means that it was not set (and the default is on) or it explicitly enabled
        && (false === ini_get('xdebug.overload_var_dump') || ini_get('xdebug.overload_var_dump'))
        // false means that it was not set (and the default is on) or it explicitly enabled
        // xdebug.overload_var_dump produces HTML only when html_errors is also enabled
        && (false === ini_get('html_errors') || ini_get('html_errors'))
        || 'cli' === \PHP_SAPI;

      return [
        new TwigFunction('dump', 'vik_twig_debug', ['is_safe' => $isDumpOutputHtmlSafe ? ['html'] : [], 'needs_context' => true, 'needs_environment' => true, 'is_variadic' => true]),
        new TwigFunction('adump', 'vik_twig_debug_admin', ['is_safe' => $isDumpOutputHtmlSafe ? ['html'] : [], 'needs_context' => true, 'needs_environment' => true, 'is_variadic' => true]),
      ];
    }
  }
}

namespace {

  use Bitrix\Main\Context;
  use Twig\Environment;
  use Twig\Template;
  use Twig\TemplateWrapper;
  use Vik\Api\VApi;
  use Vik\Other\Dump;

  function vik_twig_debug(Environment $env, $context, ...$vars)
  {
    if (!$env->isDebug()) {
      return;
    }

    ob_start();

    if (!$vars) {
      $vars = [];
      foreach ($context as $key => $value) {
        if (!$value instanceof Template && !$value instanceof TemplateWrapper) {
          $vars[$key] = $value;
        }
      }

      dump($vars);
    } else {
      dump(...$vars);
    }

    return ob_get_clean();
  }

  function vik_twig_debug_admin(Environment $env, $context, ...$vars)
  {
    if (!$env->isDebug()) {
      return;
    }

    ob_start();

    $status = false;
    if (VApi::isAdmin()) $status = true;
    $request = \Bitrix\Main\Context::getCurrent()->getRequest();
    if ($request->get("all") !== null) $status = true;

    if ($status) {
      if (!$vars) {
        $vars = [];
        foreach ($context as $key => $value) {
          if (!$value instanceof Template && !$value instanceof TemplateWrapper) {
            $vars[$key] = $value;
          }
        }

        dump($vars);
      } else {
        dump(...$vars);
      }
    }

    return ob_get_clean();
  }
}

Создаем файл /local/php_interface/include/bitrix.php, и прописываем в нем

<?php

namespace Vik\Twig {

  use Bitrix\Main\Localization\Loc;
  use Twig\Extension\AbstractExtension;
  use Twig\Extension\GlobalsInterface;
  use Twig\TwigFunction;
  use CBitrixComponent;
  use CMain;

  final class Bitrix extends AbstractExtension implements GlobalsInterface
  {
    const DEFAULT_TEMPLATE_PATH = "/bitrix/templates/.default";

    public function getName()
    {
      return 'bitrix';
    }

    public function getGlobals(): array
    {
      return [
        'APPLICATION' => $GLOBALS['APPLICATION'],
        'LANG' => LANG,
        'POST_FORM_ACTION_URI' => POST_FORM_ACTION_URI,
        'SITE_SERVER_NAME' => SITE_SERVER_NAME,
        'SITE_TEMPLATE_PATH' => SITE_TEMPLATE_PATH,
        'DEFAULT_TEMPLATE_PATH' => self::DEFAULT_TEMPLATE_PATH,
        '_REQUEST' => $_REQUEST,
        '_SESSION' => $_SESSION,
        '_COOKIE' => $_COOKIE,
        '_SERVER' => $_SERVER,
      ];
    }

    public function getFilters()
    {
      return parent::getFilters(); // TODO: Change the autogenerated stub
    }

    public function getFunctions(): array
    {
      return [
        new TwigFunction('isUser', 'vik_twig_isUser'),
        new TwigFunction('isAdmin', 'vik_twig_isAdmin'),
        new TwigFunction('bitrix_sessid_post', 'vik_twig_bitrix_sessid_post', ['str']),
        new TwigFunction('bitrix_sessid_get', 'vik_twig_bitrix_sessid_get', ['str']),
        new TwigFunction('showNote', 'vik_twig_showNote', ['message', 'css_class']),
        new TwigFunction('showError', 'vik_twig_showError', ['message', 'css_class']),
        new TwigFunction('showMessage', 'vik_twig_showMessage', ['message']),
        new TwigFunction('getMessage', '\Bitrix\Main\Localization\Loc::getMessage'),
        new TwigFunction('showComponent', [$this, 'showComponent'])
      ];
    }

    public function showComponent($name, $template = '', $params = [], CBitrixComponent $parentComponent = null, $functionParams = [], $returnResult = false)
    {
      $app = new CMain();
      $app->IncludeComponent($name, $template, $params, $parentComponent, $functionParams, $returnResult);
    }
  }
}

namespace {

  use Bitrix\Main\Localization\Loc;

  /**
   * Проверяем является ли пользователь администратором
   *
   * @return mixed
   */
  function vik_twig_isAdmin()
  {
    return $GLOBALS['USER']->IsAuthorized();
  }

  /**
   * Проверяем является ли пользователь авторизованным
   *
   * @return mixed
   */
  function vik_twig_isUser()
  {
    return $GLOBALS['USER']->IsAdmin();
  }

  /**
   * Сессия post
   *
   * @param string $str
   * @return int|string
   */
  function vik_twig_bitrix_sessid_post($str = '')
  {
    if (!empty($str))
      return bitrix_sessid_post($str);

    return bitrix_sessid_post();
  }

  /**
   * Сессия get
   *
   * @param string $str
   * @return int|string
   */
  function vik_twig_bitrix_sessid_get($str = '')
  {
    if (!empty($str))
      return bitrix_sessid_get($str);

    return bitrix_sessid_get();
  }

  /**
   * @param null $message
   * @param string $css_class
   * @return bool
   */
  function vik_twig_showNote($message = null, $css_class = 'notetext')
  {
    if (empty($message))
      return false;

    ShowError($message, $css_class);
  }

  /**
   * @param null $message
   * @param string $css_class
   * @return bool
   */
  function vik_twig_showError($message = null, $css_class = 'errortext')
  {
    if (empty($message))
      return false;

    ShowNote($message, $css_class);
  }

  /**
   * @param null $message
   * @return bool
   */
  function vik_twig_showMessage($message)
  {
    if (empty($message))
      return false;

    ShowMessage($message);
  }
}

В /local/init.php подключаем наши классы, и инициализируем twig.

//подключаем классы
\CModule::AddAutoloadClasses('', [
  '\Vik\Twig' => '/local/php_interface/include/twig.php',
  '\Vik\Twig\Debug' => '/local/php_interface/include/debug.php',
  '\Vik\Twig\Bitrix' => '/local/php_interface/include/bitrix.php',
]);

//подключаем шаблонизатор
\Vik\Twig::initialize($_SERVER['DOCUMENT_ROOT'], '/bitrix/cache/twig');