Skip to content

Commit

Permalink
Added support foo.some_thing === foo.someThing for properties and arr…
Browse files Browse the repository at this point in the history
…ay calls
  • Loading branch information
hason committed Jan 22, 2014
1 parent e113cf4 commit db8557a
Show file tree
Hide file tree
Showing 3 changed files with 192 additions and 44 deletions.
106 changes: 80 additions & 26 deletions ext/twig/twig.c
Original file line number Diff line number Diff line change
Expand Up @@ -735,10 +735,11 @@ static int twig_add_property_to_class(void *pDest APPLY_TSRMLS_DC, int num_args,
zend_class_entry *ce;
zval *retval;
char *class_name, *prop_name;

zend_property_info *pptr = (zend_property_info *) pDest;
APPLY_TSRMLS_FETCH();

if (!(pptr->flags & ZEND_ACC_PUBLIC) || (pptr->flags & ZEND_ACC_STATIC)) {
if ((pptr->flags & ZEND_ACC_PRIVATE) || (pptr->flags & ZEND_ACC_STATIC)) {
return 0;
}

Expand All @@ -752,6 +753,7 @@ static int twig_add_property_to_class(void *pDest APPLY_TSRMLS_DC, int num_args,
#endif

add_assoc_string(retval, prop_name, prop_name, 1);
add_assoc_string(retval, TWIG_DECAMELIZE(prop_name), prop_name, 1);

return 0;
}
Expand All @@ -771,6 +773,7 @@ static void twig_add_class_to_cache(zval *cache, zval *object, char *class_name
array_init(class_properties);
// add all methods to self::cache[$class]['methods']
zend_hash_apply_with_arguments(&class_ce->function_table APPLY_TSRMLS_CC, twig_add_method_to_class, 1, class_methods);
// add all properties to self::cache[$class]['properties']
zend_hash_apply_with_arguments(&class_ce->properties_info APPLY_TSRMLS_CC, twig_add_property_to_class, 2, &class_ce, class_properties);

add_assoc_zval(class_info, "methods", class_methods);
Expand Down Expand Up @@ -815,14 +818,46 @@ PHP_FUNCTION(twig_template_get_attributes)
type = "any";
}

/*
if (is_object($object)) {
$class = get_class($object);
if (!isset(self::$cache[$class])) {
self::$cache[$class] = $this->getCacheForClass($class);
}
}
*/
if (Z_TYPE_P(object) == IS_OBJECT) {
class_name = TWIG_GET_CLASS_NAME(object TSRMLS_CC);
tmp_self_cache = TWIG_GET_STATIC_PROPERTY(template, "cache" TSRMLS_CC);
tmp_class = TWIG_GET_ARRAY_ELEMENT(tmp_self_cache, class_name, strlen(class_name) TSRMLS_CC);

if (!tmp_class) {
twig_add_class_to_cache(tmp_self_cache, object, class_name TSRMLS_CC);
tmp_class = TWIG_GET_ARRAY_ELEMENT(tmp_self_cache, class_name, strlen(class_name) TSRMLS_CC);
}
efree(class_name);
}

/*
// array
if (Twig_Template::METHOD_CALL !== $type) {
$arrayItem = is_bool($item) || is_float($item) ? (int) $item : $item;
$hasArrayItem = false;
if (is_array($object) && array_key_exists($arrayItem, $object)) {
$hasArrayItem = true;
} elseif ($object instanceof ArrayAccess) {
if (isset($object[$arrayItem])) {
$hasArrayItem = true;
} elseif (isset(self::$cache[$class]['properties'][$arrayItem])
&& isset($object[self::$cache[$class]['properties'][$arrayItem]])
) {
$arrayItem = self::$cache[$class]['properties'][$arrayItem];
$hasArrayItem = true;
}
}
if ((is_array($object) && array_key_exists($arrayItem, $object))
|| ($object instanceof ArrayAccess && isset($object[$arrayItem]))
) {
if ($hasArrayItem) {
if ($isDefinedTest) {
return true;
}
Expand All @@ -833,15 +868,30 @@ PHP_FUNCTION(twig_template_get_attributes)


if (strcmp("method", type) != 0) {
if ((TWIG_ARRAY_KEY_EXISTS(object, zitem))
|| (TWIG_INSTANCE_OF(object, zend_ce_arrayaccess TSRMLS_CC) && TWIG_ISSET_ARRAYOBJECT_ELEMENT(object, zitem TSRMLS_CC))
) {
zval *tmp_properties, *tmp_item = NULL;

if (TWIG_ARRAY_KEY_EXISTS(object, zitem)) {
tmp_item = zitem;
} else if (TWIG_INSTANCE_OF(object, zend_ce_arrayaccess TSRMLS_CC)) {
if (TWIG_ISSET_ARRAYOBJECT_ELEMENT(object, zitem TSRMLS_CC)) {
tmp_item = zitem;
} else {
tmp_properties = TWIG_GET_ARRAY_ELEMENT(tmp_class, "properties", strlen("properties") TSRMLS_CC);
if ((tmp_item = TWIG_GET_ARRAY_ELEMENT(tmp_properties, item, item_len TSRMLS_CC))
&& !TWIG_ISSET_ARRAYOBJECT_ELEMENT(object, tmp_item TSRMLS_CC)
) {
tmp_item = NULL;
}
}
}

if (tmp_item) {

if (isDefinedTest) {
RETURN_TRUE;
}

ret = TWIG_GET_ARRAY_ELEMENT_ZVAL(object, zitem TSRMLS_CC);
ret = TWIG_GET_ARRAY_ELEMENT_ZVAL(object, tmp_item TSRMLS_CC);

if (!ret) {
ret = &EG(uninitialized_zval);
Expand Down Expand Up @@ -932,24 +982,21 @@ PHP_FUNCTION(twig_template_get_attributes)

return;
}
/*
$class = get_class($object);
*/

class_name = TWIG_GET_CLASS_NAME(object TSRMLS_CC);
tmp_self_cache = TWIG_GET_STATIC_PROPERTY(template, "cache" TSRMLS_CC);
tmp_class = TWIG_GET_ARRAY_ELEMENT(tmp_self_cache, class_name, strlen(class_name) TSRMLS_CC);

if (!tmp_class) {
twig_add_class_to_cache(tmp_self_cache, object, class_name TSRMLS_CC);
tmp_class = TWIG_GET_ARRAY_ELEMENT(tmp_self_cache, class_name, strlen(class_name) TSRMLS_CC);
}
efree(class_name);

/*
// object property
if (Twig_Template::METHOD_CALL !== $type) {
$property = null;
if (isset($object->$item) || array_key_exists((string) $item, $object)) {
$property = $item;
} elseif (isset(self::$cache[$class]['properties'][$item])
&& isset($object->{self::$cache[$class]['properties'][$item]})
) {
$property = self::$cache[$class]['properties'][$item];
}
if (null !== $property) {
if ($isDefinedTest) {
return true;
}
Expand All @@ -963,23 +1010,30 @@ PHP_FUNCTION(twig_template_get_attributes)
}
*/
if (strcmp("method", type) != 0) {
zval *tmp_properties, *tmp_item;
zval *tmp_properties, *tmp_item = NULL;

tmp_properties = TWIG_GET_ARRAY_ELEMENT(tmp_class, "properties", strlen("properties") TSRMLS_CC);
tmp_item = TWIG_GET_ARRAY_ELEMENT(tmp_properties, item, item_len TSRMLS_CC);

if (tmp_item || TWIG_HAS_PROPERTY(object, zitem TSRMLS_CC) || TWIG_HAS_DYNAMIC_PROPERTY(object, item, item_len TSRMLS_CC)) {
if (TWIG_HAS_PROPERTY(object, zitem TSRMLS_CC) || TWIG_HAS_DYNAMIC_PROPERTY(object, item, item_len TSRMLS_CC)) {
tmp_item = zitem;
} else if (tmp_item = TWIG_GET_ARRAY_ELEMENT(tmp_properties, item, item_len TSRMLS_CC)) {
if (!TWIG_HAS_PROPERTY(object, tmp_item TSRMLS_CC) && !TWIG_HAS_DYNAMIC_PROPERTY(object, Z_STRVAL_P(tmp_item), strlen(tmp_item) TSRMLS_CC)) {
tmp_item = NULL;
}
}

if (tmp_item) {
if (isDefinedTest) {
RETURN_TRUE;
}
if (TWIG_CALL_SB(TWIG_PROPERTY_CHAR(template, "env" TSRMLS_CC), "hasExtension", "sandbox" TSRMLS_CC)) {
TWIG_CALL_ZZ(TWIG_CALL_S(TWIG_PROPERTY_CHAR(template, "env" TSRMLS_CC), "getExtension", "sandbox" TSRMLS_CC), "checkPropertyAllowed", object, zitem TSRMLS_CC);
TWIG_CALL_ZZ(TWIG_CALL_S(TWIG_PROPERTY_CHAR(template, "env" TSRMLS_CC), "getExtension", "sandbox" TSRMLS_CC), "checkPropertyAllowed", object, tmp_item TSRMLS_CC);
}
if (EG(exception)) {
return;
}

ret = TWIG_PROPERTY(object, zitem TSRMLS_CC);
ret = TWIG_PROPERTY(object, tmp_item TSRMLS_CC);
RETURN_ZVAL(ret, 1, 0);
}
}
Expand Down
72 changes: 55 additions & 17 deletions lib/Twig/Template.php
Original file line number Diff line number Diff line change
Expand Up @@ -348,13 +348,32 @@ final protected function getContext($context, $item, $ignoreStrictCheck = false)
*/
protected function getAttribute($object, $item, array $arguments = array(), $type = Twig_Template::ANY_CALL, $isDefinedTest = false, $ignoreStrictCheck = false)
{
if (is_object($object)) {
$class = get_class($object);
if (!isset(self::$cache[$class])) {
self::$cache[$class] = $this->getCacheForClass($class);
}
}

// array
if (Twig_Template::METHOD_CALL !== $type) {
$arrayItem = is_bool($item) || is_float($item) ? (int) $item : $item;
$hasArrayItem = false;

if (is_array($object) && array_key_exists($arrayItem, $object)) {
$hasArrayItem = true;
} elseif ($object instanceof ArrayAccess) {
if (isset($object[$arrayItem])) {
$hasArrayItem = true;
} elseif (isset(self::$cache[$class]['properties'][$arrayItem])
&& isset($object[self::$cache[$class]['properties'][$arrayItem]])
) {
$arrayItem = self::$cache[$class]['properties'][$arrayItem];
$hasArrayItem = true;
}
}

if ((is_array($object) && array_key_exists($arrayItem, $object))
|| ($object instanceof ArrayAccess && isset($object[$arrayItem]))
) {
if ($hasArrayItem) {
if ($isDefinedTest) {
return true;
}
Expand Down Expand Up @@ -395,28 +414,32 @@ protected function getAttribute($object, $item, array $arguments = array(), $typ
throw new Twig_Error_Runtime(sprintf('Impossible to invoke a method ("%s") on a %s variable ("%s")', $item, gettype($object), $object), -1, $this->getTemplateName());
}

$class = get_class($object);

// object property
if (Twig_Template::METHOD_CALL !== $type) {
$property = null;

if (isset($object->$item) || array_key_exists((string) $item, $object)) {
$property = $item;
} elseif (isset(self::$cache[$class]['properties'][$item])
&& isset($object->{self::$cache[$class]['properties'][$item]})
) {
$property = self::$cache[$class]['properties'][$item];
}

if (null !== $property) {
if ($isDefinedTest) {
return true;
}

if ($this->env->hasExtension('sandbox')) {
$this->env->getExtension('sandbox')->checkPropertyAllowed($object, $item);
$this->env->getExtension('sandbox')->checkPropertyAllowed($object, $property);
}

return $object->$item;
return $object->$property;
}
}

// object method
if (!isset(self::$cache[$class])) {
self::$cache[$class] = $this->getCacheForClass($class);
}

$call = false;
if (isset(self::$cache[$class]['methods'][$item])) {
$method = self::$cache[$class]['methods'][$item];
Expand Down Expand Up @@ -476,17 +499,32 @@ protected function getAttribute($object, $item, array $arguments = array(), $typ
*/
protected function getCacheForClass($class)
{
$cache = array('methods' => array(), 'properties' => array());

$methods = get_class_methods($class);
if (!empty($methods)) {
$cache['methods'] = array_combine($methods, $methods);
$keys = array_merge(preg_replace('/^(?:get|is)_?(.++)$/i', '\\1', $methods), $methods);
$keys = array_merge(preg_replace('/((?<=[a-z]|\d)[A-Z]|(?<!^)[A-Z](?=[a-z]))/', '_\\1', $keys), $keys);
$cache['methods'] += array_change_key_case(array_combine($keys, array_merge($methods, $methods, $methods, $methods)));
}

if (empty($methods)) {
return array('methods' => array());
$properties = array_keys(get_class_vars($class));
$implements = class_implements($class, false);
if (isset($implements['ArrayAccess']) || (isset($cache['methods']['__isset']) && isset($cache['methods']['__get']))) {
$reflection = new ReflectionClass($class);
foreach ($reflection->getProperties(ReflectionProperty::IS_PROTECTED) as $property) {
$properties[] = $property->getName();
}
}

$cache = array_combine($methods, $methods);
$keys = array_merge(preg_replace('/^(?:get|is)_?(.++)$/i', '\\1', $methods), $methods);
$keys = array_merge(preg_replace('/((?<=[a-z]|\d)[A-Z]|(?<!^)[A-Z](?=[a-z]))/', '_\\1', $keys), $keys);
if (!empty($properties)) {
$properties = array_combine($properties, $properties);
$cache['properties'] = array_flip(preg_replace('/((?<=[a-z]|\d)[A-Z]|(?<!^)[A-Z](?=[a-z]))/', '_\\1', $properties));
$cache['properties'] = array_change_key_case(array_diff_key($cache['properties'], $properties));
}

return array('methods' => $cache + array_change_key_case(array_combine($keys, array_merge($methods, $methods, $methods, $methods))));
return $cache;
}

/**
Expand Down
58 changes: 57 additions & 1 deletion test/Twig/Tests/TemplateTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,7 @@ public function testGetAttributeCallExceptions($useExt = false)

$object = new Twig_TemplateMagicMethodExceptionObject();

$this->assertEquals(null, $template->getAttribute($object, 'foo'));
$this->assertNull($template->getAttribute($object, 'foo'));
}

public function getGetAttributeTests()
Expand Down Expand Up @@ -367,6 +367,22 @@ public function getGetAttributeTests()
array(true, 'httpresponsecode', $methodAndPropObject, 'get_http_response_code', array(), $methodType),

array(true, 'http2_response', $methodAndPropObject, 'http2_response', array(), $anyType),

array(true, 'responseCode', $methodAndPropObject, 'responseCode', array(), $anyType),
array(true, 'responseCode', $methodAndPropObject, 'response_code', array(), $anyType),
));

$magicPropertyObject = new Twig_TemplateMagicPropertyPropertyObject;
$arrayAccessPropertyObject = new Twig_TemplatePropertyArrayAccess;

// additional property tests
$tests = array_merge($tests, array(
array(true, 'camelCase', $magicPropertyObject, 'camelCase', array(), $anyType),
array(true, 'camelCase', $magicPropertyObject, 'camel_case', array(), $anyType),
array(true, 'camelCase', $arrayAccessPropertyObject, 'camelCase', array(), $anyType),
array(true, 'camelCase', $arrayAccessPropertyObject, 'camelCase', array(), $arrayType),
array(true, 'camelCase', $arrayAccessPropertyObject, 'camel_case', array(), $anyType),
array(true, 'camelCase', $arrayAccessPropertyObject, 'camel_case', array(), $arrayType),
));

// tests when input is not an array or object
Expand Down Expand Up @@ -507,6 +523,21 @@ public function __get($name)
}
}

class Twig_TemplateMagicPropertyPropertyObject
{
protected $camelCase = 'camelCase';

public function __isset($name)
{
return isset($this->$name);
}

public function __get($name)
{
return $this->$name;
}
}

class Twig_TemplateMagicPropertyObjectWithException
{
public function __isset($key)
Expand Down Expand Up @@ -556,6 +587,29 @@ public function offsetUnset($offset)
}
}

class Twig_TemplatePropertyArrayAccess implements ArrayAccess
{
protected $camelCase = 'camelCase';

public function offsetExists($offset)
{
return isset($this->$offset);
}

public function offsetGet($offset)
{
return $this->$offset;
}

public function offsetSet($offset, $value)
{
}

public function offsetUnset($offset)
{
}
}

class Twig_TemplateMethodObject
{
public function getDefined()
Expand Down Expand Up @@ -634,6 +688,8 @@ public function get_http2_response()
{
return 'http2_response';
}

public $responseCode = 'responseCode';
}

class Twig_TemplateMagicMethodObject
Expand Down

0 comments on commit db8557a

Please sign in to comment.