├── .gitignore ├── .scrutinizer.yml ├── README.md ├── composer.json ├── docs └── hermitage.md └── src ├── Blade.php ├── BladeCompiler.php ├── BladeProvider.php ├── ViewFinder.php └── functions.php /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor 2 | composer.phar 3 | composer.lock 4 | .DS_Store 5 | /.idea 6 | -------------------------------------------------------------------------------- /.scrutinizer.yml: -------------------------------------------------------------------------------- 1 | filter: 2 | paths: 3 | - 'src/*' 4 | excluded_paths: 5 | - 'vendor/*' 6 | - 'tests/*' 7 | tools: 8 | php_cs_fixer: 9 | config: { level: psr2 } 10 | checks: 11 | php: 12 | code_rating: true 13 | duplication: true -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Latest Stable Version](https://poser.pugx.org/arrilot/bitrix-blade/v/stable.svg)](https://packagist.org/packages/arrilot/bitrix-blade/) 2 | [![Total Downloads](https://img.shields.io/packagist/dt/arrilot/bitrix-blade.svg?style=flat)](https://packagist.org/packages/Arrilot/bitrix-blade) 3 | [![Scrutinizer Quality Score](https://scrutinizer-ci.com/g/arrilot/bitrix-blade/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/arrilot/bitrix-blade/) 4 | 5 | # Данный пакет больше активно не поддерживается 6 | 7 | Причина - мы больше не используем Битрикс в своих проектах. 8 | Если вам интересен этот проект и вы хотите заняться его поддержкой - форкните его и создайте Issue в данном репозитории чтобы мы поместили здесь ссылку на форк. 9 | 10 | Форки: 11 | - https://github.com/Ge1i0N/bitrix-blade 12 | 13 | # Bitrix Blade - интеграция шаблонизатора Blade в Битрикс 14 | 15 | ## Установка 16 | 17 | 1)```composer require arrilot/bitrix-blade``` 18 | 19 | 2) добавляем в init.php 20 | 21 | ```php 22 | 23 | use Arrilot\BitrixBlade\BladeProvider; 24 | 25 | require $_SERVER['DOCUMENT_ROOT']."/vendor/autoload.php"; 26 | 27 | BladeProvider::register(); 28 | ``` 29 | 30 | ## Использование 31 | 32 | Заменяем шаблон компонента с `template.php` на `template.blade` и можно писать на `Blade` 33 | 34 | Директива `@include('path.to.view')` модифицирована следующим образом: 35 | 36 | 1. Сначала view ищется относительно директории текущего шаблона компонента (там где лежит template.blade) 37 | 2. Если не view там не нашёлся, то он ищется относительно базовой директории (по умолчанию `local/views`, но может быть указана другая при вызове `BladeProvider::register()`) 38 | 39 | ## Пользовательские директивы (custom directives) 40 | 41 | Для того чтобы добавить свою директиву, необходимо зарегистрировать её в компиляторе: 42 | 43 | ``` 44 | $compiler = BladeProvider::getCompiler(); 45 | $compiler->directive('directiveName', function ($expression) { 46 | return '...'; 47 | }); 48 | ``` 49 | При установке пакета `BladeProvider::register()` за вас уже автоматически зарегистрировано некоторое количество полезных директив: 50 | 51 | 1. ```@bxComponent``` - аналог ```$APPLICATION->IncludeComponent()``` 52 | 2. ```@block('key')``` и ```@endblock``` - всё что заключено между ними будет выведено в месте, где вызван метод ```$APPLICATION->ShowViewContent('key')``` 53 | 3. ```@lang('key')``` - равносильно ```{!! Bitrix\Main\Localization\Loc::getMessage('key') !!} ``` 54 | 4. ```@auth``` и ```@endauth``` - сокращенная запись `IsAuthorized()) ?> ... ` 55 | 5. ```@guest``` и ```@endguest``` - аналогично, но проверка на неавторизованного юзера. 56 | 6. ```@admin``` и ```@endadmin``` - аналогично, но `$USER->IsAdmin()` 57 | 7. ```@csrf``` - сокращенная форма для `````` 58 | 8. [Директивы по работе с эрмитажем](docs/hermitage.md) 59 | 60 | ## Конфигурация 61 | 62 | При необходимости пути можно поменять в конфигурации. 63 | .settings_extra.php 64 | ```php 65 | 'bitrix-blade' => [ 66 | 'value' => [ 67 | 'baseViewPath' => '/absolute/path/or/path/from/document/root', // по умолчанию 'local/views' 68 | 'cachePath' => '/absolute/path/or/path/from/document/root', // по умолчанию 'local/cache/blade' 69 | ], 70 | 'readonly' => false, 71 | ], 72 | ``` 73 | 74 | ## Очистка кэша 75 | 76 | Для обеспечения высокой скорости работы Blade кэширует скомпилированные шаблоны в php файлы. 77 | В большинстве случаев чистить этот кэш самостоятельно потребности нет, потому что блейд сверяет время модификации файлов шаблонов и кэша и самостоятеьно инвалидирует этот кэш. 78 | Однако в некоторых случаях (например при добавлении новой пользовательской директивы), этот кэш всё-же надо сбросить. 79 | Делается это методом ```BladeProvider::clearCache()``` 80 | 81 | 82 | ## Некоторые моменты 83 | 84 | 1. Битрикс позволяет использовать сторонние шаблонизаторы только в шаблонах компонентов. Шаблоны сайтов только на php. 85 | 2. По понятным причинам наследованием шаблонов в полную силу воспользоваться не получится. 86 | 3. Традиционное расширение `.blade.php` использовать нельзя. Битрикс видя `.php` включает php движок. 87 | 4. Вместо `$this` в шаблоне следует использовать `$template` - например `$template->setFrameMode(true);` 88 | 5. Проверку `` прописывать в blade-шаблоне не нужно, она добавляется в скомпилированные view автоматически. Также вместе с этим выполняется и ```extract($arResult, EXTR_SKIP);``` 89 | 6. Чтобы языковой файл из шаблона подключился, его (этот языковой файл) надо назвать как обычно - `template.php` 90 | 91 | ## Дополнительно 92 | 93 | PhpStorm 94 | 95 | 1. Чтобы включить подсветку синтаксиса в PhpStorm для .blade файлов нужно добавить это расширение в 96 | `Settings->Editor->File Types->Blade` 97 | 2. Чтобы PhpStorm понимал и подсвечивалл должным образом пользовательские директивы из этого пакета их можно добавить в него. Делается это в `Settings->Language & Frameworks->PHP->Blade->Directives` 98 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "arrilot/bitrix-blade", 3 | "license": "MIT", 4 | "keywords": [ 5 | "bitrix", 6 | "blade" 7 | ], 8 | "authors": [ 9 | { 10 | "name": "Nekrasov Ilya", 11 | "email": "nekrasov.ilya90@gmail.com" 12 | } 13 | ], 14 | "homepage": "https://github.com/Arrilot/bitrix-blade", 15 | "require": { 16 | "php": ">=5.4.0", 17 | "illuminate/container": "5.*", 18 | "illuminate/view": "5.*", 19 | "illuminate/events": "5.*", 20 | "arrilot/bitrix-hermitage": "^1" 21 | }, 22 | "autoload": { 23 | "psr-4": { 24 | "Arrilot\\BitrixBlade\\": "src/" 25 | }, 26 | "files": [ 27 | "src/functions.php" 28 | ] 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /docs/hermitage.md: -------------------------------------------------------------------------------- 1 | #Директивы по работе с эрмитажем 2 | 3 | Используется вспомогательный пакет [https://github.com/arrilot/bitrix-hermitage](https://github.com/arrilot/bitrix-hermitage) 4 | 5 | Таблица соответвтия директив и методов пакета 6 | 7 | ```php 8 | @actionEditAndDeleteIBlockElement($element) => Action::editAndDeleteIBlockElement($template, $element), 9 | @actionEditIBlockElement($element) => Action::editIBlockElement($template, $element), 10 | @actionDeleteIBlockElement($element, $confirm = '...') => Action::deleteIBlockElement($template, $element, $confirm = '...'), 11 | 12 | @actionEditAndDeleteIBlockSection($section) => Action::editAndDeleteIBlockSection($template, $section), 13 | @actionEditIBlockSection($section) => Action::editIBlockSection($template, $section), 14 | @actionDeleteIBlockSection($section, $confirm = '...') => Action::deleteIBlockSection($template, $section, $confirm = '...'), 15 | 16 | @actionEditAndDeleteHLBlockElement($element) => Action::editAndDeleteHLBlockElement($template, $element) , 17 | @actionEditHLBlockElement($element) => Action::editHLBlockElement($template, $element), 18 | @actionDeleteHLBlockElement($element, $confirm = '...') => Action::deleteHLBlockElement($template, $element, $confirm = '...'), 19 | 20 | @actionAddForIBlock($iblockId, [...]) => Action::addForIBlock($templateOrComponent, $iblockId, [...]), 21 | ``` 22 | -------------------------------------------------------------------------------- /src/Blade.php: -------------------------------------------------------------------------------- 1 | viewPaths = $viewPaths; 53 | $this->cachePath = $cachePath; 54 | $this->container = $container; 55 | 56 | $this->registerFilesystem(); 57 | $this->registerEvents(); 58 | $this->registerEngineResolver(); 59 | $this->registerViewFinder(); 60 | $this->registerFactory(); 61 | } 62 | 63 | /** 64 | * Getter for view factory. 65 | * 66 | * @return Factory 67 | */ 68 | public function view() 69 | { 70 | return $this->viewFactory; 71 | } 72 | 73 | /** 74 | * Register filesystem in container. 75 | * 76 | * @return void 77 | */ 78 | public function registerFilesystem() 79 | { 80 | $this->container->singleton('files', function () { 81 | return new Filesystem(); 82 | }); 83 | } 84 | 85 | /** 86 | * Register events in container. 87 | * 88 | * @return void 89 | */ 90 | public function registerEvents() 91 | { 92 | $this->container->singleton('events', function () { 93 | return new Dispatcher(); 94 | }); 95 | } 96 | 97 | /** 98 | * Register the engine resolver instance. 99 | * 100 | * @return void 101 | */ 102 | public function registerEngineResolver() 103 | { 104 | $me = $this; 105 | 106 | $this->container->singleton('view.engine.resolver', function () use ($me) { 107 | $resolver = new EngineResolver(); 108 | 109 | $me->registerPhpEngine($resolver); 110 | $me->registerBladeEngine($resolver); 111 | 112 | return $resolver; 113 | }); 114 | } 115 | 116 | /** 117 | * Register the PHP engine implementation. 118 | * 119 | * @param EngineResolver $resolver 120 | * 121 | * @return void 122 | */ 123 | public function registerPhpEngine($resolver) 124 | { 125 | $resolver->register('php', function () { 126 | return new PhpEngine(); 127 | }); 128 | } 129 | 130 | /** 131 | * Register the Blade engine implementation. 132 | * 133 | * @param EngineResolver $resolver 134 | * 135 | * @return void 136 | */ 137 | public function registerBladeEngine($resolver) 138 | { 139 | $me = $this; 140 | $app = $this->container; 141 | 142 | $this->container->singleton('blade.compiler', function ($app) use ($me) { 143 | $cache = $me->cachePath; 144 | 145 | return new BladeCompiler($app['files'], $cache); 146 | }); 147 | 148 | $resolver->register('blade', function () use ($app) { 149 | return new CompilerEngine($app['blade.compiler'], $app['files']); 150 | }); 151 | } 152 | 153 | /** 154 | * Register the view factory. 155 | */ 156 | public function registerFactory() 157 | { 158 | $resolver = $this->container['view.engine.resolver']; 159 | 160 | $finder = $this->container['view.finder']; 161 | 162 | $factory = new Factory($resolver, $finder, $this->container['events']); 163 | $factory->setContainer($this->container); 164 | 165 | //$factory->share('app', $this->container); 166 | $this->viewFactory = $factory; 167 | } 168 | 169 | /** 170 | * Register the view finder implementation. 171 | * 172 | * @return void 173 | */ 174 | public function registerViewFinder() 175 | { 176 | $me = $this; 177 | $this->container->singleton('view.finder', function ($app) use ($me) { 178 | $paths = $me->viewPaths; 179 | 180 | return new ViewFinder($app['files'], $paths); 181 | }); 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /src/BladeCompiler.php: -------------------------------------------------------------------------------- 1 | '; 19 | $result .= ''; 20 | 21 | return $result . parent::compileString($value); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/BladeProvider.php: -------------------------------------------------------------------------------- 1 | ['blade'], 59 | 'function' => 'renderBladeTemplate', 60 | ]; 61 | } 62 | 63 | protected static function isAbsolutePath($path) 64 | { 65 | return $path && ($path[0] === DIRECTORY_SEPARATOR || preg_match('~\A[A-Z]:(?![^/\\\\])~i', $path) > 0); 66 | } 67 | 68 | /** 69 | * Get view factory. 70 | * 71 | * @return Factory 72 | */ 73 | public static function getViewFactory() 74 | { 75 | return static::$viewFactory; 76 | } 77 | 78 | /** 79 | * @return BladeCompiler 80 | */ 81 | public static function getCompiler() 82 | { 83 | return static::$container['blade.compiler']; 84 | } 85 | 86 | /** 87 | * Clear all compiled view files. 88 | */ 89 | public static function clearCache() 90 | { 91 | $path = static::$cachePath; 92 | 93 | if (!$path) { 94 | throw new RuntimeException('Cache path is empty'); 95 | } 96 | 97 | $success = true; 98 | foreach (glob("{$path}/*") as $view) { 99 | try { 100 | if (!@unlink($view)) { 101 | $success = false; 102 | } 103 | } catch (ErrorException $e) { 104 | $success = false; 105 | } 106 | } 107 | 108 | return $success; 109 | } 110 | 111 | /** 112 | * Update paths where blade tries to find additional views. 113 | * 114 | * @param string $templateDir 115 | */ 116 | public static function addTemplateFolderToViewPaths($templateDir) 117 | { 118 | $finder = Container::getInstance()->make('view.finder'); 119 | 120 | $currentPaths = $finder->getPaths(); 121 | $newPaths = [$_SERVER['DOCUMENT_ROOT'].$templateDir]; 122 | 123 | // Полностью перезаписывать пути нельзя, иначе вложенные компоненты + include перестанут работать. 124 | $newPaths = array_values(array_unique(array_merge($newPaths, $currentPaths))); 125 | if (!in_array(static::$baseViewPath, $newPaths)) { 126 | $newPaths[] = static::$baseViewPath; 127 | } 128 | 129 | // Необходимо очистить внутренний кэш ViewFinder-а 130 | // Потому что иначе если в родительском компоненте есть @include('foo'), то при вызове @include('foo') из дочернего, 131 | // он не будет искать foo в дочернем, а сразу подключит foo из родительского компонента 132 | $finder->flush(); 133 | 134 | $finder->setPaths($newPaths); 135 | } 136 | 137 | /** 138 | * Undo addTemplateFolderToViewPaths 139 | * 140 | * @param string $templateDir 141 | */ 142 | public static function removeTemplateFolderFromViewPaths($templateDir) 143 | { 144 | $finder = Container::getInstance()->make('view.finder'); 145 | $currentPaths = $finder->getPaths(); 146 | $finder->setPaths(array_diff($currentPaths, [$_SERVER['DOCUMENT_ROOT'].$templateDir] )); 147 | 148 | // Необходимо очистить внутренний кэш ViewFinder-а 149 | // Потому что иначе если в дочернем компоненте есть @include('foo'), то при вызове @include('foo') в родительском 150 | // после подключения дочернего, 151 | // он не будет искать foo в родительском, а сразу подключит foo из дочернего компонента 152 | $finder->flush(); 153 | } 154 | 155 | /** 156 | * Instantiate service container if it's not instantiated yet. 157 | */ 158 | protected static function instantiateServiceContainer() 159 | { 160 | $container = Container::getInstance(); 161 | 162 | if (!$container) { 163 | $container = new Container(); 164 | Container::setInstance($container); 165 | } 166 | 167 | static::$container = $container; 168 | } 169 | 170 | /** 171 | * Instantiate view factory. 172 | */ 173 | protected static function instantiateViewFactory() 174 | { 175 | static::createDirIfNotExist(static::$baseViewPath); 176 | static::createDirIfNotExist(static::$cachePath); 177 | 178 | $viewPaths = [ 179 | static::$baseViewPath, 180 | ]; 181 | $cache = static::$cachePath; 182 | 183 | $blade = new Blade($viewPaths, $cache, static::$container); 184 | 185 | static::$viewFactory = $blade->view(); 186 | static::$viewFactory->addExtension('blade', 'blade'); 187 | } 188 | 189 | /** 190 | * Create dir if it does not exist. 191 | * 192 | * @param string $path 193 | */ 194 | protected static function createDirIfNotExist($path) 195 | { 196 | if (!file_exists($path)) { 197 | $mask = umask(0); 198 | mkdir($path, 0777, true); 199 | umask($mask); 200 | } 201 | } 202 | 203 | /** 204 | * Register bitrix directives. 205 | */ 206 | protected static function registerBitrixDirectives() 207 | { 208 | $compiler = static::getCompiler(); 209 | 210 | $endIf = function () { 211 | return ''; 212 | }; 213 | 214 | $compiler->directive('bxComponent', function ($expression) { 215 | $expression = rtrim($expression, ')'); 216 | $expression = ltrim($expression, '('); 217 | 218 | return 'IncludeComponent('.$expression.'); ?>'; 219 | }); 220 | 221 | $compiler->directive('block', function ($expression) { 222 | $expression = rtrim($expression, ')'); 223 | $expression = ltrim($expression, '('); 224 | 225 | return ''; 226 | }); 227 | 228 | $compiler->directive('endblock', function () { 229 | return 'AddViewContent($__bx_block, ob_get_clean()); ?>'; 230 | }); 231 | 232 | $compiler->directive('lang', function ($expression) { 233 | return ''; 234 | }); 235 | 236 | $compiler->directive('auth', function () { 237 | return 'IsAuthorized()): ?>'; 238 | }); 239 | $compiler->directive('guest', function () { 240 | return 'IsAuthorized()): ?>'; 241 | }); 242 | $compiler->directive('admin', function () { 243 | return 'IsAdmin()): ?>'; 244 | }); 245 | $compiler->directive('csrf', function ($name = 'sessid') { 246 | $name = !empty($name) ? $name : 'sessid'; 247 | $name = trim($name, '"'); 248 | $name = trim($name, "'"); 249 | return ''; 250 | }); 251 | 252 | $compiler->directive('endauth', $endIf); 253 | $compiler->directive('endguest', $endIf); 254 | $compiler->directive('endadmin', $endIf); 255 | 256 | static::registerHermitageDirectives($compiler); 257 | } 258 | 259 | /** 260 | * @param BladeCompiler $compiler 261 | */ 262 | private static function registerHermitageDirectives($compiler) 263 | { 264 | $simpleDirectives = [ 265 | 'actionAddForIBlock' => 'addForIBlock', 266 | ]; 267 | foreach ($simpleDirectives as $directive => $action) { 268 | $compiler->directive($directive, function ($expression) use ($action) { 269 | $expression = rtrim($expression, ')'); 270 | $expression = ltrim($expression, '('); 271 | return ''; 272 | }); 273 | } 274 | 275 | $echoDirectives = [ 276 | 'actionEditIBlockElement' => 'editIBlockElement', 277 | 'actionDeleteIBlockElement' => 'deleteIBlockElement', 278 | 'actionEditAndDeleteIBlockElement' => 'editAndDeleteIBlockElement', 279 | 280 | 'actionEditIBlockSection' => 'editIBlockSection', 281 | 'actionDeleteIBlockSection' => 'deleteIBlockSection', 282 | 'actionEditAndDeleteIBlockSection' => 'editAndDeleteIBlockSection', 283 | 284 | 'actionEditHLBlockElement' => 'editHLBlockElement', 285 | 'actionDeleteHLBlockElement' => 'deleteHLBlockElement', 286 | 'actionEditAndDeleteHLBlockElement' => 'editAndDeleteHLBlockElement', 287 | ]; 288 | foreach ($echoDirectives as $directive => $action) { 289 | $compiler->directive($directive, function ($expression) use ($action) { 290 | $expression = rtrim($expression, ')'); 291 | $expression = ltrim($expression, '('); 292 | return ''; 293 | }); 294 | } 295 | } 296 | 297 | /** 298 | * @return string|null 299 | */ 300 | public static function getCachePath() 301 | { 302 | return static::$cachePath; 303 | } 304 | } 305 | -------------------------------------------------------------------------------- /src/ViewFinder.php: -------------------------------------------------------------------------------- 1 | paths = $paths; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/functions.php: -------------------------------------------------------------------------------- 1 | __folder); 26 | 27 | global $APPLICATION, $USER; 28 | 29 | echo $view->file($_SERVER['DOCUMENT_ROOT'].$templateFile, compact( 30 | 'arParams', 31 | 'arResult', 32 | 'arLangMessages', 33 | 'template', 34 | 'templateFolder', 35 | 'parentTemplateFolder', 36 | 'APPLICATION', 37 | 'USER' 38 | ))->render(); 39 | 40 | $epilogue = $templateFolder.'/component_epilog.php'; 41 | if (file_exists($_SERVER['DOCUMENT_ROOT'].$epilogue)) { 42 | $component = $template->__component; 43 | $component->SetTemplateEpilog([ 44 | 'epilogFile' => $epilogue, 45 | 'templateName' => $template->__name, 46 | 'templateFile' => $template->__file, 47 | 'templateFolder' => $template->__folder, 48 | 'templateData' => false, 49 | ]); 50 | } 51 | 52 | BladeProvider::removeTemplateFolderFromViewPaths($template->__folder); 53 | } 54 | } 55 | --------------------------------------------------------------------------------