ViewRenderer.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351
  1. <?php
  2. /**
  3. * @link http://www.yiiframework.com/
  4. * @copyright Copyright (c) 2008 Yii Software LLC
  5. * @license http://www.yiiframework.com/license/
  6. */
  7. namespace yii\twig;
  8. use Yii;
  9. use yii\base\View;
  10. use yii\base\ViewRenderer as BaseViewRenderer;
  11. /**
  12. * TwigViewRenderer allows you to use Twig templates in views.
  13. *
  14. * @property array $lexerOptions @see self::$lexerOptions. This property is write-only.
  15. *
  16. * @author Alexander Makarov <sam@rmcreative.ru>
  17. * @since 2.0
  18. */
  19. class ViewRenderer extends BaseViewRenderer
  20. {
  21. /**
  22. * @var string the directory or path alias pointing to where Twig cache will be stored. Set to false to disable
  23. * templates cache.
  24. */
  25. public $cachePath = '@runtime/Twig/cache';
  26. /**
  27. * @var array Twig options.
  28. * @see http://twig.sensiolabs.org/doc/api.html#environment-options
  29. */
  30. public $options = [];
  31. /**
  32. * @var array Global variables.
  33. * Keys of the array are names to call in template, values are scalar or objects or names of static classes.
  34. * Example: `['html' => ['class' => '\yii\helpers\Html'], 'debug' => YII_DEBUG]`.
  35. * In the template you can use it like this: `{{ html.a('Login', 'site/login') | raw }}`.
  36. */
  37. public $globals = [];
  38. /**
  39. * @var array Custom functions.
  40. * Keys of the array are names to call in template, values are names of functions or static methods of some class.
  41. * Example: `['rot13' => 'str_rot13', 'a' => '\yii\helpers\Html::a']`.
  42. * In the template you can use it like this: `{{ rot13('test') }}` or `{{ a('Login', 'site/login') | raw }}`.
  43. */
  44. public $functions = [];
  45. /**
  46. * @var array Custom filters.
  47. * Keys of the array are names to call in template, values are names of functions or static methods of some class.
  48. * Example: `['rot13' => 'str_rot13', 'jsonEncode' => '\yii\helpers\Json::encode']`.
  49. * In the template you can use it like this: `{{ 'test'|rot13 }}` or `{{ model|jsonEncode }}`.
  50. */
  51. public $filters = [];
  52. /**
  53. * @var array Custom extensions.
  54. * Example: `['Twig_Extension_Sandbox', new \Twig_Extension_Text()]`
  55. */
  56. public $extensions = [];
  57. /**
  58. * @var array Twig lexer options.
  59. *
  60. * Example: Smarty-like syntax:
  61. * ```php
  62. * [
  63. * 'tag_comment' => ['{*', '*}'],
  64. * 'tag_block' => ['{', '}'],
  65. * 'tag_variable' => ['{$', '}']
  66. * ]
  67. * ```
  68. * @see http://twig.sensiolabs.org/doc/recipes.html#customizing-the-syntax
  69. */
  70. public $lexerOptions = [];
  71. /**
  72. * @var array namespaces and classes to import.
  73. *
  74. * Example:
  75. *
  76. * ```php
  77. * [
  78. * 'yii\bootstrap',
  79. * 'app\assets',
  80. * \yii\bootstrap\NavBar::class,
  81. * ]
  82. * ```
  83. */
  84. public $uses = [];
  85. /**
  86. * @var \Twig_Environment twig environment object that renders twig templates
  87. */
  88. public $twig;
  89. /**
  90. * @var string twig namespace to use in templates
  91. * @since 2.0.5
  92. */
  93. public $twigViewsNamespace = \Twig_Loader_Filesystem::MAIN_NAMESPACE;
  94. /**
  95. * @var string twig namespace to use in modules templates
  96. * @since 2.0.5
  97. */
  98. public $twigModulesNamespace = 'modules';
  99. /**
  100. * @var string twig namespace to use in widgets templates
  101. * @since 2.0.5
  102. */
  103. public $twigWidgetsNamespace = 'widgets';
  104. /**
  105. * @var array twig fallback paths
  106. * @since 2.0.5
  107. */
  108. public $twigFallbackPaths = [];
  109. public function init()
  110. {
  111. // Create environment with empty loader
  112. $loader = new Twig_Empty_Loader();
  113. $this->twig = new \Twig_Environment($loader, array_merge([
  114. 'cache' => Yii::getAlias($this->cachePath),
  115. 'charset' => Yii::$app->charset,
  116. ], $this->options));
  117. // Adding custom globals (objects or static classes)
  118. if (!empty($this->globals)) {
  119. $this->addGlobals($this->globals);
  120. }
  121. // Adding custom functions
  122. if (!empty($this->functions)) {
  123. $this->addFunctions($this->functions);
  124. }
  125. // Adding custom filters
  126. if (!empty($this->filters)) {
  127. $this->addFilters($this->filters);
  128. }
  129. $this->addExtensions([new Extension($this->uses)]);
  130. // Adding custom extensions
  131. if (!empty($this->extensions)) {
  132. $this->addExtensions($this->extensions);
  133. }
  134. $this->twig->addGlobal('app', \Yii::$app);
  135. }
  136. /**
  137. * Renders a view file.
  138. *
  139. * This method is invoked by [[View]] whenever it tries to render a view.
  140. * Child classes must implement this method to render the given view file.
  141. *
  142. * @param View $view the view object used for rendering the file.
  143. * @param string $file the view file.
  144. * @param array $params the parameters to be passed to the view file.
  145. *
  146. * @return string the rendering result
  147. */
  148. public function render($view, $file, $params)
  149. {
  150. $this->twig->addGlobal('this', $view);
  151. $loader = new \Twig_Loader_Filesystem(dirname($file));
  152. if ($view instanceof View) {
  153. $this->addFallbackPaths($loader, $view->theme);
  154. }
  155. $this->addAliases($loader, Yii::$aliases);
  156. $this->twig->setLoader($loader);
  157. // Change lexer syntax (must be set after other settings)
  158. if (!empty($this->lexerOptions)) {
  159. $this->setLexerOptions($this->lexerOptions);
  160. }
  161. return $this->twig->render(pathinfo($file, PATHINFO_BASENAME), $params);
  162. }
  163. /**
  164. * Adds aliases
  165. *
  166. * @param \Twig_Loader_Filesystem $loader
  167. * @param array $aliases
  168. */
  169. protected function addAliases($loader, $aliases)
  170. {
  171. foreach ($aliases as $alias => $path) {
  172. if (is_array($path)) {
  173. $this->addAliases($loader, $path);
  174. } elseif (is_string($path) && is_dir($path)) {
  175. $loader->addPath($path, substr($alias, 1));
  176. }
  177. }
  178. }
  179. /**
  180. * Adds fallback paths to twig loader
  181. *
  182. * @param \Twig_Loader_Filesystem $loader
  183. * @param \yii\base\Theme|null $theme
  184. * @since 2.0.5
  185. */
  186. protected function addFallbackPaths($loader, $theme)
  187. {
  188. foreach ($this->twigFallbackPaths as $namespace => $path) {
  189. $path = Yii::getAlias($path);
  190. if (!is_dir($path)) {
  191. continue;
  192. }
  193. if (is_string($namespace)) {
  194. $loader->addPath($path, $namespace);
  195. } else {
  196. $loader->addPath($path);
  197. }
  198. }
  199. if ($theme instanceOf \yii\base\Theme && is_array($theme->pathMap)) {
  200. $pathMap = $theme->pathMap;
  201. if (isset($pathMap['@app/views'])) {
  202. foreach ((array)$pathMap['@app/views'] as $path) {
  203. $path = Yii::getAlias($path);
  204. if (is_dir($path)) {
  205. $loader->addPath($path, $this->twigViewsNamespace);
  206. }
  207. }
  208. }
  209. if (isset($pathMap['@app/modules'])) {
  210. foreach ((array)$pathMap['@app/modules'] as $path) {
  211. $path = Yii::getAlias($path);
  212. if (is_dir($path)) {
  213. $loader->addPath($path, $this->twigModulesNamespace);
  214. }
  215. }
  216. }
  217. if (isset($pathMap['@app/widgets'])) {
  218. foreach ((array)$pathMap['@app/widgets'] as $path) {
  219. $path = Yii::getAlias($path);
  220. if (is_dir($path)) {
  221. $loader->addPath($path, $this->twigWidgetsNamespace);
  222. }
  223. }
  224. }
  225. }
  226. $defaultViewsPath = Yii::getAlias('@app/views');
  227. if (is_dir($defaultViewsPath)) {
  228. $loader->addPath($defaultViewsPath, $this->twigViewsNamespace);
  229. }
  230. $defaultModulesPath = Yii::getAlias('@app/modules');
  231. if (is_dir($defaultModulesPath)) {
  232. $loader->addPath($defaultModulesPath, $this->twigModulesNamespace);
  233. }
  234. $defaultWidgetsPath = Yii::getAlias('@app/widgets');
  235. if (is_dir($defaultWidgetsPath)) {
  236. $loader->addPath($defaultWidgetsPath, $this->twigWidgetsNamespace);
  237. }
  238. }
  239. /**
  240. * Adds global objects or static classes
  241. * @param array $globals @see self::$globals
  242. */
  243. public function addGlobals($globals)
  244. {
  245. foreach ($globals as $name => $value) {
  246. if (is_array($value) && isset($value['class'])) {
  247. $value = new ViewRendererStaticClassProxy($value['class']);
  248. }
  249. $this->twig->addGlobal($name, $value);
  250. }
  251. }
  252. /**
  253. * Adds custom functions
  254. * @param array $functions @see self::$functions
  255. */
  256. public function addFunctions($functions)
  257. {
  258. $this->_addCustom('Function', $functions);
  259. }
  260. /**
  261. * Adds custom filters
  262. * @param array $filters @see self::$filters
  263. */
  264. public function addFilters($filters)
  265. {
  266. $this->_addCustom('Filter', $filters);
  267. }
  268. /**
  269. * Adds custom extensions
  270. * @param array $extensions @see self::$extensions
  271. */
  272. public function addExtensions($extensions)
  273. {
  274. foreach ($extensions as $extName) {
  275. $this->twig->addExtension(is_object($extName) ? $extName : Yii::createObject($extName));
  276. }
  277. }
  278. /**
  279. * Sets Twig lexer options to change templates syntax
  280. * @param array $options @see self::$lexerOptions
  281. */
  282. public function setLexerOptions($options)
  283. {
  284. $lexer = new \Twig_Lexer($this->twig, $options);
  285. $this->twig->setLexer($lexer);
  286. }
  287. /**
  288. * Adds custom function or filter
  289. * @param string $classType 'Function' or 'Filter'
  290. * @param array $elements Parameters of elements to add
  291. * @throws \Exception
  292. */
  293. private function _addCustom($classType, $elements)
  294. {
  295. $classFunction = 'Twig_Simple' . $classType;
  296. foreach ($elements as $name => $func) {
  297. $twigElement = null;
  298. switch ($func) {
  299. // Callable (including just a name of function).
  300. case is_callable($func):
  301. $twigElement = new $classFunction($name, $func);
  302. break;
  303. // Callable (including just a name of function) + options array.
  304. case is_array($func) && is_callable($func[0]):
  305. $twigElement = new $classFunction($name, $func[0], (!empty($func[1]) && is_array($func[1])) ? $func[1] : []);
  306. break;
  307. case $func instanceof \Twig_SimpleFunction || $func instanceof \Twig_SimpleFilter:
  308. $twigElement = $func;
  309. }
  310. if ($twigElement !== null) {
  311. $this->twig->{'add'.$classType}($twigElement);
  312. } else {
  313. throw new \Exception("Incorrect options for \"$classType\" $name.");
  314. }
  315. }
  316. }
  317. }