/
v.bolshakov
/
AIEcosystem-Testing
Обзор
Документация
Войти
/
v.bolshakov
/
AIEcosystem-Testing
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
dev
console/controllers/CustomMessageController.php
315 строк
11 KB
Developer
Initial commit
03 авг 2026, 17:43
03 авг 2026, 17:43
7433917
Код
Авторство
О чём код?
<?php namespace console\controllers; use Yii; use yii\base\InvalidConfigException; use yii\console\controllers\MessageController; use yii\helpers\Console; use yii\twig\ViewRenderer; /** * Extracts messages to be translated from source files (include *.twig). * * Расширение базового контроллера. Добавляет возможность парсить twig-шаблоны * При обработке *.twig файлов компилирует их в *.php и затем уже парсит * Для компиляции используется yii\twig\ViewRenderer, для инстанцирования которого * используется конфигурация из Yii::$app->params['twigViewRenderer'] * * Настройки в @console/main.php * ~~~ * 'controllerMap' => [ * 'customMessage' => [ * 'class' => 'console\controllers\CustomMessageController', * ] * ], * ~~~ * * @author Dmitry E. Semenov <sde.tomsk@gmail.com> * @copyright Self (c) 2019-2020 */ class CustomMessageController extends MessageController { /** * Рендерер twig-шаблонов * * @var null|ViewRenderer */ protected $twigRenderer; /** * Получить рендерер twig-шаблонов * * @return null|object */ public function getTwigRenderer() { if ($this->twigRenderer) { return $this->twigRenderer; } try { $this->twigRenderer = $this->createTwigRenderer(); return $this->twigRenderer; } catch (\Exception $e) { $this->stdout( Console::ansiFormat( "Не удалось инициализировать Twig Renderer. Парсинг twig-шаблонов невозможнен.\n", [Console::FG_RED] ) ); $this->stdout(Console::ansiFormat('Exception: ' . $e->getMessage() . "\n")); return null; } } /** * Создать экземляр \yii\twig\ViewRenderer, используя конфигурационный массив Yii::$app->params['twigViewRenderer'] * * @return ViewRenderer * @throws InvalidConfigException */ protected function createTwigRenderer() { $config = Yii::$app->params['twigViewRenderer']; if (!$config) { throw new InvalidConfigException('Не заданы параметры конфигурации для twig renderer'); } $renderer = Yii::createObject($config); if (!($renderer instanceof ViewRenderer)) { throw new InvalidConfigException('Неверный класс объекта. Ожидался \\yii\\twig\\ViewRenderer, получен ' . get_class($renderer)); } return $renderer; } /** * Извлечь сообщения из twig-шаблона * * @param string $fileName имя файла из которого необходимо извлечь сообещния * @param string $translator имя функции, используемой для перевода * @param array $ignoreCategories категории сообщений, которые необходимо игнорировать * @return array */ protected function extractMessagesFromTwig($fileName, $translator, $ignoreCategories = []) { $coloredFileName = Console::ansiFormat($fileName, [Console::FG_GREEN]); $this->stdout("Extracting messages from $coloredFileName...\n"); $viewRenderer = $this->getTwigRenderer(); if (!$viewRenderer) { return []; } $loader = new \Twig_Loader_Filesystem(dirname($fileName)); $viewRenderer->twig->setLoader($loader); $baseName = pathinfo($fileName, PATHINFO_BASENAME); $subject = $viewRenderer->twig->compileSource( $viewRenderer->twig->getLoader()->getSource($baseName), $baseName ); $messages = []; foreach ((array)$translator as $currentTranslator) { $translatorTokens = token_get_all('<?php ' . $currentTranslator); array_shift($translatorTokens); $tokens = token_get_all($subject); $messages = array_merge_recursive( $messages, $this->extractMessagesFromTokens( $tokens, $translatorTokens, $ignoreCategories ) ); } $this->stdout("\n"); return $messages; } /** * При парсинге twig-шаблонов использовать специальный метод [[extractMessagesFromTwig()]] * * {@inheritdoc} */ protected function extractMessages($fileName, $translator, $ignoreCategories = []) { if (pathinfo($fileName, PATHINFO_EXTENSION) === 'twig') { return $this->extractMessagesFromTwig($fileName, $translator, $ignoreCategories = []); } return parent::extractMessages($fileName, $translator, $ignoreCategories = []); } /** * Перегруженный приватный метод из родительского класса * * @param array $tokens tokens to be processed. * @param array $translatorTokens translator tokens. * @param array $ignoreCategories message categories to ignore. * @return array messages. */ protected function extractMessagesFromTokens(array $tokens, array $translatorTokens, array $ignoreCategories) { $messages = []; $transTokensCount = count($translatorTokens); $matchedTokensCount = 0; $buffer = []; $pendParenthesisCount = 0; foreach ($tokens as $token) { // finding out translator call if ($matchedTokensCount < $transTokensCount) { if ($this->tokensEqual($token, $translatorTokens[$matchedTokensCount])) { $matchedTokensCount++; continue; } $matchedTokensCount = 0; } elseif ($matchedTokensCount === $transTokensCount) { // translator found // end of function call $this->handleToken( $token, $buffer, $messages, $matchedTokensCount, $pendParenthesisCount, $pendParenthesisCount, $transTokensCount, $ignoreCategories, $translatorTokens ); } } return $messages; } /** * Обработать токен * Вынесен из [[extractMessagesFromTokens()]] для уменьшения сложности метода * * @param $token * @param $buffer * @param $messages * @param $matchedTokensCount * @param $pendParenthesisCount * @param $pendParenthesisCount * @param $transTokensCount * @param $ignoreCategories * @param $translatorTokens */ protected function handleToken( &$token, &$buffer, &$messages, &$matchedTokensCount, &$pendParenthesisCount, &$transTokensCount, &$ignoreCategories, &$translatorTokens ) { if ($this->tokensEqual(')', $token)) { $pendParenthesisCount--; $this->extractMessageFromToken( $token, $buffer, $messages, $matchedTokensCount, $pendParenthesisCount, $transTokensCount, $ignoreCategories, $translatorTokens ); } elseif ($this->tokensEqual('(', $token)) { // count beginning of function call, skipping translator beginning if ($pendParenthesisCount > 0) { $buffer[] = $token; } $pendParenthesisCount++; } elseif (isset($token[0]) && !in_array($token[0], [T_WHITESPACE, T_COMMENT])) { // ignore comments and whitespaces $buffer[] = $token; } } /** * Извлечь сообщение из токена * Вынесен из [[handleToken()]] для уменьшения сложности метода * * @param $token * @param $buffer * @param $messages * @param $matchedTokensCount * @param $pendParenthesisCount * @param $pendParenthesisCount * @param $transTokensCount * @param $ignoreCategories * @param $translatorTokens */ protected function extractMessageFromToken( &$token, &$buffer, &$messages, &$matchedTokensCount, &$pendParenthesisCount, &$transTokensCount, &$ignoreCategories, &$translatorTokens ) { if ($pendParenthesisCount === 0) { // end of translator call or end of something that we can't extract if (isset($buffer[0][0], $buffer[1], $buffer[2][0]) && $buffer[0][0] === T_CONSTANT_ENCAPSED_STRING && $buffer[1] === ',' && $buffer[2][0] === T_CONSTANT_ENCAPSED_STRING) { // is valid call we can extract $category = stripcslashes($buffer[0][1]); $category = mb_substr($category, 1, mb_strlen($category) - 2); if (!$this->isCategoryIgnored($category, $ignoreCategories)) { $message = stripcslashes($buffer[2][1]); $message = mb_substr($message, 1, mb_strlen($message) - 2); $messages[$category][] = $message; } $nestedTokens = array_slice($buffer, 3); if (count($nestedTokens) > $transTokensCount) { // search for possible nested translator calls $messages = array_merge_recursive( $messages, $this->extractMessagesFromTokens( $nestedTokens, $translatorTokens, $ignoreCategories ) ); } } elseif ($pendParenthesisCount !== 0) { // invalid call or dynamic call we can't extract $line = Console::ansiFormat($this->getLine($buffer), [Console::FG_CYAN]); $skipping = Console::ansiFormat('Skipping line', [Console::FG_YELLOW]); $this->stdout("$skipping $line. Make sure both category and message are static strings.\n"); } // prepare for the next match $matchedTokensCount = 0; $pendParenthesisCount = 0; $buffer = []; return; } $buffer[] = $token; } }