Twig-1.3.0/lib/Twig/Template.php
changeset 4 9a001a04b634
equal deleted inserted replaced
3:6d109e3804ac 4:9a001a04b634
       
     1 <?php
       
     2 
       
     3 /*
       
     4  * This file is part of Twig.
       
     5  *
       
     6  * (c) 2009 Fabien Potencier
       
     7  * (c) 2009 Armin Ronacher
       
     8  *
       
     9  * For the full copyright and license information, please view the LICENSE
       
    10  * file that was distributed with this source code.
       
    11  */
       
    12 
       
    13 /**
       
    14  * Default base class for compiled templates.
       
    15  *
       
    16  * @package twig
       
    17  * @author  Fabien Potencier <fabien@symfony.com>
       
    18  */
       
    19 abstract class Twig_Template implements Twig_TemplateInterface
       
    20 {
       
    21     static protected $cache = array();
       
    22 
       
    23     protected $parents;
       
    24     protected $env;
       
    25     protected $blocks;
       
    26     protected $traits;
       
    27 
       
    28     /**
       
    29      * Constructor.
       
    30      *
       
    31      * @param Twig_Environment $env A Twig_Environment instance
       
    32      */
       
    33     public function __construct(Twig_Environment $env)
       
    34     {
       
    35         $this->env = $env;
       
    36         $this->blocks = array();
       
    37         $this->traits = array();
       
    38     }
       
    39 
       
    40     /**
       
    41      * Returns the template name.
       
    42      *
       
    43      * @return string The template name
       
    44      */
       
    45     abstract public function getTemplateName();
       
    46 
       
    47     /**
       
    48      * Returns the Twig environment.
       
    49      *
       
    50      * @return Twig_Environment The Twig environment
       
    51      */
       
    52     public function getEnvironment()
       
    53     {
       
    54         return $this->env;
       
    55     }
       
    56 
       
    57     /**
       
    58      * Returns the parent template.
       
    59      *
       
    60      * This method is for internal use only and should never be called
       
    61      * directly.
       
    62      *
       
    63      * @return Twig_TemplateInterface|false The parent template or false if there is no parent
       
    64      */
       
    65     public function getParent(array $context)
       
    66     {
       
    67         $parent = $this->doGetParent($context);
       
    68         if (false === $parent) {
       
    69             return false;
       
    70         } elseif ($parent instanceof Twig_Template) {
       
    71             $name = $parent->getTemplateName();
       
    72             $this->parents[$name] = $parent;
       
    73             $parent = $name;
       
    74         } elseif (!isset($this->parents[$parent])) {
       
    75             $this->parents[$parent] = $this->env->loadTemplate($parent);
       
    76         }
       
    77 
       
    78         return $this->parents[$parent];
       
    79     }
       
    80 
       
    81     abstract protected function doGetParent(array $context);
       
    82 
       
    83     /**
       
    84      * Displays a parent block.
       
    85      *
       
    86      * This method is for internal use only and should never be called
       
    87      * directly.
       
    88      *
       
    89      * @param string $name    The block name to display from the parent
       
    90      * @param array  $context The context
       
    91      * @param array  $blocks  The current set of blocks
       
    92      */
       
    93     public function displayParentBlock($name, array $context, array $blocks = array())
       
    94     {
       
    95         if (isset($this->traits[$name])) {
       
    96             $this->traits[$name][0]->displayBlock($name, $context, $blocks);
       
    97         } elseif (false !== $parent = $this->getParent($context)) {
       
    98             $parent->displayBlock($name, $context, $blocks);
       
    99         } else {
       
   100             throw new Twig_Error_Runtime(sprintf('The template has no parent and no traits defining the "%s" block', $name), -1, $this->getTemplateName());
       
   101         }
       
   102     }
       
   103 
       
   104     /**
       
   105      * Displays a block.
       
   106      *
       
   107      * This method is for internal use only and should never be called
       
   108      * directly.
       
   109      *
       
   110      * @param string $name    The block name to display
       
   111      * @param array  $context The context
       
   112      * @param array  $blocks  The current set of blocks
       
   113      */
       
   114     public function displayBlock($name, array $context, array $blocks = array())
       
   115     {
       
   116         if (isset($blocks[$name])) {
       
   117             $b = $blocks;
       
   118             unset($b[$name]);
       
   119             call_user_func($blocks[$name], $context, $b);
       
   120         } elseif (isset($this->blocks[$name])) {
       
   121             call_user_func($this->blocks[$name], $context, $blocks);
       
   122         } elseif (false !== $parent = $this->getParent($context)) {
       
   123             $parent->displayBlock($name, $context, array_merge($this->blocks, $blocks));
       
   124         }
       
   125     }
       
   126 
       
   127     /**
       
   128      * Renders a parent block.
       
   129      *
       
   130      * This method is for internal use only and should never be called
       
   131      * directly.
       
   132      *
       
   133      * @param string $name    The block name to render from the parent
       
   134      * @param array  $context The context
       
   135      * @param array  $blocks  The current set of blocks
       
   136      *
       
   137      * @return string The rendered block
       
   138      */
       
   139     public function renderParentBlock($name, array $context, array $blocks = array())
       
   140     {
       
   141         ob_start();
       
   142         $this->displayParentBlock($name, $context, $blocks);
       
   143 
       
   144         return ob_get_clean();
       
   145     }
       
   146 
       
   147     /**
       
   148      * Renders a block.
       
   149      *
       
   150      * This method is for internal use only and should never be called
       
   151      * directly.
       
   152      *
       
   153      * @param string $name    The block name to render
       
   154      * @param array  $context The context
       
   155      * @param array  $blocks  The current set of blocks
       
   156      *
       
   157      * @return string The rendered block
       
   158      */
       
   159     public function renderBlock($name, array $context, array $blocks = array())
       
   160     {
       
   161         ob_start();
       
   162         $this->displayBlock($name, $context, $blocks);
       
   163 
       
   164         return ob_get_clean();
       
   165     }
       
   166 
       
   167     /**
       
   168      * Returns whether a block exists or not.
       
   169      *
       
   170      * This method is for internal use only and should never be called
       
   171      * directly.
       
   172      *
       
   173      * This method does only return blocks defined in the current template
       
   174      * or defined in "used" traits.
       
   175      *
       
   176      * It does not return blocks from parent templates as the parent
       
   177      * template name can be dynamic, which is only known based on the
       
   178      * current context.
       
   179      *
       
   180      * @param string $name The block name
       
   181      *
       
   182      * @return Boolean true if the block exists, false otherwise
       
   183      */
       
   184     public function hasBlock($name)
       
   185     {
       
   186         return isset($this->blocks[$name]);
       
   187     }
       
   188 
       
   189     /**
       
   190      * Returns all block names.
       
   191      *
       
   192      * This method is for internal use only and should never be called
       
   193      * directly.
       
   194      *
       
   195      * @return array An array of block names
       
   196      *
       
   197      * @see hasBlock
       
   198      */
       
   199     public function getBlockNames()
       
   200     {
       
   201         return array_keys($this->blocks);
       
   202     }
       
   203 
       
   204     /**
       
   205      * Returns all blocks.
       
   206      *
       
   207      * This method is for internal use only and should never be called
       
   208      * directly.
       
   209      *
       
   210      * @return array An array of blocks
       
   211      *
       
   212      * @see hasBlock
       
   213      */
       
   214     public function getBlocks()
       
   215     {
       
   216         return $this->blocks;
       
   217     }
       
   218 
       
   219     /**
       
   220      * Displays the template with the given context.
       
   221      *
       
   222      * @param array $context An array of parameters to pass to the template
       
   223      * @param array $blocks  An array of blocks to pass to the template
       
   224      */
       
   225     public function display(array $context, array $blocks = array())
       
   226     {
       
   227         try {
       
   228             $this->doDisplay($context, $blocks);
       
   229         } catch (Twig_Error $e) {
       
   230             throw $e;
       
   231         } catch (Exception $e) {
       
   232             throw new Twig_Error_Runtime(sprintf('An exception has been thrown during the rendering of a template ("%s").', $e->getMessage()), -1, null, $e);
       
   233         }
       
   234     }
       
   235 
       
   236     /**
       
   237      * Renders the template with the given context and returns it as string.
       
   238      *
       
   239      * @param array $context An array of parameters to pass to the template
       
   240      *
       
   241      * @return string The rendered template
       
   242      */
       
   243     public function render(array $context)
       
   244     {
       
   245         $level = ob_get_level();
       
   246         ob_start();
       
   247         try {
       
   248             $this->display($context);
       
   249         } catch (Exception $e) {
       
   250             while (ob_get_level() > $level) {
       
   251                 ob_end_clean();
       
   252             }
       
   253 
       
   254             throw $e;
       
   255         }
       
   256 
       
   257         return ob_get_clean();
       
   258     }
       
   259 
       
   260     /**
       
   261      * Auto-generated method to display the template with the given context.
       
   262      *
       
   263      * @param array $context An array of parameters to pass to the template
       
   264      * @param array $blocks  An array of blocks to pass to the template
       
   265      */
       
   266     abstract protected function doDisplay(array $context, array $blocks = array());
       
   267 
       
   268     /**
       
   269      * Returns a variable from the context.
       
   270      *
       
   271      * @param array   $context The context
       
   272      * @param string  $item    The variable to return from the context
       
   273      *
       
   274      * @return The content of the context variable
       
   275      *
       
   276      * @throws Twig_Error_Runtime if the variable does not exist and Twig is running in strict mode
       
   277      */
       
   278     protected function getContext($context, $item)
       
   279     {
       
   280         if (!array_key_exists($item, $context)) {
       
   281             if (!$this->env->isStrictVariables()) {
       
   282                 return null;
       
   283             }
       
   284 
       
   285             throw new Twig_Error_Runtime(sprintf('Variable "%s" does not exist', $item));
       
   286         }
       
   287 
       
   288         return $context[$item];
       
   289     }
       
   290 
       
   291     /**
       
   292      * Returns the attribute value for a given array/object.
       
   293      *
       
   294      * @param mixed   $object        The object or array from where to get the item
       
   295      * @param mixed   $item          The item to get from the array or object
       
   296      * @param array   $arguments     An array of arguments to pass if the item is an object method
       
   297      * @param string  $type          The type of attribute (@see Twig_TemplateInterface)
       
   298      * @param Boolean $isDefinedTest Whether this is only a defined check
       
   299      */
       
   300     protected function getAttribute($object, $item, array $arguments = array(), $type = Twig_TemplateInterface::ANY_CALL, $isDefinedTest = false)
       
   301     {
       
   302         // array
       
   303         if (Twig_TemplateInterface::METHOD_CALL !== $type) {
       
   304             if ((is_array($object) && array_key_exists($item, $object))
       
   305                 || ($object instanceof ArrayAccess && isset($object[$item]))
       
   306             ) {
       
   307                 if ($isDefinedTest) {
       
   308                     return true;
       
   309                 }
       
   310 
       
   311                 return $object[$item];
       
   312             }
       
   313 
       
   314             if (Twig_TemplateInterface::ARRAY_CALL === $type) {
       
   315                 if ($isDefinedTest) {
       
   316                     return false;
       
   317                 }
       
   318 
       
   319                 if (!$this->env->isStrictVariables()) {
       
   320                     return null;
       
   321                 }
       
   322 
       
   323                 if (is_object($object)) {
       
   324                     throw new Twig_Error_Runtime(sprintf('Key "%s" in object (with ArrayAccess) of type "%s" does not exist', $item, get_class($object)));
       
   325                 // array
       
   326                 } else {
       
   327                     throw new Twig_Error_Runtime(sprintf('Key "%s" for array with keys "%s" does not exist', $item, implode(', ', array_keys($object))));
       
   328                 }
       
   329             }
       
   330         }
       
   331 
       
   332         if (!is_object($object)) {
       
   333             if ($isDefinedTest) {
       
   334                 return false;
       
   335             }
       
   336 
       
   337             if (!$this->env->isStrictVariables()) {
       
   338                 return null;
       
   339             }
       
   340 
       
   341             throw new Twig_Error_Runtime(sprintf('Item "%s" for "%s" does not exist', $item, $object));
       
   342         }
       
   343 
       
   344         // get some information about the object
       
   345         $class = get_class($object);
       
   346         if (!isset(self::$cache[$class])) {
       
   347             $r = new ReflectionClass($class);
       
   348             self::$cache[$class] = array('methods' => array(), 'properties' => array());
       
   349             foreach ($r->getMethods(ReflectionMethod::IS_PUBLIC) as $method) {
       
   350                 self::$cache[$class]['methods'][strtolower($method->getName())] = true;
       
   351             }
       
   352 
       
   353             foreach ($r->getProperties(ReflectionProperty::IS_PUBLIC) as $property) {
       
   354                 self::$cache[$class]['properties'][$property->getName()] = true;
       
   355             }
       
   356         }
       
   357 
       
   358         // object property
       
   359         if (Twig_TemplateInterface::METHOD_CALL !== $type) {
       
   360             if (isset(self::$cache[$class]['properties'][$item])
       
   361                 || isset($object->$item) || array_key_exists($item, $object)
       
   362             ) {
       
   363                 if ($isDefinedTest) {
       
   364                     return true;
       
   365                 }
       
   366 
       
   367                 if ($this->env->hasExtension('sandbox')) {
       
   368                     $this->env->getExtension('sandbox')->checkPropertyAllowed($object, $item);
       
   369                 }
       
   370 
       
   371                 return $object->$item;
       
   372             }
       
   373         }
       
   374 
       
   375         // object method
       
   376         $lcItem = strtolower($item);
       
   377         if (isset(self::$cache[$class]['methods'][$lcItem])) {
       
   378             $method = $item;
       
   379         } elseif (isset(self::$cache[$class]['methods']['get'.$lcItem])) {
       
   380             $method = 'get'.$item;
       
   381         } elseif (isset(self::$cache[$class]['methods']['is'.$lcItem])) {
       
   382             $method = 'is'.$item;
       
   383         } elseif (isset(self::$cache[$class]['methods']['__call'])) {
       
   384             $method = $item;
       
   385         } else {
       
   386             if ($isDefinedTest) {
       
   387                 return false;
       
   388             }
       
   389 
       
   390             if (!$this->env->isStrictVariables()) {
       
   391                 return null;
       
   392             }
       
   393 
       
   394             throw new Twig_Error_Runtime(sprintf('Method "%s" for object "%s" does not exist', $item, get_class($object)));
       
   395         }
       
   396 
       
   397         if ($isDefinedTest) {
       
   398             return true;
       
   399         }
       
   400 
       
   401         if ($this->env->hasExtension('sandbox')) {
       
   402             $this->env->getExtension('sandbox')->checkMethodAllowed($object, $method);
       
   403         }
       
   404 
       
   405         $ret = call_user_func_array(array($object, $method), $arguments);
       
   406 
       
   407         if ($object instanceof Twig_TemplateInterface) {
       
   408             return new Twig_Markup($ret);
       
   409         }
       
   410 
       
   411         return $ret;
       
   412     }
       
   413 }