merged branch Tobion/patch-1 (PR #1030)

This PR was merged into the master branch.

Discussion
----------

fix numeric & boolean & float keys in array

Reopened PR of #878

Commits
-------

2c56b70 Merge pull request #3 from tucksaun/patch-1
17d6cc7 [ext] fixed tabs
46012ce [ext] improvements
21f8062 [ext] code cleaning
9347c52 [ext] updated PHP code documenting C code
b41ce60 updated C extension accordingly
e5291e1 rename item to attribute in the exception message because that's the term that is used in twig, e.g. the  function
a93f0dc further improved exception message and distinguish array_call and any_call access
26bac14 added tests for array access with confusable keys
db744d5 fix test that that would not fail if no exception is thrown
9ea16ec refactored getAttribute
6fb5afa improved error message for non-existent or invalid attributes
fdfd506 added changelog entry about fixed boolean array access
da616c5 fixed getAttribute for array access with a boolean or float key
b6acf9d updated CHANGELOG
c4d5b3f added some unit tests for previous merge
e84448b fix numeric keys in array
This commit is contained in:
Fabien Potencier
2013-05-07 20:56:07 +02:00
4 changed files with 208 additions and 122 deletions
+2
View File
@@ -11,6 +11,8 @@
* fixed fatal error that should be an exception when adding a filter/function/test too late * fixed fatal error that should be an exception when adding a filter/function/test too late
* added a batch filter * added a batch filter
* added support for encoding an array as query string in the url_encode filter * added support for encoding an array as query string in the url_encode filter
* fixed getting a numeric-like item on a variable ('09' for instance)
* fixed getting a boolean or float key on an array, so it is consistent with PHP's array access: `{{ array[false] }}` behaves the same as `echo $array[false];` (equals `$array[0]`)
* 1.12.2 (2013-02-09) * 1.12.2 (2013-02-09)
+124 -102
View File
@@ -76,12 +76,29 @@ zend_module_entry twig_module_entry = {
ZEND_GET_MODULE(twig) ZEND_GET_MODULE(twig)
#endif #endif
int TWIG_ARRAY_KEY_EXISTS(zval *array, char* key, int key_len) int TWIG_ARRAY_KEY_EXISTS(zval *array, zval *key)
{ {
zval temp;
int result;
if (Z_TYPE_P(array) != IS_ARRAY) { if (Z_TYPE_P(array) != IS_ARRAY) {
return 0; return 0;
} }
return zend_symtable_exists(Z_ARRVAL_P(array), key, key_len + 1);
switch (Z_TYPE_P(key)) {
case IS_NULL:
return zend_hash_exists(Z_ARRVAL_P(array), "", 1);
case IS_BOOL:
case IS_DOUBLE:
convert_to_long(key);
case IS_LONG:
return zend_hash_index_exists(Z_ARRVAL_P(array), Z_LVAL_P(key));
default:
convert_to_string(key);
return zend_symtable_exists(Z_ARRVAL_P(array), Z_STRVAL_P(key), Z_STRLEN_P(key) + 1);
}
} }
int TWIG_INSTANCE_OF(zval *object, zend_class_entry *interface TSRMLS_DC) int TWIG_INSTANCE_OF(zval *object, zend_class_entry *interface TSRMLS_DC)
@@ -106,23 +123,23 @@ int TWIG_INSTANCE_OF_USERLAND(zval *object, char *interface TSRMLS_DC)
zval *TWIG_GET_ARRAYOBJECT_ELEMENT(zval *object, zval *offset TSRMLS_DC) zval *TWIG_GET_ARRAYOBJECT_ELEMENT(zval *object, zval *offset TSRMLS_DC)
{ {
zend_class_entry *ce = Z_OBJCE_P(object); zend_class_entry *ce = Z_OBJCE_P(object);
zval *retval; zval *retval;
if (Z_TYPE_P(object) == IS_OBJECT) { if (Z_TYPE_P(object) == IS_OBJECT) {
SEPARATE_ARG_IF_REF(offset); SEPARATE_ARG_IF_REF(offset);
zend_call_method_with_1_params(&object, ce, NULL, "offsetget", &retval, offset); zend_call_method_with_1_params(&object, ce, NULL, "offsetget", &retval, offset);
zval_ptr_dtor(&offset); zval_ptr_dtor(&offset);
if (!retval) { if (!retval) {
if (!EG(exception)) { if (!EG(exception)) {
zend_error(E_ERROR, "Undefined offset for object of type %s used as array", ce->name); zend_error(E_ERROR, "Undefined offset for object of type %s used as array", ce->name);
} }
return NULL; return NULL;
} }
return retval; return retval;
} }
return NULL; return NULL;
} }
@@ -245,7 +262,7 @@ zval *TWIG_GET_ARRAY_ELEMENT_ZVAL(zval *class, zval *prop_name TSRMLS_DC)
zval **tmp_zval; zval **tmp_zval;
char *tmp_name; char *tmp_name;
if (class == NULL || Z_TYPE_P(class) != IS_ARRAY || Z_TYPE_P(prop_name) != IS_STRING) { if (class == NULL || Z_TYPE_P(class) != IS_ARRAY) {
if (class != NULL && Z_TYPE_P(class) == IS_OBJECT && TWIG_INSTANCE_OF(class, zend_ce_arrayaccess TSRMLS_CC)) { if (class != NULL && Z_TYPE_P(class) == IS_OBJECT && TWIG_INSTANCE_OF(class, zend_ce_arrayaccess TSRMLS_CC)) {
// array access object // array access object
return TWIG_GET_ARRAYOBJECT_ELEMENT(class, prop_name TSRMLS_CC); return TWIG_GET_ARRAYOBJECT_ELEMENT(class, prop_name TSRMLS_CC);
@@ -253,11 +270,23 @@ zval *TWIG_GET_ARRAY_ELEMENT_ZVAL(zval *class, zval *prop_name TSRMLS_DC)
return NULL; return NULL;
} }
convert_to_string(prop_name); switch(Z_TYPE_P(prop_name)) {
tmp_name = Z_STRVAL_P(prop_name); case IS_NULL:
if (zend_symtable_find(HASH_OF(class), tmp_name, strlen(tmp_name)+1, (void**) &tmp_zval) == SUCCESS) { zend_hash_find(HASH_OF(class), "", 1, (void**) &tmp_zval);
return *tmp_zval; return *tmp_zval;
case IS_BOOL:
case IS_DOUBLE:
convert_to_long(prop_name);
case IS_LONG:
zend_hash_index_find(HASH_OF(class), Z_LVAL_P(prop_name), (void **) &tmp_zval);
return *tmp_zval;
case IS_STRING:
zend_symtable_find(HASH_OF(class), Z_STRVAL_P(prop_name), Z_STRLEN_P(prop_name) + 1, (void**) &tmp_zval);
return *tmp_zval;
} }
return NULL; return NULL;
} }
@@ -717,7 +746,7 @@ PHP_FUNCTION(twig_template_get_attributes)
zval *object; zval *object;
char *item; char *item;
int item_len; int item_len;
zval zitem; zval *zitem, ztmpitem;
zval *arguments = NULL; zval *arguments = NULL;
zval *ret = NULL; zval *ret = NULL;
char *type = NULL; char *type = NULL;
@@ -728,22 +757,17 @@ PHP_FUNCTION(twig_template_get_attributes)
zval *tmp_self_cache; zval *tmp_self_cache;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ozs|asbb", &template, &object, &item, &item_len, &arguments, &type, &type_len, &isDefinedTest, &ignoreStrictCheck) == FAILURE) { if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ozz|asbb", &template, &object, &zitem, &arguments, &type, &type_len, &isDefinedTest, &ignoreStrictCheck) == FAILURE) {
return; return;
} }
INIT_PZVAL(&zitem); // convert the item to a string
ZVAL_STRINGL(&zitem, item, item_len, 0); ztmpitem = *zitem;
zval_copy_ctor(&ztmpitem);
switch (is_numeric_string(item, item_len, &Z_LVAL(zitem), &Z_DVAL(zitem), 0)) { convert_to_string(&ztmpitem);
case IS_LONG: item_len = Z_STRLEN(ztmpitem);
Z_TYPE(zitem) = IS_LONG; item = estrndup(Z_STRVAL(ztmpitem), item_len);
break; zval_dtor(&ztmpitem);
case IS_DOUBLE:
Z_TYPE(zitem) = IS_DOUBLE;
convert_to_long(&zitem);
break;
}
if (!type) { if (!type) {
type = "any"; type = "any";
@@ -752,28 +776,31 @@ PHP_FUNCTION(twig_template_get_attributes)
/* /*
// array // array
if (Twig_TemplateInterface::METHOD_CALL !== $type) { if (Twig_TemplateInterface::METHOD_CALL !== $type) {
if ((is_array($object) && array_key_exists($item, $object)) $arrayItem = is_bool($item) || is_float($item) ? (int) $item : $item;
|| ($object instanceof ArrayAccess && isset($object[$item]))
if ((is_array($object) && array_key_exists($arrayItem, $object))
|| ($object instanceof ArrayAccess && isset($object[$arrayItem]))
) { ) {
if ($isDefinedTest) { if ($isDefinedTest) {
return true; return true;
} }
return $object[$item]; return $object[$arrayItem];
} }
*/ */
if (strcmp("method", type) != 0) { if (strcmp("method", type) != 0) {
// printf("XXXmethod: %s\n", type); if ((TWIG_ARRAY_KEY_EXISTS(object, zitem))
if ((TWIG_ARRAY_KEY_EXISTS(object, item, item_len)) || (TWIG_INSTANCE_OF(object, zend_ce_arrayaccess TSRMLS_CC) && TWIG_ISSET_ARRAYOBJECT_ELEMENT(object, zitem TSRMLS_CC))
|| (TWIG_INSTANCE_OF(object, zend_ce_arrayaccess TSRMLS_CC) && TWIG_ISSET_ARRAYOBJECT_ELEMENT(object, &zitem TSRMLS_CC))
) { ) {
zval *ret;
if (isDefinedTest) { if (isDefinedTest) {
RETURN_TRUE; RETURN_TRUE;
} }
ret = TWIG_GET_ARRAY_ELEMENT(object, item, item_len TSRMLS_CC); ret = TWIG_GET_ARRAY_ELEMENT_ZVAL(object, zitem TSRMLS_CC);
if (!ret) { if (!ret) {
ret = &EG(uninitialized_zval); ret = &EG(uninitialized_zval);
} }
@@ -792,7 +819,7 @@ PHP_FUNCTION(twig_template_get_attributes)
return null; return null;
} }
*/ */
if (strcmp("array", type) == 0) { if (strcmp("array", type) == 0 || Z_TYPE_P(object) != IS_OBJECT) {
if (isDefinedTest) { if (isDefinedTest) {
RETURN_FALSE; RETURN_FALSE;
} }
@@ -801,11 +828,13 @@ PHP_FUNCTION(twig_template_get_attributes)
} }
/* /*
if (is_object($object)) { if (is_object($object)) {
throw new Twig_Error_Runtime(sprintf('Key "%s" in object (with ArrayAccess) of type "%s" does not exist', $item, get_class($object)), -1, $this->getTemplateName()); throw new Twig_Error_Runtime(sprintf('Key "%s" in object (with ArrayAccess) of type "%s" does not exist', $arrayItem, get_class($object)), -1, $this->getTemplateName());
} elseif (is_array($object)) { } elseif (is_array($object)) {
throw new Twig_Error_Runtime(sprintf('Key "%s" for array with keys "%s" does not exist', $item, implode(', ', array_keys($object))), -1, $this->getTemplateName()); throw new Twig_Error_Runtime(sprintf('Key "%s" for array with keys "%s" does not exist', $arrayItem, implode(', ', array_keys($object))), -1, $this->getTemplateName());
} elseif (Twig_TemplateInterface::ARRAY_CALL === $type) {
throw new Twig_Error_Runtime(sprintf('Impossible to access a key ("%s") on a %s variable ("%s")', $item, gettype($object), $object), -1, $this->getTemplateName());
} else { } else {
throw new Twig_Error_Runtime(sprintf('Impossible to access a key ("%s") on a "%s" variable', $item, gettype($object)), -1, $this->getTemplateName()); throw new Twig_Error_Runtime(sprintf('Impossible to access an attribute ("%s") on a %s variable ("%s")', $item, gettype($object), $object), -1, $this->getTemplateName());
} }
} }
} }
@@ -815,7 +844,15 @@ PHP_FUNCTION(twig_template_get_attributes)
} else if (Z_TYPE_P(object) == IS_ARRAY) { } else if (Z_TYPE_P(object) == IS_ARRAY) {
TWIG_RUNTIME_ERROR(template TSRMLS_CC, "Key \"%s\" for array with keys \"%s\" does not exist", item, TWIG_IMPLODE_ARRAY_KEYS(", ", object TSRMLS_CC)); TWIG_RUNTIME_ERROR(template TSRMLS_CC, "Key \"%s\" for array with keys \"%s\" does not exist", item, TWIG_IMPLODE_ARRAY_KEYS(", ", object TSRMLS_CC));
} else { } else {
TWIG_RUNTIME_ERROR(template TSRMLS_CC, "Impossible to access a key (\"%s\") on a \"%s\" variable", item, zend_zval_type_name(object)); char *type_name = zend_zval_type_name(object);
Z_ADDREF_P(object);
convert_to_string(object);
TWIG_RUNTIME_ERROR(template TSRMLS_CC,
(strcmp("array", type) == 0)
? "Impossible to access a key (\"%s\") on a %s variable (\"%s\")"
: "Impossible to access an attribute (\"%s\") on a %s variable (\"%s\")",
item, type_name, Z_STRVAL_P(object));
zval_ptr_dtor(&object);
} }
return; return;
} }
@@ -836,58 +873,47 @@ PHP_FUNCTION(twig_template_get_attributes)
if ($ignoreStrictCheck || !$this->env->isStrictVariables()) { if ($ignoreStrictCheck || !$this->env->isStrictVariables()) {
return null; return null;
} }
throw new Twig_Error_Runtime(sprintf('Item "%s" for "%s" does not exist', $item, implode(', ', array_keys($object)))); throw new Twig_Error_Runtime(sprintf('Impossible to invoke a method ("%s") on a %s variable ("%s")', $item, gettype($object), $object), -1, $this->getTemplateName());
} }
*/ */
if (ignoreStrictCheck || !TWIG_CALL_BOOLEAN(TWIG_PROPERTY_CHAR(template, "env" TSRMLS_CC), "isStrictVariables" TSRMLS_CC)) { if (ignoreStrictCheck || !TWIG_CALL_BOOLEAN(TWIG_PROPERTY_CHAR(template, "env" TSRMLS_CC), "isStrictVariables" TSRMLS_CC)) {
RETURN_FALSE; return;
}
if (Z_TYPE_P(object) == IS_ARRAY) {
TWIG_RUNTIME_ERROR(template TSRMLS_CC, "Item \"%s\" for \"Array\" does not exist", item);
} else {
Z_ADDREF_P(object);
convert_to_string_ex(&object);
TWIG_RUNTIME_ERROR(template TSRMLS_CC, "Item \"%s\" for \"%s\" does not exist", item, Z_STRVAL_P(object));
zval_ptr_dtor(&object);
} }
char *type_name = zend_zval_type_name(object);
Z_ADDREF_P(object);
convert_to_string_ex(&object);
TWIG_RUNTIME_ERROR(template TSRMLS_CC, "Impossible to invoke a method (\"%s\") on a %s variable (\"%s\")", item, type_name, Z_STRVAL_P(object));
zval_ptr_dtor(&object);
return; return;
} }
/* /*
// get some information about the object
$class = get_class($object); $class = get_class($object);
if (!isset(self::$cache[$class])) {
$r = new ReflectionClass($class);
self::$cache[$class] = array('methods' => array(), 'properties' => array());
foreach ($r->getMethods(ReflectionMethod::IS_PUBLIC) as $method) {
self::$cache[$class]['methods'][strtolower($method->getName())] = true;
}
foreach ($r->getProperties(ReflectionProperty::IS_PUBLIC) as $property) {
self::$cache[$class]['properties'][$property->getName()] = true;
}
}
*/ */
if (Z_TYPE_P(object) == IS_OBJECT) { char *class_name = NULL;
char *class_name = NULL; zval *tmp_class;
class_name = TWIG_GET_CLASS_NAME(object TSRMLS_CC); class_name = TWIG_GET_CLASS_NAME(object TSRMLS_CC);
tmp_self_cache = TWIG_GET_STATIC_PROPERTY(template, "cache" 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 (!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); 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);
} }
efree(class_name);
/* /*
// object property // object property
if (Twig_TemplateInterface::METHOD_CALL !== $type) { if (Twig_TemplateInterface::METHOD_CALL !== $type) {
if (isset(self::$cache[$class]['properties'][$item]) if (isset($object->$item) || array_key_exists((string) $item, $object)) {
|| isset($object->$item) || array_key_exists($item, $object)
) {
if ($isDefinedTest) { if ($isDefinedTest) {
return true; return true;
} }
if ($this->env->hasExtension('sandbox')) { if ($this->env->hasExtension('sandbox')) {
$this->env->getExtension('sandbox')->checkPropertyAllowed($object, $item); $this->env->getExtension('sandbox')->checkPropertyAllowed($object, $item);
} }
@@ -897,42 +923,41 @@ PHP_FUNCTION(twig_template_get_attributes)
} }
*/ */
if (strcmp("method", type) != 0) { if (strcmp("method", type) != 0) {
zval *tmp_class, *tmp_properties, *tmp_item; zval *tmp_properties, *tmp_item;
char *class_name = NULL;
class_name = TWIG_GET_CLASS_NAME(object TSRMLS_CC);
tmp_class = TWIG_GET_ARRAY_ELEMENT(tmp_self_cache, class_name, strlen(class_name) TSRMLS_CC);
tmp_properties = TWIG_GET_ARRAY_ELEMENT(tmp_class, "properties", strlen("properties") TSRMLS_CC); 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); tmp_item = TWIG_GET_ARRAY_ELEMENT(tmp_properties, item, item_len TSRMLS_CC);
efree(class_name); if (tmp_item || TWIG_HAS_PROPERTY(object, zitem TSRMLS_CC) || TWIG_HAS_DYNAMIC_PROPERTY(object, 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 (isDefinedTest) { if (isDefinedTest) {
RETURN_TRUE; RETURN_TRUE;
} }
if (TWIG_CALL_SB(TWIG_PROPERTY_CHAR(template, "env" TSRMLS_CC), "hasExtension", "sandbox" TSRMLS_CC)) { 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, zitem TSRMLS_CC);
} }
if (EG(exception)) { if (EG(exception)) {
return; return;
} }
ret = TWIG_PROPERTY(object, &zitem TSRMLS_CC); ret = TWIG_PROPERTY(object, zitem TSRMLS_CC);
RETURN_ZVAL(ret, 1, 0); RETURN_ZVAL(ret, 1, 0);
} }
} }
/* /*
// object method // object method
if (!isset(self::$cache[$class]['methods'])) {
self::$cache[$class]['methods'] = array_change_key_case(array_flip(get_class_methods($object)));
}
$lcItem = strtolower($item); $lcItem = strtolower($item);
if (isset(self::$cache[$class]['methods'][$lcItem])) { if (isset(self::$cache[$class]['methods'][$lcItem])) {
$method = $item; $method = (string) $item;
} elseif (isset(self::$cache[$class]['methods']['get'.$lcItem])) { } elseif (isset(self::$cache[$class]['methods']['get'.$lcItem])) {
$method = 'get'.$item; $method = 'get'.$item;
} elseif (isset(self::$cache[$class]['methods']['is'.$lcItem])) { } elseif (isset(self::$cache[$class]['methods']['is'.$lcItem])) {
$method = 'is'.$item; $method = 'is'.$item;
} elseif (isset(self::$cache[$class]['methods']['__call'])) { } elseif (isset(self::$cache[$class]['methods']['__call'])) {
$method = $item; $method = (string) $item;
*/ */
{ {
char *lcItem = TWIG_STRTOLOWER(item, item_len); char *lcItem = TWIG_STRTOLOWER(item, item_len);
@@ -940,10 +965,8 @@ PHP_FUNCTION(twig_template_get_attributes)
char *method = NULL; char *method = NULL;
char *tmp_method_name_get; char *tmp_method_name_get;
char *tmp_method_name_is; char *tmp_method_name_is;
zval *tmp_class, *tmp_methods; zval *tmp_methods;
char *class_name = NULL;
class_name = TWIG_GET_CLASS_NAME(object TSRMLS_CC);
lcItem_length = strlen(lcItem); lcItem_length = strlen(lcItem);
tmp_method_name_get = emalloc(4 + lcItem_length); tmp_method_name_get = emalloc(4 + lcItem_length);
tmp_method_name_is = emalloc(3 + lcItem_length); tmp_method_name_is = emalloc(3 + lcItem_length);
@@ -951,9 +974,7 @@ PHP_FUNCTION(twig_template_get_attributes)
sprintf(tmp_method_name_get, "get%s", lcItem); sprintf(tmp_method_name_get, "get%s", lcItem);
sprintf(tmp_method_name_is, "is%s", lcItem); sprintf(tmp_method_name_is, "is%s", lcItem);
tmp_class = TWIG_GET_ARRAY_ELEMENT(tmp_self_cache, class_name, strlen(class_name) TSRMLS_CC);
tmp_methods = TWIG_GET_ARRAY_ELEMENT(tmp_class, "methods", strlen("methods") TSRMLS_CC); tmp_methods = TWIG_GET_ARRAY_ELEMENT(tmp_class, "methods", strlen("methods") TSRMLS_CC);
efree(class_name);
if (TWIG_GET_ARRAY_ELEMENT(tmp_methods, lcItem, lcItem_length TSRMLS_CC)) { if (TWIG_GET_ARRAY_ELEMENT(tmp_methods, lcItem, lcItem_length TSRMLS_CC)) {
method = item; method = item;
@@ -968,11 +989,14 @@ PHP_FUNCTION(twig_template_get_attributes)
if ($isDefinedTest) { if ($isDefinedTest) {
return false; return false;
} }
if ($ignoreStrictCheck || !$this->env->isStrictVariables()) { if ($ignoreStrictCheck || !$this->env->isStrictVariables()) {
return null; return null;
} }
throw new Twig_Error_Runtime(sprintf('Method "%s" for object "%s" does not exist', $item, get_class($object)));
throw new Twig_Error_Runtime(sprintf('Method "%s" for object "%s" does not exist', $item, get_class($object)), -1, $this->getTemplateName());
} }
if ($isDefinedTest) { if ($isDefinedTest) {
return true; return true;
} }
@@ -1004,7 +1028,7 @@ PHP_FUNCTION(twig_template_get_attributes)
} }
*/ */
if (TWIG_CALL_SB(TWIG_PROPERTY_CHAR(template, "env" TSRMLS_CC), "hasExtension", "sandbox" TSRMLS_CC)) { 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), "checkMethodAllowed", object, &zitem TSRMLS_CC); TWIG_CALL_ZZ(TWIG_CALL_S(TWIG_PROPERTY_CHAR(template, "env" TSRMLS_CC), "getExtension", "sandbox" TSRMLS_CC), "checkMethodAllowed", object, zitem TSRMLS_CC);
} }
if (EG(exception)) { if (EG(exception)) {
efree(tmp_method_name_get); efree(tmp_method_name_get);
@@ -1015,15 +1039,15 @@ PHP_FUNCTION(twig_template_get_attributes)
/* /*
$ret = call_user_func_array(array($object, $method), $arguments); $ret = call_user_func_array(array($object, $method), $arguments);
*/ */
if (Z_TYPE_P(object) == IS_OBJECT) { ret = TWIG_CALL_USER_FUNC_ARRAY(object, method, arguments TSRMLS_CC);
ret = TWIG_CALL_USER_FUNC_ARRAY(object, method, arguments TSRMLS_CC); free_ret = 1;
free_ret = 1;
}
efree(tmp_method_name_get); efree(tmp_method_name_get);
efree(tmp_method_name_is); efree(tmp_method_name_is);
efree(lcItem); efree(lcItem);
} }
/* /*
// useful when calling a template method from a template
// this is not supported but unfortunately heavily used in the Symfony profiler
if ($object instanceof Twig_TemplateInterface) { if ($object instanceof Twig_TemplateInterface) {
return $ret === '' ? '' : new Twig_Markup($ret, $this->env->getCharset()); return $ret === '' ? '' : new Twig_Markup($ret, $this->env->getCharset());
} }
@@ -1033,9 +1057,7 @@ PHP_FUNCTION(twig_template_get_attributes)
// ret can be null, if e.g. the called method throws an exception // ret can be null, if e.g. the called method throws an exception
if (ret) { if (ret) {
if (TWIG_INSTANCE_OF_USERLAND(object, "Twig_TemplateInterface" TSRMLS_CC)) { if (TWIG_INSTANCE_OF_USERLAND(object, "Twig_TemplateInterface" TSRMLS_CC)) {
if (Z_STRLEN_P(ret) == 0) { if (Z_STRLEN_P(ret) != 0) {
free_ret = 1;
} else {
zval *charset = TWIG_CALL_USER_FUNC_ARRAY(TWIG_PROPERTY_CHAR(template, "env" TSRMLS_CC), "getCharset", NULL TSRMLS_CC); zval *charset = TWIG_CALL_USER_FUNC_ARRAY(TWIG_PROPERTY_CHAR(template, "env" TSRMLS_CC), "getCharset", NULL TSRMLS_CC);
TWIG_NEW(return_value, "Twig_Markup", ret, charset TSRMLS_CC); TWIG_NEW(return_value, "Twig_Markup", ret, charset TSRMLS_CC);
zval_ptr_dtor(&charset); zval_ptr_dtor(&charset);
+15 -13
View File
@@ -336,21 +336,21 @@ abstract class Twig_Template implements Twig_TemplateInterface
*/ */
protected function getAttribute($object, $item, array $arguments = array(), $type = Twig_TemplateInterface::ANY_CALL, $isDefinedTest = false, $ignoreStrictCheck = false) protected function getAttribute($object, $item, array $arguments = array(), $type = Twig_TemplateInterface::ANY_CALL, $isDefinedTest = false, $ignoreStrictCheck = false)
{ {
$item = ctype_digit((string) $item) ? (int) $item : (string) $item;
// array // array
if (Twig_TemplateInterface::METHOD_CALL !== $type) { if (Twig_TemplateInterface::METHOD_CALL !== $type) {
if ((is_array($object) && array_key_exists($item, $object)) $arrayItem = is_bool($item) || is_float($item) ? (int) $item : $item;
|| ($object instanceof ArrayAccess && isset($object[$item]))
if ((is_array($object) && array_key_exists($arrayItem, $object))
|| ($object instanceof ArrayAccess && isset($object[$arrayItem]))
) { ) {
if ($isDefinedTest) { if ($isDefinedTest) {
return true; return true;
} }
return $object[$item]; return $object[$arrayItem];
} }
if (Twig_TemplateInterface::ARRAY_CALL === $type) { if (Twig_TemplateInterface::ARRAY_CALL === $type || !is_object($object)) {
if ($isDefinedTest) { if ($isDefinedTest) {
return false; return false;
} }
@@ -360,11 +360,13 @@ abstract class Twig_Template implements Twig_TemplateInterface
} }
if (is_object($object)) { if (is_object($object)) {
throw new Twig_Error_Runtime(sprintf('Key "%s" in object (with ArrayAccess) of type "%s" does not exist', $item, get_class($object)), -1, $this->getTemplateName()); throw new Twig_Error_Runtime(sprintf('Key "%s" in object (with ArrayAccess) of type "%s" does not exist', $arrayItem, get_class($object)), -1, $this->getTemplateName());
} elseif (is_array($object)) { } elseif (is_array($object)) {
throw new Twig_Error_Runtime(sprintf('Key "%s" for array with keys "%s" does not exist', $item, implode(', ', array_keys($object))), -1, $this->getTemplateName()); throw new Twig_Error_Runtime(sprintf('Key "%s" for array with keys "%s" does not exist', $arrayItem, implode(', ', array_keys($object))), -1, $this->getTemplateName());
} elseif (Twig_TemplateInterface::ARRAY_CALL === $type) {
throw new Twig_Error_Runtime(sprintf('Impossible to access a key ("%s") on a %s variable ("%s")', $item, gettype($object), $object), -1, $this->getTemplateName());
} else { } else {
throw new Twig_Error_Runtime(sprintf('Impossible to access a key ("%s") on a "%s" variable', $item, gettype($object)), -1, $this->getTemplateName()); throw new Twig_Error_Runtime(sprintf('Impossible to access an attribute ("%s") on a %s variable ("%s")', $item, gettype($object), $object), -1, $this->getTemplateName());
} }
} }
} }
@@ -378,14 +380,14 @@ abstract class Twig_Template implements Twig_TemplateInterface
return null; return null;
} }
throw new Twig_Error_Runtime(sprintf('Item "%s" for "%s" does not exist', $item, is_array($object) ? 'Array' : $object), -1, $this->getTemplateName()); 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); $class = get_class($object);
// object property // object property
if (Twig_TemplateInterface::METHOD_CALL !== $type) { if (Twig_TemplateInterface::METHOD_CALL !== $type) {
if (isset($object->$item) || array_key_exists($item, $object)) { if (isset($object->$item) || array_key_exists((string) $item, $object)) {
if ($isDefinedTest) { if ($isDefinedTest) {
return true; return true;
} }
@@ -405,13 +407,13 @@ abstract class Twig_Template implements Twig_TemplateInterface
$lcItem = strtolower($item); $lcItem = strtolower($item);
if (isset(self::$cache[$class]['methods'][$lcItem])) { if (isset(self::$cache[$class]['methods'][$lcItem])) {
$method = $item; $method = (string) $item;
} elseif (isset(self::$cache[$class]['methods']['get'.$lcItem])) { } elseif (isset(self::$cache[$class]['methods']['get'.$lcItem])) {
$method = 'get'.$item; $method = 'get'.$item;
} elseif (isset(self::$cache[$class]['methods']['is'.$lcItem])) { } elseif (isset(self::$cache[$class]['methods']['is'.$lcItem])) {
$method = 'is'.$item; $method = 'is'.$item;
} elseif (isset(self::$cache[$class]['methods']['__call'])) { } elseif (isset(self::$cache[$class]['methods']['__call'])) {
$method = $item; $method = (string) $item;
} else { } else {
if ($isDefinedTest) { if ($isDefinedTest) {
return false; return false;
+67 -7
View File
@@ -34,19 +34,22 @@ class Twig_Tests_TemplateTest extends PHPUnit_Framework_TestCase
try { try {
$template->render($context); $template->render($context);
$this->fail('Accessing an invalid attribute should throw an exception.');
} catch (Twig_Error_Runtime $e) { } catch (Twig_Error_Runtime $e) {
$this->assertEquals(sprintf($message, $name), $e->getMessage()); $this->assertSame(sprintf($message, $name), $e->getMessage());
} }
} }
public function getAttributeExceptions() public function getAttributeExceptions()
{ {
$tests = array( $tests = array(
array('{{ string["a"] }}', 'Impossible to access a key ("a") on a "string" variable in "%s" at line 1', false), array('{{ string["a"] }}', 'Impossible to access a key ("a") on a string variable ("foo") in "%s" at line 1', false),
array('{{ array["a"] }}', 'Key "a" for array with keys "foo" does not exist in "%s" at line 1', false), array('{{ array["a"] }}', 'Key "a" for array with keys "foo" does not exist in "%s" at line 1', false),
array('{{ array_access["a"] }}', 'Key "a" in object (with ArrayAccess) of type "Twig_TemplateArrayAccessObject" does not exist in "%s" at line 1', false), array('{{ array_access["a"] }}', 'Key "a" in object (with ArrayAccess) of type "Twig_TemplateArrayAccessObject" does not exist in "%s" at line 1', false),
array('{{ string.a }}', 'Item "a" for "foo" does not exist in "%s" at line 1', false), array('{{ string.a }}', 'Impossible to access an attribute ("a") on a string variable ("foo") in "%s" at line 1', false),
array('{{ array.a }}', 'Item "a" for "Array" does not exist in "%s" at line 1', false), array('{{ string.a() }}', 'Impossible to invoke a method ("a") on a string variable ("foo") in "%s" at line 1', false),
array('{{ array.a }}', 'Key "a" for array with keys "foo" does not exist in "%s" at line 1', false),
array('{{ attribute(array, -10) }}', 'Key "-10" for array with keys "foo" does not exist in "%s" at line 1', false),
array('{{ array_access.a }}', 'Method "a" for object "Twig_TemplateArrayAccessObject" does not exist in "%s" at line 1', false), array('{{ array_access.a }}', 'Method "a" for object "Twig_TemplateArrayAccessObject" does not exist in "%s" at line 1', false),
array('{% macro foo(obj) %}{{ obj.missing_method() }}{% endmacro %}{{ _self.foo(array_access) }}', 'Method "missing_method" for object "Twig_TemplateArrayAccessObject" does not exist in "%s" at line 1', false), array('{% macro foo(obj) %}{{ obj.missing_method() }}{% endmacro %}{{ _self.foo(array_access) }}', 'Method "missing_method" for object "Twig_TemplateArrayAccessObject" does not exist in "%s" at line 1', false),
); );
@@ -139,6 +142,46 @@ class Twig_Tests_TemplateTest extends PHPUnit_Framework_TestCase
return $bools; return $bools;
} }
/**
* @dataProvider getTestsDependingOnExtensionAvailability
*/
public function testGetAttributeOnArrayWithConfusableKey($useExt = false)
{
$template = new Twig_TemplateTest(
new Twig_Environment(),
$useExt
);
$array = array('Zero', 'One', -1 => 'MinusOne', '' => 'EmptyString', '1.5' => 'FloatButString', '01' => 'IntegerButStringWithLeadingZeros');
$this->assertSame('Zero', $array[false]);
$this->assertSame('One', $array[true]);
$this->assertSame('One', $array[1.5]);
$this->assertSame('One', $array['1']);
$this->assertSame('MinusOne', $array[-1.5]);
$this->assertSame('FloatButString', $array['1.5']);
$this->assertSame('IntegerButStringWithLeadingZeros', $array['01']);
$this->assertSame('EmptyString', $array[null]);
$this->assertSame('Zero', $template->getAttribute($array, false), 'false is treated as 0 when accessing an array (equals PHP behavior)');
$this->assertSame('One', $template->getAttribute($array, true), 'true is treated as 1 when accessing an array (equals PHP behavior)');
$this->assertSame('One', $template->getAttribute($array, 1.5), 'float is casted to int when accessing an array (equals PHP behavior)');
$this->assertSame('One', $template->getAttribute($array, '1'), '"1" is treated as integer 1 when accessing an array (equals PHP behavior)');
$this->assertSame('MinusOne', $template->getAttribute($array, -1.5), 'negative float is casted to int when accessing an array (equals PHP behavior)');
$this->assertSame('FloatButString', $template->getAttribute($array, '1.5'), '"1.5" is treated as-is when accessing an array (equals PHP behavior)');
$this->assertSame('IntegerButStringWithLeadingZeros', $template->getAttribute($array, '01'), '"01" is treated as-is when accessing an array (equals PHP behavior)');
$this->assertSame('EmptyString', $template->getAttribute($array, null), 'null is treated as "" when accessing an array (equals PHP behavior)');
}
public function getTestsDependingOnExtensionAvailability()
{
if (function_exists('twig_template_get_attributes')) {
return array(array(false), array(true));
}
return array(array(false));
}
/** /**
* @dataProvider getGetAttributeTests * @dataProvider getGetAttributeTests
*/ */
@@ -199,6 +242,8 @@ class Twig_Tests_TemplateTest extends PHPUnit_Framework_TestCase
'null' => null, 'null' => null,
'1' => 1, '1' => 1,
'bar' => true, 'bar' => true,
'09' => '09',
'+4' => '+4',
); );
$objectArray = new Twig_TemplateArrayAccessObject(); $objectArray = new Twig_TemplateArrayAccessObject();
@@ -224,6 +269,8 @@ class Twig_Tests_TemplateTest extends PHPUnit_Framework_TestCase
array(true, 1, 1.0), array(true, 1, 1.0),
array(true, null, 'null'), array(true, null, 'null'),
array(true, true, 'bar'), array(true, true, 'bar'),
array(true, '09', '09'),
array(true, '+4', '+4'),
); );
$testObjects = array( $testObjects = array(
// array(object, type of fetch) // array(object, type of fetch)
@@ -243,6 +290,10 @@ class Twig_Tests_TemplateTest extends PHPUnit_Framework_TestCase
foreach ($basicTests as $test) { foreach ($basicTests as $test) {
// properties cannot be numbers // properties cannot be numbers
if (($testObject[0] instanceof stdClass || $testObject[0] instanceof Twig_TemplatePropertyObject) && is_numeric($test[2])) { if (($testObject[0] instanceof stdClass || $testObject[0] instanceof Twig_TemplatePropertyObject) && is_numeric($test[2])) {
continue;
}
if ('+4' === $test[2] && $methodObject === $testObject[0]) {
continue; continue;
} }
@@ -293,9 +344,9 @@ class Twig_Tests_TemplateTest extends PHPUnit_Framework_TestCase
// tests when input is not an array or object // tests when input is not an array or object
$tests = array_merge($tests, array( $tests = array_merge($tests, array(
array(false, null, 42, 'a', array(), $anyType, false, 'Item "a" for "42" does not exist'), array(false, null, 42, 'a', array(), $anyType, false, 'Impossible to access an attribute ("a") on a integer variable ("42")'),
array(false, null, "string", 'a', array(), $anyType, false, 'Item "a" for "string" does not exist'), array(false, null, "string", 'a', array(), $anyType, false, 'Impossible to access an attribute ("a") on a string variable ("string")'),
array(false, null, array(), 'a', array(), $anyType, false, 'Item "a" for "Array" does not exist'), array(false, null, array(), 'a', array(), $anyType, false, 'Key "a" for array with keys "" does not exist'),
)); ));
// add twig_template_get_attributes tests // add twig_template_get_attributes tests
@@ -380,6 +431,8 @@ class Twig_TemplateArrayAccessObject implements ArrayAccess
'null' => null, 'null' => null,
'1' => 1, '1' => 1,
'bar' => true, 'bar' => true,
'09' => '09',
'+4' => '+4',
); );
public function offsetExists($name) public function offsetExists($name)
@@ -410,6 +463,8 @@ class Twig_TemplateMagicPropertyObject
'null' => null, 'null' => null,
'1' => 1, '1' => 1,
'bar' => true, 'bar' => true,
'09' => '09',
'+4' => '+4',
); );
protected $protected = 'protected'; protected $protected = 'protected';
@@ -478,6 +533,11 @@ class Twig_TemplateMethodObject
return 1; return 1;
} }
public function get09()
{
return '09';
}
public function getZero() public function getZero()
{ {
return 0; return 0;