Environment.php 26KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966
  1. <?php
  2. /*
  3. * This file is part of Twig.
  4. *
  5. * (c) 2009 Fabien Potencier
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. /**
  11. * Stores the Twig configuration.
  12. *
  13. * @package twig
  14. * @author Fabien Potencier <fabien@symfony.com>
  15. */
  16. class Twig_Environment
  17. {
  18. const VERSION = '1.4.0';
  19. protected $charset;
  20. protected $loader;
  21. protected $debug;
  22. protected $autoReload;
  23. protected $cache;
  24. protected $lexer;
  25. protected $parser;
  26. protected $compiler;
  27. protected $baseTemplateClass;
  28. protected $extensions;
  29. protected $parsers;
  30. protected $visitors;
  31. protected $filters;
  32. protected $tests;
  33. protected $functions;
  34. protected $globals;
  35. protected $runtimeInitialized;
  36. protected $loadedTemplates;
  37. protected $strictVariables;
  38. protected $unaryOperators;
  39. protected $binaryOperators;
  40. protected $templateClassPrefix = '__TwigTemplate_';
  41. protected $functionCallbacks;
  42. protected $filterCallbacks;
  43. protected $staging;
  44. /**
  45. * Constructor.
  46. *
  47. * Available options:
  48. *
  49. * * debug: When set to `true`, the generated templates have a __toString()
  50. * method that you can use to display the generated nodes (default to
  51. * false).
  52. *
  53. * * charset: The charset used by the templates (default to utf-8).
  54. *
  55. * * base_template_class: The base template class to use for generated
  56. * templates (default to Twig_Template).
  57. *
  58. * * cache: An absolute path where to store the compiled templates, or
  59. * false to disable compilation cache (default)
  60. *
  61. * * auto_reload: Whether to reload the template is the original source changed.
  62. * If you don't provide the auto_reload option, it will be
  63. * determined automatically base on the debug value.
  64. *
  65. * * strict_variables: Whether to ignore invalid variables in templates
  66. * (default to false).
  67. *
  68. * * autoescape: Whether to enable auto-escaping (default to true);
  69. *
  70. * * optimizations: A flag that indicates which optimizations to apply
  71. * (default to -1 which means that all optimizations are enabled;
  72. * set it to 0 to disable)
  73. *
  74. * @param Twig_LoaderInterface $loader A Twig_LoaderInterface instance
  75. * @param array $options An array of options
  76. */
  77. public function __construct(Twig_LoaderInterface $loader = null, $options = array())
  78. {
  79. if (null !== $loader) {
  80. $this->setLoader($loader);
  81. }
  82. $options = array_merge(array(
  83. 'debug' => false,
  84. 'charset' => 'UTF-8',
  85. 'base_template_class' => 'Twig_Template',
  86. 'strict_variables' => false,
  87. 'autoescape' => true,
  88. 'cache' => false,
  89. 'auto_reload' => null,
  90. 'optimizations' => -1,
  91. ), $options);
  92. $this->debug = (bool) $options['debug'];
  93. $this->charset = $options['charset'];
  94. $this->baseTemplateClass = $options['base_template_class'];
  95. $this->autoReload = null === $options['auto_reload'] ? $this->debug : (bool) $options['auto_reload'];
  96. $this->extensions = array(
  97. 'core' => new Twig_Extension_Core(),
  98. 'escaper' => new Twig_Extension_Escaper((bool) $options['autoescape']),
  99. 'optimizer' => new Twig_Extension_Optimizer($options['optimizations']),
  100. );
  101. $this->strictVariables = (bool) $options['strict_variables'];
  102. $this->runtimeInitialized = false;
  103. $this->setCache($options['cache']);
  104. $this->functionCallbacks = array();
  105. $this->filterCallbacks = array();
  106. }
  107. /**
  108. * Gets the base template class for compiled templates.
  109. *
  110. * @return string The base template class name
  111. */
  112. public function getBaseTemplateClass()
  113. {
  114. return $this->baseTemplateClass;
  115. }
  116. /**
  117. * Sets the base template class for compiled templates.
  118. *
  119. * @param string $class The base template class name
  120. */
  121. public function setBaseTemplateClass($class)
  122. {
  123. $this->baseTemplateClass = $class;
  124. }
  125. /**
  126. * Enables debugging mode.
  127. */
  128. public function enableDebug()
  129. {
  130. $this->debug = true;
  131. }
  132. /**
  133. * Disables debugging mode.
  134. */
  135. public function disableDebug()
  136. {
  137. $this->debug = false;
  138. }
  139. /**
  140. * Checks if debug mode is enabled.
  141. *
  142. * @return Boolean true if debug mode is enabled, false otherwise
  143. */
  144. public function isDebug()
  145. {
  146. return $this->debug;
  147. }
  148. /**
  149. * Enables the auto_reload option.
  150. */
  151. public function enableAutoReload()
  152. {
  153. $this->autoReload = true;
  154. }
  155. /**
  156. * Disables the auto_reload option.
  157. */
  158. public function disableAutoReload()
  159. {
  160. $this->autoReload = false;
  161. }
  162. /**
  163. * Checks if the auto_reload option is enabled.
  164. *
  165. * @return Boolean true if auto_reload is enabled, false otherwise
  166. */
  167. public function isAutoReload()
  168. {
  169. return $this->autoReload;
  170. }
  171. /**
  172. * Enables the strict_variables option.
  173. */
  174. public function enableStrictVariables()
  175. {
  176. $this->strictVariables = true;
  177. }
  178. /**
  179. * Disables the strict_variables option.
  180. */
  181. public function disableStrictVariables()
  182. {
  183. $this->strictVariables = false;
  184. }
  185. /**
  186. * Checks if the strict_variables option is enabled.
  187. *
  188. * @return Boolean true if strict_variables is enabled, false otherwise
  189. */
  190. public function isStrictVariables()
  191. {
  192. return $this->strictVariables;
  193. }
  194. /**
  195. * Gets the cache directory or false if cache is disabled.
  196. *
  197. * @return string|false
  198. */
  199. public function getCache()
  200. {
  201. return $this->cache;
  202. }
  203. /**
  204. * Sets the cache directory or false if cache is disabled.
  205. *
  206. * @param string|false $cache The absolute path to the compiled templates,
  207. * or false to disable cache
  208. */
  209. public function setCache($cache)
  210. {
  211. $this->cache = $cache ? $cache : false;
  212. }
  213. /**
  214. * Gets the cache filename for a given template.
  215. *
  216. * @param string $name The template name
  217. *
  218. * @return string The cache file name
  219. */
  220. public function getCacheFilename($name)
  221. {
  222. if (false === $this->cache) {
  223. return false;
  224. }
  225. $class = substr($this->getTemplateClass($name), strlen($this->templateClassPrefix));
  226. return $this->getCache().'/'.substr($class, 0, 2).'/'.substr($class, 2, 2).'/'.substr($class, 4).'.php';
  227. }
  228. /**
  229. * Gets the template class associated with the given string.
  230. *
  231. * @param string $name The name for which to calculate the template class name
  232. *
  233. * @return string The template class name
  234. */
  235. public function getTemplateClass($name)
  236. {
  237. return $this->templateClassPrefix.md5($this->loader->getCacheKey($name));
  238. }
  239. /**
  240. * Gets the template class prefix.
  241. *
  242. * @return string The template class prefix
  243. */
  244. public function getTemplateClassPrefix()
  245. {
  246. return $this->templateClassPrefix;
  247. }
  248. /**
  249. * Renders a template.
  250. *
  251. * @param string $name The template name
  252. * @param array $context An array of parameters to pass to the template
  253. *
  254. * @return string The rendered template
  255. */
  256. public function render($name, array $context = array())
  257. {
  258. return $this->loadTemplate($name)->render($context);
  259. }
  260. /**
  261. * Displays a template.
  262. *
  263. * @param string $name The template name
  264. * @param array $context An array of parameters to pass to the template
  265. */
  266. public function display($name, array $context = array())
  267. {
  268. $this->loadTemplate($name)->display($context);
  269. }
  270. /**
  271. * Loads a template by name.
  272. *
  273. * @param string $name The template name
  274. *
  275. * @return Twig_TemplateInterface A template instance representing the given template name
  276. */
  277. public function loadTemplate($name)
  278. {
  279. $cls = $this->getTemplateClass($name);
  280. if (isset($this->loadedTemplates[$cls])) {
  281. return $this->loadedTemplates[$cls];
  282. }
  283. if (!class_exists($cls, false)) {
  284. if (false === $cache = $this->getCacheFilename($name)) {
  285. eval('?>'.$this->compileSource($this->loader->getSource($name), $name));
  286. } else {
  287. if (!is_file($cache) || ($this->isAutoReload() && !$this->isTemplateFresh($name, filemtime($cache)))) {
  288. $this->writeCacheFile($cache, $this->compileSource($this->loader->getSource($name), $name));
  289. }
  290. require_once $cache;
  291. }
  292. }
  293. if (!$this->runtimeInitialized) {
  294. $this->initRuntime();
  295. }
  296. return $this->loadedTemplates[$cls] = new $cls($this);
  297. }
  298. /**
  299. * Returns true if the template is still fresh.
  300. *
  301. * Besides checking the loader for freshness information,
  302. * this method also checks if the enabled extensions have
  303. * not changed.
  304. *
  305. * @param string $name The template name
  306. * @param timestamp $time The last modification time of the cached template
  307. *
  308. * @return Boolean true if the template is fresh, false otherwise
  309. */
  310. public function isTemplateFresh($name, $time)
  311. {
  312. foreach ($this->extensions as $extension) {
  313. $r = new ReflectionObject($extension);
  314. if (filemtime($r->getFileName()) > $time) {
  315. return false;
  316. }
  317. }
  318. return $this->loader->isFresh($name, $time);
  319. }
  320. public function resolveTemplate($names)
  321. {
  322. if (!is_array($names)) {
  323. $names = array($names);
  324. }
  325. foreach ($names as $name) {
  326. if ($name instanceof Twig_Template) {
  327. return $name;
  328. }
  329. try {
  330. return $this->loadTemplate($name);
  331. } catch (Twig_Error_Loader $e) {
  332. }
  333. }
  334. if (1 === count($names)) {
  335. throw $e;
  336. }
  337. throw new Twig_Error_Loader(sprintf('Unable to find one of the following templates: "%s".', implode('", "', $names)));
  338. }
  339. /**
  340. * Clears the internal template cache.
  341. */
  342. public function clearTemplateCache()
  343. {
  344. $this->loadedTemplates = array();
  345. }
  346. /**
  347. * Clears the template cache files on the filesystem.
  348. */
  349. public function clearCacheFiles()
  350. {
  351. if (false === $this->cache) {
  352. return;
  353. }
  354. foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($this->cache), RecursiveIteratorIterator::LEAVES_ONLY) as $file) {
  355. if ($file->isFile()) {
  356. @unlink($file->getPathname());
  357. }
  358. }
  359. }
  360. /**
  361. * Gets the Lexer instance.
  362. *
  363. * @return Twig_LexerInterface A Twig_LexerInterface instance
  364. */
  365. public function getLexer()
  366. {
  367. if (null === $this->lexer) {
  368. $this->lexer = new Twig_Lexer($this);
  369. }
  370. return $this->lexer;
  371. }
  372. /**
  373. * Sets the Lexer instance.
  374. *
  375. * @param Twig_LexerInterface A Twig_LexerInterface instance
  376. */
  377. public function setLexer(Twig_LexerInterface $lexer)
  378. {
  379. $this->lexer = $lexer;
  380. }
  381. /**
  382. * Tokenizes a source code.
  383. *
  384. * @param string $source The template source code
  385. * @param string $name The template name
  386. *
  387. * @return Twig_TokenStream A Twig_TokenStream instance
  388. */
  389. public function tokenize($source, $name = null)
  390. {
  391. return $this->getLexer()->tokenize($source, $name);
  392. }
  393. /**
  394. * Gets the Parser instance.
  395. *
  396. * @return Twig_ParserInterface A Twig_ParserInterface instance
  397. */
  398. public function getParser()
  399. {
  400. if (null === $this->parser) {
  401. $this->parser = new Twig_Parser($this);
  402. }
  403. return $this->parser;
  404. }
  405. /**
  406. * Sets the Parser instance.
  407. *
  408. * @param Twig_ParserInterface A Twig_ParserInterface instance
  409. */
  410. public function setParser(Twig_ParserInterface $parser)
  411. {
  412. $this->parser = $parser;
  413. }
  414. /**
  415. * Parses a token stream.
  416. *
  417. * @param Twig_TokenStream $tokens A Twig_TokenStream instance
  418. *
  419. * @return Twig_Node_Module A Node tree
  420. */
  421. public function parse(Twig_TokenStream $tokens)
  422. {
  423. return $this->getParser()->parse($tokens);
  424. }
  425. /**
  426. * Gets the Compiler instance.
  427. *
  428. * @return Twig_CompilerInterface A Twig_CompilerInterface instance
  429. */
  430. public function getCompiler()
  431. {
  432. if (null === $this->compiler) {
  433. $this->compiler = new Twig_Compiler($this);
  434. }
  435. return $this->compiler;
  436. }
  437. /**
  438. * Sets the Compiler instance.
  439. *
  440. * @param Twig_CompilerInterface $compiler A Twig_CompilerInterface instance
  441. */
  442. public function setCompiler(Twig_CompilerInterface $compiler)
  443. {
  444. $this->compiler = $compiler;
  445. }
  446. /**
  447. * Compiles a Node.
  448. *
  449. * @param Twig_NodeInterface $node A Twig_NodeInterface instance
  450. *
  451. * @return string The compiled PHP source code
  452. */
  453. public function compile(Twig_NodeInterface $node)
  454. {
  455. return $this->getCompiler()->compile($node)->getSource();
  456. }
  457. /**
  458. * Compiles a template source code.
  459. *
  460. * @param string $source The template source code
  461. * @param string $name The template name
  462. *
  463. * @return string The compiled PHP source code
  464. */
  465. public function compileSource($source, $name = null)
  466. {
  467. try {
  468. return $this->compile($this->parse($this->tokenize($source, $name)));
  469. } catch (Twig_Error $e) {
  470. $e->setTemplateFile($name);
  471. throw $e;
  472. } catch (Exception $e) {
  473. throw new Twig_Error_Runtime(sprintf('An exception has been thrown during the compilation of a template ("%s").', $e->getMessage()), -1, $name, $e);
  474. }
  475. }
  476. /**
  477. * Sets the Loader instance.
  478. *
  479. * @param Twig_LoaderInterface $loader A Twig_LoaderInterface instance
  480. */
  481. public function setLoader(Twig_LoaderInterface $loader)
  482. {
  483. $this->loader = $loader;
  484. }
  485. /**
  486. * Gets the Loader instance.
  487. *
  488. * @return Twig_LoaderInterface A Twig_LoaderInterface instance
  489. */
  490. public function getLoader()
  491. {
  492. return $this->loader;
  493. }
  494. /**
  495. * Sets the default template charset.
  496. *
  497. * @param string $charset The default charset
  498. */
  499. public function setCharset($charset)
  500. {
  501. $this->charset = $charset;
  502. }
  503. /**
  504. * Gets the default template charset.
  505. *
  506. * @return string The default charset
  507. */
  508. public function getCharset()
  509. {
  510. return $this->charset;
  511. }
  512. /**
  513. * Initializes the runtime environment.
  514. */
  515. public function initRuntime()
  516. {
  517. $this->runtimeInitialized = true;
  518. foreach ($this->getExtensions() as $extension) {
  519. $extension->initRuntime($this);
  520. }
  521. }
  522. /**
  523. * Returns true if the given extension is registered.
  524. *
  525. * @param string $name The extension name
  526. *
  527. * @return Boolean Whether the extension is registered or not
  528. */
  529. public function hasExtension($name)
  530. {
  531. return isset($this->extensions[$name]);
  532. }
  533. /**
  534. * Gets an extension by name.
  535. *
  536. * @param string $name The extension name
  537. *
  538. * @return Twig_ExtensionInterface A Twig_ExtensionInterface instance
  539. */
  540. public function getExtension($name)
  541. {
  542. if (!isset($this->extensions[$name])) {
  543. throw new Twig_Error_Runtime(sprintf('The "%s" extension is not enabled.', $name));
  544. }
  545. return $this->extensions[$name];
  546. }
  547. /**
  548. * Registers an extension.
  549. *
  550. * @param Twig_ExtensionInterface $extension A Twig_ExtensionInterface instance
  551. */
  552. public function addExtension(Twig_ExtensionInterface $extension)
  553. {
  554. $this->extensions[$extension->getName()] = $extension;
  555. }
  556. /**
  557. * Removes an extension by name.
  558. *
  559. * @param string $name The extension name
  560. */
  561. public function removeExtension($name)
  562. {
  563. unset($this->extensions[$name]);
  564. }
  565. /**
  566. * Registers an array of extensions.
  567. *
  568. * @param array $extensions An array of extensions
  569. */
  570. public function setExtensions(array $extensions)
  571. {
  572. foreach ($extensions as $extension) {
  573. $this->addExtension($extension);
  574. }
  575. }
  576. /**
  577. * Returns all registered extensions.
  578. *
  579. * @return array An array of extensions
  580. */
  581. public function getExtensions()
  582. {
  583. return $this->extensions;
  584. }
  585. /**
  586. * Registers a Token Parser.
  587. *
  588. * @param Twig_TokenParserInterface $parser A Twig_TokenParserInterface instance
  589. */
  590. public function addTokenParser(Twig_TokenParserInterface $parser)
  591. {
  592. $this->staging['token_parsers'][] = $parser;
  593. }
  594. /**
  595. * Gets the registered Token Parsers.
  596. *
  597. * @return Twig_TokenParserInterface[] An array of Twig_TokenParserInterface instances
  598. */
  599. public function getTokenParsers()
  600. {
  601. if (null === $this->parsers) {
  602. $this->parsers = new Twig_TokenParserBroker();
  603. if (isset($this->staging['token_parsers'])) {
  604. foreach ($this->staging['token_parsers'] as $parser) {
  605. $this->parsers->addTokenParser($parser);
  606. }
  607. }
  608. foreach ($this->getExtensions() as $extension) {
  609. $parsers = $extension->getTokenParsers();
  610. foreach($parsers as $parser) {
  611. if ($parser instanceof Twig_TokenParserInterface) {
  612. $this->parsers->addTokenParser($parser);
  613. } else if ($parser instanceof Twig_TokenParserBrokerInterface) {
  614. $this->parsers->addTokenParserBroker($parser);
  615. } else {
  616. throw new Twig_Error_Runtime('getTokenParsers() must return an array of Twig_TokenParserInterface or Twig_TokenParserBrokerInterface instances');
  617. }
  618. }
  619. }
  620. }
  621. return $this->parsers;
  622. }
  623. /**
  624. * Registers a Node Visitor.
  625. *
  626. * @param Twig_NodeVisitorInterface $visitor A Twig_NodeVisitorInterface instance
  627. */
  628. public function addNodeVisitor(Twig_NodeVisitorInterface $visitor)
  629. {
  630. $this->staging['visitors'][] = $visitor;
  631. }
  632. /**
  633. * Gets the registered Node Visitors.
  634. *
  635. * @return Twig_NodeVisitorInterface[] An array of Twig_NodeVisitorInterface instances
  636. */
  637. public function getNodeVisitors()
  638. {
  639. if (null === $this->visitors) {
  640. $this->visitors = isset($this->staging['visitors']) ? $this->staging['visitors'] : array();
  641. foreach ($this->getExtensions() as $extension) {
  642. $this->visitors = array_merge($this->visitors, $extension->getNodeVisitors());
  643. }
  644. }
  645. return $this->visitors;
  646. }
  647. /**
  648. * Registers a Filter.
  649. *
  650. * @param string $name The filter name
  651. * @param Twig_FilterInterface $visitor A Twig_FilterInterface instance
  652. */
  653. public function addFilter($name, Twig_FilterInterface $filter)
  654. {
  655. $this->staging['filters'][$name] = $filter;
  656. }
  657. /**
  658. * Get a filter by name.
  659. *
  660. * Subclasses may override this method and load filters differently;
  661. * so no list of filters is available.
  662. *
  663. * @param string $name The filter name
  664. *
  665. * @return Twig_Filter|false A Twig_Filter instance or false if the filter does not exists
  666. */
  667. public function getFilter($name)
  668. {
  669. if (null === $this->filters) {
  670. $this->getFilters();
  671. }
  672. if (isset($this->filters[$name])) {
  673. return $this->filters[$name];
  674. }
  675. foreach ($this->filterCallbacks as $callback) {
  676. if (false !== $filter = call_user_func($callback, $name)) {
  677. return $filter;
  678. }
  679. }
  680. return false;
  681. }
  682. public function registerUndefinedFilterCallback($callable)
  683. {
  684. $this->filterCallbacks[] = $callable;
  685. }
  686. /**
  687. * Gets the registered Filters.
  688. *
  689. * @return Twig_FilterInterface[] An array of Twig_FilterInterface instances
  690. */
  691. public function getFilters()
  692. {
  693. if (null === $this->filters) {
  694. $this->filters = isset($this->staging['filters']) ? $this->staging['filters'] : array();
  695. foreach ($this->getExtensions() as $extension) {
  696. $this->filters = array_merge($this->filters, $extension->getFilters());
  697. }
  698. }
  699. return $this->filters;
  700. }
  701. /**
  702. * Registers a Test.
  703. *
  704. * @param string $name The test name
  705. * @param Twig_TestInterface $visitor A Twig_TestInterface instance
  706. */
  707. public function addTest($name, Twig_TestInterface $test)
  708. {
  709. $this->staging['tests'][$name] = $test;
  710. }
  711. /**
  712. * Gets the registered Tests.
  713. *
  714. * @return Twig_TestInterface[] An array of Twig_TestInterface instances
  715. */
  716. public function getTests()
  717. {
  718. if (null === $this->tests) {
  719. $this->tests = isset($this->staging['tests']) ? $this->staging['tests'] : array();
  720. foreach ($this->getExtensions() as $extension) {
  721. $this->tests = array_merge($this->tests, $extension->getTests());
  722. }
  723. }
  724. return $this->tests;
  725. }
  726. /**
  727. * Registers a Function.
  728. *
  729. * @param string $name The function name
  730. * @param Twig_FunctionInterface $function A Twig_FunctionInterface instance
  731. */
  732. public function addFunction($name, Twig_FunctionInterface $function)
  733. {
  734. $this->staging['functions'][$name] = $function;
  735. }
  736. /**
  737. * Get a function by name.
  738. *
  739. * Subclasses may override this method and load functions differently;
  740. * so no list of functions is available.
  741. *
  742. * @param string $name function name
  743. *
  744. * @return Twig_Function|false A Twig_Function instance or false if the function does not exists
  745. */
  746. public function getFunction($name)
  747. {
  748. if (null === $this->functions) {
  749. $this->getFunctions();
  750. }
  751. if (isset($this->functions[$name])) {
  752. return $this->functions[$name];
  753. }
  754. foreach ($this->functionCallbacks as $callback) {
  755. if (false !== $function = call_user_func($callback, $name)) {
  756. return $function;
  757. }
  758. }
  759. return false;
  760. }
  761. public function registerUndefinedFunctionCallback($callable)
  762. {
  763. $this->functionCallbacks[] = $callable;
  764. }
  765. public function getFunctions()
  766. {
  767. if (null === $this->functions) {
  768. $this->functions = isset($this->staging['functions']) ? $this->staging['functions'] : array();
  769. foreach ($this->getExtensions() as $extension) {
  770. $this->functions = array_merge($this->functions, $extension->getFunctions());
  771. }
  772. }
  773. return $this->functions;
  774. }
  775. /**
  776. * Registers a Global.
  777. *
  778. * @param string $name The global name
  779. * @param mixed $value The global value
  780. */
  781. public function addGlobal($name, $value)
  782. {
  783. $this->staging['globals'][$name] = $value;
  784. }
  785. /**
  786. * Gets the registered Globals.
  787. *
  788. * @return array An array of globals
  789. */
  790. public function getGlobals()
  791. {
  792. if (null === $this->globals) {
  793. $this->globals = isset($this->staging['globals']) ? $this->staging['globals'] : array();
  794. foreach ($this->getExtensions() as $extension) {
  795. $this->globals = array_merge($this->globals, $extension->getGlobals());
  796. }
  797. }
  798. return $this->globals;
  799. }
  800. /**
  801. * Gets the registered unary Operators.
  802. *
  803. * @return array An array of unary operators
  804. */
  805. public function getUnaryOperators()
  806. {
  807. if (null === $this->unaryOperators) {
  808. $this->initOperators();
  809. }
  810. return $this->unaryOperators;
  811. }
  812. /**
  813. * Gets the registered binary Operators.
  814. *
  815. * @return array An array of binary operators
  816. */
  817. public function getBinaryOperators()
  818. {
  819. if (null === $this->binaryOperators) {
  820. $this->initOperators();
  821. }
  822. return $this->binaryOperators;
  823. }
  824. protected function initOperators()
  825. {
  826. $this->unaryOperators = array();
  827. $this->binaryOperators = array();
  828. foreach ($this->getExtensions() as $extension) {
  829. $operators = $extension->getOperators();
  830. if (!$operators) {
  831. continue;
  832. }
  833. if (2 !== count($operators)) {
  834. throw new InvalidArgumentException(sprintf('"%s::getOperators()" does not return a valid operators array.', get_class($extension)));
  835. }
  836. $this->unaryOperators = array_merge($this->unaryOperators, $operators[0]);
  837. $this->binaryOperators = array_merge($this->binaryOperators, $operators[1]);
  838. }
  839. }
  840. protected function writeCacheFile($file, $content)
  841. {
  842. if (!is_dir(dirname($file))) {
  843. mkdir(dirname($file), 0777, true);
  844. }
  845. $tmpFile = tempnam(dirname($file), basename($file));
  846. if (false !== @file_put_contents($tmpFile, $content)) {
  847. // rename does not work on Win32 before 5.2.6
  848. if (@rename($tmpFile, $file) || (@copy($tmpFile, $file) && unlink($tmpFile))) {
  849. chmod($file, 0644);
  850. return;
  851. }
  852. }
  853. throw new Twig_Error_Runtime(sprintf('Failed to write cache file "%s".', $file));
  854. }
  855. }