Skip to content

Commit 0a4c21d

Browse files
committed
Added support foo.some_thing === foo.someThing for properties and array calls
1 parent cc48443 commit 0a4c21d

File tree

3 files changed

+198
-52
lines changed

3 files changed

+198
-52
lines changed

ext/twig/twig.c

Lines changed: 86 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -733,10 +733,11 @@ static int twig_add_property_to_class(void *pDest APPLY_TSRMLS_DC, int num_args,
733733
zend_class_entry *ce;
734734
zval *retval;
735735
char *class_name, *prop_name;
736+
736737
zend_property_info *pptr = (zend_property_info *) pDest;
737738
APPLY_TSRMLS_FETCH();
738739

739-
if (!(pptr->flags & ZEND_ACC_PUBLIC) || (pptr->flags & ZEND_ACC_STATIC)) {
740+
if ((pptr->flags & ZEND_ACC_PRIVATE) || (pptr->flags & ZEND_ACC_STATIC)) {
740741
return 0;
741742
}
742743

@@ -750,6 +751,7 @@ static int twig_add_property_to_class(void *pDest APPLY_TSRMLS_DC, int num_args,
750751
#endif
751752

752753
add_assoc_string(retval, prop_name, prop_name, 1);
754+
add_assoc_string(retval, TWIG_DECAMELIZE(prop_name), prop_name, 1);
753755

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

774777
add_assoc_zval(class_info, "methods", class_methods);
@@ -813,14 +816,46 @@ PHP_FUNCTION(twig_template_get_attributes)
813816
type = "any";
814817
}
815818

819+
/*
820+
if (is_object($object)) {
821+
$class = get_class($object);
822+
if (!isset(self::$cache[$class])) {
823+
self::$cache[$class] = $this->getCacheForClass($class);
824+
}
825+
}
826+
*/
827+
if (Z_TYPE_P(object) == IS_OBJECT) {
828+
class_name = TWIG_GET_CLASS_NAME(object TSRMLS_CC);
829+
tmp_self_cache = TWIG_GET_STATIC_PROPERTY(template, "cache" TSRMLS_CC);
830+
tmp_class = TWIG_GET_ARRAY_ELEMENT(tmp_self_cache, class_name, strlen(class_name) TSRMLS_CC);
831+
832+
if (!tmp_class) {
833+
twig_add_class_to_cache(tmp_self_cache, object, class_name TSRMLS_CC);
834+
tmp_class = TWIG_GET_ARRAY_ELEMENT(tmp_self_cache, class_name, strlen(class_name) TSRMLS_CC);
835+
}
836+
efree(class_name);
837+
}
838+
816839
/*
817840
// array
818841
if (Twig_Template::METHOD_CALL !== $type) {
819842
$arrayItem = is_bool($item) || is_float($item) ? (int) $item : $item;
843+
$hasArrayItem = false;
844+
845+
if (is_array($object) && array_key_exists($arrayItem, $object)) {
846+
$hasArrayItem = true;
847+
} elseif ($object instanceof ArrayAccess) {
848+
if (isset($object[$arrayItem])) {
849+
$hasArrayItem = true;
850+
} elseif (isset(self::$cache[$class]['properties'][$arrayItem])
851+
&& isset($object[self::$cache[$class]['properties'][$arrayItem]])
852+
) {
853+
$arrayItem = self::$cache[$class]['properties'][$arrayItem];
854+
$hasArrayItem = true;
855+
}
856+
}
820857
821-
if ((is_array($object) && array_key_exists($arrayItem, $object))
822-
|| ($object instanceof ArrayAccess && isset($object[$arrayItem]))
823-
) {
858+
if ($hasArrayItem) {
824859
if ($isDefinedTest) {
825860
return true;
826861
}
@@ -831,15 +866,29 @@ PHP_FUNCTION(twig_template_get_attributes)
831866

832867

833868
if (strcmp("method", type) != 0) {
834-
if ((TWIG_ARRAY_KEY_EXISTS(object, zitem))
835-
|| (TWIG_INSTANCE_OF(object, zend_ce_arrayaccess TSRMLS_CC) && TWIG_ISSET_ARRAYOBJECT_ELEMENT(object, zitem TSRMLS_CC))
836-
) {
869+
zval *tmp_properties, *tmp_item = NULL;
870+
871+
if (TWIG_ARRAY_KEY_EXISTS(object, zitem)) {
872+
tmp_item = zitem;
873+
} else if (TWIG_INSTANCE_OF(object, zend_ce_arrayaccess TSRMLS_CC)) {
874+
if (TWIG_ISSET_ARRAYOBJECT_ELEMENT(object, zitem TSRMLS_CC)) {
875+
tmp_item = zitem;
876+
} else {
877+
tmp_properties = TWIG_GET_ARRAY_ELEMENT(tmp_class, "properties", strlen("properties") TSRMLS_CC);
878+
if ((tmp_item = TWIG_GET_ARRAY_ELEMENT(tmp_properties, item, item_len TSRMLS_CC))
879+
&& !TWIG_ISSET_ARRAYOBJECT_ELEMENT(object, tmp_item TSRMLS_CC)
880+
) {
881+
tmp_item = NULL;
882+
}
883+
}
884+
}
837885

886+
if (tmp_item) {
838887
if (isDefinedTest) {
839888
RETURN_TRUE;
840889
}
841890

842-
ret = TWIG_GET_ARRAY_ELEMENT_ZVAL(object, zitem TSRMLS_CC);
891+
ret = TWIG_GET_ARRAY_ELEMENT_ZVAL(object, tmp_item TSRMLS_CC);
843892

844893
if (!ret) {
845894
ret = &EG(uninitialized_zval);
@@ -856,7 +905,7 @@ PHP_FUNCTION(twig_template_get_attributes)
856905
return false;
857906
}
858907
if ($ignoreStrictCheck || !$this->env->isStrictVariables()) {
859-
return null;
908+
return;
860909
}
861910
*/
862911
if (strcmp("array", type) == 0 || Z_TYPE_P(object) != IS_OBJECT) {
@@ -924,8 +973,9 @@ PHP_FUNCTION(twig_template_get_attributes)
924973
}
925974
/*
926975
if ($ignoreStrictCheck || !$this->env->isStrictVariables()) {
927-
return null;
976+
return;
928977
}
978+
929979
throw new Twig_Error_Runtime(sprintf('Impossible to invoke a method ("%s") on a %s variable ("%s")', $item, gettype($object), $object), -1, $this->getTemplateName());
930980
}
931981
*/
@@ -943,65 +993,64 @@ PHP_FUNCTION(twig_template_get_attributes)
943993

944994
return;
945995
}
946-
/*
947-
$class = get_class($object);
948-
*/
949-
950-
class_name = TWIG_GET_CLASS_NAME(object TSRMLS_CC);
951-
tmp_self_cache = TWIG_GET_STATIC_PROPERTY(template, "cache" TSRMLS_CC);
952-
tmp_class = TWIG_GET_ARRAY_ELEMENT(tmp_self_cache, class_name, strlen(class_name) TSRMLS_CC);
953-
954-
if (!tmp_class) {
955-
twig_add_class_to_cache(tmp_self_cache, object, class_name TSRMLS_CC);
956-
tmp_class = TWIG_GET_ARRAY_ELEMENT(tmp_self_cache, class_name, strlen(class_name) TSRMLS_CC);
957-
}
958-
efree(class_name);
959996

960997
/*
961998
// object property
962999
if (Twig_Template::METHOD_CALL !== $type) {
1000+
$property = null;
1001+
9631002
if (isset($object->$item) || array_key_exists((string) $item, $object)) {
1003+
$property = $item;
1004+
} elseif (isset(self::$cache[$class]['properties'][$item])
1005+
&& isset($object->{self::$cache[$class]['properties'][$item]})
1006+
) {
1007+
$property = self::$cache[$class]['properties'][$item];
1008+
}
1009+
1010+
if (null !== $property) {
9641011
if ($isDefinedTest) {
9651012
return true;
9661013
}
9671014
9681015
if ($this->env->hasExtension('sandbox')) {
969-
$this->env->getExtension('sandbox')->checkPropertyAllowed($object, $item);
1016+
$this->env->getExtension('sandbox')->checkPropertyAllowed($object, $property);
9701017
}
9711018
972-
return $object->$item;
1019+
return $object->$property;
9731020
}
9741021
}
9751022
*/
9761023
if (strcmp("method", type) != 0) {
977-
zval *tmp_properties, *tmp_item;
1024+
zval *tmp_properties, *tmp_item = NULL;
9781025

9791026
tmp_properties = TWIG_GET_ARRAY_ELEMENT(tmp_class, "properties", strlen("properties") TSRMLS_CC);
980-
tmp_item = TWIG_GET_ARRAY_ELEMENT(tmp_properties, item, item_len TSRMLS_CC);
9811027

982-
if (tmp_item || TWIG_HAS_PROPERTY(object, zitem TSRMLS_CC) || TWIG_HAS_DYNAMIC_PROPERTY(object, item, item_len TSRMLS_CC)) {
1028+
if (TWIG_HAS_PROPERTY(object, zitem TSRMLS_CC) || TWIG_HAS_DYNAMIC_PROPERTY(object, item, item_len TSRMLS_CC)) {
1029+
tmp_item = zitem;
1030+
} else if (tmp_item = TWIG_GET_ARRAY_ELEMENT(tmp_properties, item, item_len TSRMLS_CC)) {
1031+
if (!TWIG_HAS_PROPERTY(object, tmp_item TSRMLS_CC) && !TWIG_HAS_DYNAMIC_PROPERTY(object, Z_STRVAL_P(tmp_item), strlen(tmp_item) TSRMLS_CC)) {
1032+
tmp_item = NULL;
1033+
}
1034+
}
1035+
1036+
if (tmp_item) {
9831037
if (isDefinedTest) {
9841038
RETURN_TRUE;
9851039
}
9861040
if (TWIG_CALL_SB(TWIG_PROPERTY_CHAR(template, "env" TSRMLS_CC), "hasExtension", "sandbox" TSRMLS_CC)) {
987-
TWIG_CALL_ZZ(TWIG_CALL_S(TWIG_PROPERTY_CHAR(template, "env" TSRMLS_CC), "getExtension", "sandbox" TSRMLS_CC), "checkPropertyAllowed", object, zitem TSRMLS_CC);
1041+
TWIG_CALL_ZZ(TWIG_CALL_S(TWIG_PROPERTY_CHAR(template, "env" TSRMLS_CC), "getExtension", "sandbox" TSRMLS_CC), "checkPropertyAllowed", object, tmp_item TSRMLS_CC);
9881042
}
9891043
if (EG(exception)) {
9901044
return;
9911045
}
9921046

993-
ret = TWIG_PROPERTY(object, zitem TSRMLS_CC);
1047+
ret = TWIG_PROPERTY(object, tmp_item TSRMLS_CC);
9941048
RETURN_ZVAL(ret, 1, 0);
9951049
}
9961050
}
9971051
/*
9981052
// object method
999-
if (!isset(self::$cache[$class]['methods'])) {
1000-
self::$cache[$class] = $this->getCacheForClass($class);
1001-
}
1002-
10031053
$call = false;
1004-
$lcItem = strtolower($item);
10051054
if (isset(self::$cache[$class]['methods'][$item])) {
10061055
$method = self::$cache[$class]['methods'][$item];
10071056
} elseif (($lcItem = strtolower($item)) && isset(self::$cache[$class]['methods'][$lcItem])) {
@@ -1031,7 +1080,7 @@ PHP_FUNCTION(twig_template_get_attributes)
10311080
}
10321081
10331082
if ($ignoreStrictCheck || !$this->env->isStrictVariables()) {
1034-
return null;
1083+
return;
10351084
}
10361085
10371086
throw new Twig_Error_Runtime(sprintf('Method "%s" for object "%s" does not exist', $item, get_class($object)), -1, $this->getTemplateName());
@@ -1076,7 +1125,7 @@ PHP_FUNCTION(twig_template_get_attributes)
10761125
$ret = call_user_func_array(array($object, $method), $arguments);
10771126
} catch (BadMethodCallException $e) {
10781127
if ($call && ($ignoreStrictCheck || !$this->env->isStrictVariables())) {
1079-
return null;
1128+
return;
10801129
}
10811130
throw $e;
10821131
}

lib/Twig/Template.php

Lines changed: 56 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -361,13 +361,32 @@ final protected function getContext($context, $item, $ignoreStrictCheck = false)
361361
*/
362362
protected function getAttribute($object, $item, array $arguments = array(), $type = Twig_Template::ANY_CALL, $isDefinedTest = false, $ignoreStrictCheck = false)
363363
{
364+
if (is_object($object)) {
365+
$class = get_class($object);
366+
if (!isset(self::$cache[$class])) {
367+
self::$cache[$class] = $this->getCacheForClass($class);
368+
}
369+
}
370+
364371
// array
365372
if (Twig_Template::METHOD_CALL !== $type) {
366373
$arrayItem = is_bool($item) || is_float($item) ? (int) $item : $item;
374+
$hasArrayItem = false;
375+
376+
if (is_array($object) && array_key_exists($arrayItem, $object)) {
377+
$hasArrayItem = true;
378+
} elseif ($object instanceof ArrayAccess) {
379+
if (isset($object[$arrayItem])) {
380+
$hasArrayItem = true;
381+
} elseif (isset(self::$cache[$class]['properties'][$arrayItem])
382+
&& isset($object[self::$cache[$class]['properties'][$arrayItem]])
383+
) {
384+
$arrayItem = self::$cache[$class]['properties'][$arrayItem];
385+
$hasArrayItem = true;
386+
}
387+
}
367388

368-
if ((is_array($object) && array_key_exists($arrayItem, $object))
369-
|| ($object instanceof ArrayAccess && isset($object[$arrayItem]))
370-
) {
389+
if ($hasArrayItem) {
371390
if ($isDefinedTest) {
372391
return true;
373392
}
@@ -418,26 +437,32 @@ protected function getAttribute($object, $item, array $arguments = array(), $typ
418437

419438
// object property
420439
if (Twig_Template::METHOD_CALL !== $type) {
440+
$property = null;
441+
421442
if (isset($object->$item) || array_key_exists((string) $item, $object)) {
443+
$property = $item;
444+
} elseif (isset(self::$cache[$class]['properties'][$item])
445+
&& isset($object->{self::$cache[$class]['properties'][$item]})
446+
) {
447+
$property = self::$cache[$class]['properties'][$item];
448+
}
449+
450+
if (null !== $property) {
422451
if ($isDefinedTest) {
423452
return true;
424453
}
425454

426455
if ($this->env->hasExtension('sandbox')) {
427-
$this->env->getExtension('sandbox')->checkPropertyAllowed($object, $item);
456+
$this->env->getExtension('sandbox')->checkPropertyAllowed($object, $property);
428457
}
429458

430-
return $object->$item;
459+
return $object->$property;
431460
}
432461
}
433462

434463
$class = get_class($object);
435464

436465
// object method
437-
if (!isset(self::$cache[$class])) {
438-
self::$cache[$class] = $this->getCacheForClass($class);
439-
}
440-
441466
$call = false;
442467
if (isset(self::$cache[$class]['methods'][$item])) {
443468
$method = self::$cache[$class]['methods'][$item];
@@ -497,16 +522,32 @@ protected function getAttribute($object, $item, array $arguments = array(), $typ
497522
*/
498523
protected function getCacheForClass($class)
499524
{
525+
$cache = array('methods' => array(), 'properties' => array());
526+
500527
$methods = get_class_methods($class);
528+
if (!empty($methods)) {
529+
$cache['methods'] = array_combine($methods, $methods);
530+
$keys = array_merge(preg_replace('/^(?:get|is)_?(.++)$/i', '\\1', $methods), $methods);
531+
$keys = array_merge(preg_replace('/((?<=[a-z]|\d)[A-Z]|(?<!^)[A-Z](?=[a-z]))/', '_\\1', $keys), $keys);
532+
$cache['methods'] += array_change_key_case(array_combine($keys, array_merge($methods, $methods, $methods, $methods)));
533+
}
501534

502-
if (empty($methods)) {
503-
return array('methods' => array());
535+
$properties = array_keys(get_class_vars($class));
536+
if ((isset($cache['methods']['__isset']) && isset($cache['methods']['__get']))
537+
|| (($implements = class_implements($class, false)) && isset($implements['ArrayAccess']))
538+
) {
539+
$reflection = new ReflectionClass($class);
540+
foreach ($reflection->getProperties(ReflectionProperty::IS_PROTECTED) as $property) {
541+
$properties[] = $property->getName();
542+
}
504543
}
505544

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

510-
return array('methods' => $cache + array_change_key_case(array_combine($keys, array_merge($methods, $methods, $methods, $methods))));
551+
return $cache;
511552
}
512553
}

0 commit comments

Comments
 (0)