Enhance perf of Template::getAttribute()

This commit is contained in:
Nicolas Grekas
2016-11-10 11:44:33 +01:00
parent dc3533c347
commit bdbfb15111
2 changed files with 61 additions and 20 deletions
+52 -20
View File
@@ -10,6 +10,10 @@
* file that was distributed with this source code. * file that was distributed with this source code.
*/ */
if (PHP_VERSION_ID >= 50600) {
require_once __DIR__.'/twig_call_method.php';
}
/** /**
* Default base class for compiled templates. * Default base class for compiled templates.
* *
@@ -17,6 +21,9 @@
*/ */
abstract class Twig_Template implements Twig_TemplateInterface abstract class Twig_Template implements Twig_TemplateInterface
{ {
/**
* @internal
*/
protected static $cache = array(); protected static $cache = array();
protected $parent; protected $parent;
@@ -467,6 +474,8 @@ abstract class Twig_Template implements Twig_TemplateInterface
* @return mixed The attribute value, or a Boolean when $isDefinedTest is true, or null when the attribute is not set and $ignoreStrictCheck is true * @return mixed The attribute value, or a Boolean when $isDefinedTest is true, or null when the attribute is not set and $ignoreStrictCheck is true
* *
* @throws Twig_Error_Runtime if the attribute does not exist and Twig is running in strict mode and $isDefinedTest is false * @throws Twig_Error_Runtime if the attribute does not exist and Twig is running in strict mode and $isDefinedTest is false
*
* @internal
*/ */
protected function getAttribute($object, $item, array $arguments = array(), $type = self::ANY_CALL, $isDefinedTest = false, $ignoreStrictCheck = false) protected function getAttribute($object, $item, array $arguments = array(), $type = self::ANY_CALL, $isDefinedTest = false, $ignoreStrictCheck = false)
{ {
@@ -474,7 +483,7 @@ abstract class Twig_Template implements Twig_TemplateInterface
if (self::METHOD_CALL !== $type) { if (self::METHOD_CALL !== $type) {
$arrayItem = is_bool($item) || is_float($item) ? (int) $item : $item; $arrayItem = is_bool($item) || is_float($item) ? (int) $item : $item;
if ((is_array($object) && array_key_exists($arrayItem, $object)) if ((is_array($object) && (isset($object[$arrayItem]) || array_key_exists($arrayItem, $object)))
|| ($object instanceof ArrayAccess && isset($object[$arrayItem])) || ($object instanceof ArrayAccess && isset($object[$arrayItem]))
) { ) {
if ($isDefinedTest) { if ($isDefinedTest) {
@@ -555,37 +564,54 @@ abstract class Twig_Template implements Twig_TemplateInterface
$class = get_class($object); $class = get_class($object);
// object method // object method
if (!isset(self::$cache[$class]['methods'])) { if (!isset(self::$cache[$class])) {
// get_class_methods returns all methods accessible in the scope, but we only want public ones to be accessible in templates // get_class_methods returns all methods accessible in the scope, but we only want public ones to be accessible in templates
if ($object instanceof self) { if ($object instanceof self) {
$ref = new ReflectionClass($class); $ref = new ReflectionClass($class);
$methods = array(); $methods = array();
foreach ($ref->getMethods(ReflectionMethod::IS_PUBLIC) as $refMethod) { foreach ($ref->getMethods(ReflectionMethod::IS_PUBLIC) as $refMethod) {
$methodName = strtolower($refMethod->name);
// Accessing the environment from templates is forbidden to prevent untrusted changes to the environment // Accessing the environment from templates is forbidden to prevent untrusted changes to the environment
if ('getenvironment' !== $methodName) { if ('getenvironment' !== strtolower($refMethod->name)) {
$methods[$methodName] = true; $methods[] = $refMethod->name;
} }
} }
self::$cache[$class]['methods'] = $methods;
} else { } else {
self::$cache[$class]['methods'] = array_change_key_case(array_flip(get_class_methods($object))); $methods = get_class_methods($object);
} }
$cache = array();
foreach ($methods as $method) {
$cache[$method] = $method;
$cache[$lcName = strtolower($method)] = $method;
if ('g' === $lcName[0] && 0 === strpos($lcName, 'get')) {
$name = substr($method, 3);
$lcName = substr($lcName, 3);
} elseif ('i' === $lcName[0] && 0 === strpos($lcName, 'is')) {
$name = substr($method, 2);
$lcName = substr($lcName, 2);
} else {
continue;
}
if (!isset($cache[$name])) {
$cache[$name] = $method;
}
if (!isset($cache[$lcName])) {
$cache[$lcName] = $method;
}
}
self::$cache[$class] = $cache;
} }
$call = false; $call = false;
$lcItem = strtolower($item); if (isset(self::$cache[$class][$item])) {
if (isset(self::$cache[$class]['methods'][$lcItem])) { $method = self::$cache[$class][$item];
$method = (string) $item; } elseif (isset(self::$cache[$class][$lcItem = strtolower($item)])) {
} elseif (isset(self::$cache[$class]['methods']['get'.$lcItem])) { $method = self::$cache[$class][$lcItem];
$method = 'get'.$item; } elseif (isset(self::$cache[$class]['__call'])) {
} elseif (isset(self::$cache[$class]['methods']['is'.$lcItem])) { $method = $item;
$method = 'is'.$item;
} elseif (isset(self::$cache[$class]['methods']['__call'])) {
$method = (string) $item;
$call = true; $call = true;
} else { } else {
if ($isDefinedTest) { if ($isDefinedTest) {
@@ -596,7 +622,7 @@ abstract class Twig_Template implements Twig_TemplateInterface
return; return;
} }
throw new Twig_Error_Runtime(sprintf('Neither the property "%1$s" nor one of the methods "%1$s()", "get%1$s()"/"is%1$s()" or "__call()" exist and have public access in class "%2$s".', $item, get_class($object)), -1, $this->getTemplateName()); throw new Twig_Error_Runtime(sprintf('Neither the property "%1$s" nor one of the methods "%1$s()", "get%1$s()"/"is%1$s()" or "__call()" exist and have public access in class "%2$s".', $item, $class), -1, $this->getTemplateName());
} }
if ($isDefinedTest) { if ($isDefinedTest) {
@@ -610,7 +636,13 @@ abstract class Twig_Template implements Twig_TemplateInterface
// Some objects throw exceptions when they have __call, and the method we try // Some objects throw exceptions when they have __call, and the method we try
// to call is not supported. If ignoreStrictCheck is true, we should return null. // to call is not supported. If ignoreStrictCheck is true, we should return null.
try { try {
$ret = call_user_func_array(array($object, $method), $arguments); if (!$arguments) {
$ret = $object->$method();
} elseif (PHP_VERSION_ID >= 50600) {
$ret = twig_call_method($object, $method, $arguments);
} else {
$ret = call_user_func_array(array($object, $method), $arguments);
}
} catch (BadMethodCallException $e) { } catch (BadMethodCallException $e) {
if ($call && ($ignoreStrictCheck || !$this->env->isStrictVariables())) { if ($call && ($ignoreStrictCheck || !$this->env->isStrictVariables())) {
return; return;
+9
View File
@@ -0,0 +1,9 @@
<?php
/**
* @internal
*/
function twig_call_method($object, $method, $arguments)
{
return $object->$method(...$arguments);
}