Refactor GetAttr defined implementation to consider null defined

This commit is contained in:
nikic
2011-05-24 12:48:47 +02:00
parent d90b2f7112
commit f742efd602
4 changed files with 180 additions and 114 deletions
-2
View File
@@ -33,9 +33,7 @@ class Twig_Node_Expression_Test extends Twig_Node_Expression
} elseif ($this->getNode('node') instanceof Twig_Node_Expression_GetAttr) {
$this->getNode('node')->setAttribute('is_defined_test', true);
$compiler
->raw('(null !== ')
->subcompile($this->getNode('node'))
->raw(')')
;
} else {
throw new Twig_Error_Syntax('The "defined" test only works with simple variables', $this->getLine());
+37 -10
View File
@@ -224,8 +224,6 @@ abstract class Twig_Template implements Twig_TemplateInterface
* @param array $context The context
* @param string $item The variable to return from the context
*
* @param mixed The variable value in the context
*
* @throws Twig_Error_Runtime if the variable does not exist
*/
protected function getContext($context, $item)
@@ -243,19 +241,29 @@ abstract class Twig_Template implements Twig_TemplateInterface
* @param mixed $object The object or array from where to get the item
* @param mixed $item The item to get from the array or object
* @param array $arguments An array of arguments to pass if the item is an object method
* @param integer $type The type of attribute (@see Twig_TemplateInterface)
* @param Boolean $noStrictCheck Whether to throw an exception if the item does not exist ot not
* @param string $type The type of attribute (@see Twig_TemplateInterface)
* @param Boolean $isDefinedTest Whether this is only a defined check
*/
protected function getAttribute($object, $item, array $arguments = array(), $type = Twig_TemplateInterface::ANY_CALL, $noStrictCheck = false)
protected function getAttribute($object, $item, array $arguments = array(), $type = Twig_TemplateInterface::ANY_CALL, $isDefinedTest = false)
{
// array
if (Twig_TemplateInterface::METHOD_CALL !== $type) {
if ((is_array($object) || is_object($object) && $object instanceof ArrayAccess) && isset($object[$item])) {
if ((is_array($object) && array_key_exists($item, $object))
|| ($object instanceof ArrayAccess && isset($object[$item]))
) {
if ($isDefinedTest) {
return true;
}
return $object[$item];
}
if (Twig_TemplateInterface::ARRAY_CALL === $type) {
if (!$this->env->isStrictVariables() || $noStrictCheck) {
if ($isDefinedTest) {
return false;
}
if (!$this->env->isStrictVariables()) {
return null;
}
@@ -269,9 +277,14 @@ abstract class Twig_Template implements Twig_TemplateInterface
}
if (!is_object($object)) {
if (!$this->env->isStrictVariables() || $noStrictCheck) {
if ($isDefinedTest) {
return false;
}
if (!$this->env->isStrictVariables()) {
return null;
}
throw new Twig_Error_Runtime(sprintf('Item "%s" for "%s" does not exist', $item, $object));
}
@@ -291,7 +304,13 @@ abstract class Twig_Template implements Twig_TemplateInterface
// object property
if (Twig_TemplateInterface::METHOD_CALL !== $type) {
if (isset(self::$cache[$class]['properties'][$item]) || isset($object->$item)) {
if (isset(self::$cache[$class]['properties'][$item])
|| isset($object->$item) || array_key_exists($item, $object)
) {
if ($isDefinedTest) {
return true;
}
if ($this->env->hasExtension('sandbox')) {
$this->env->getExtension('sandbox')->checkPropertyAllowed($object, $item);
}
@@ -311,13 +330,21 @@ abstract class Twig_Template implements Twig_TemplateInterface
} elseif (isset(self::$cache[$class]['methods']['__call'])) {
$method = $item;
} else {
if (!$this->env->isStrictVariables() || $noStrictCheck) {
if ($isDefinedTest) {
return false;
}
if (!$this->env->isStrictVariables()) {
return null;
}
throw new Twig_Error_Runtime(sprintf('Method "%s" for object "%s" does not exist', $item, get_class($object)));
}
if ($isDefinedTest) {
return true;
}
if ($this->env->hasExtension('sandbox')) {
$this->env->getExtension('sandbox')->checkMethodAllowed($object, $method);
}
+9 -6
View File
@@ -5,21 +5,23 @@
{{ definedVar is not defined ? 'ko' : 'ok' }}
{{ undefinedVar is defined ? 'ko' : 'ok' }}
{{ undefinedVar is not defined ? 'ok' : 'ko' }}
{{ nullVar is defined ? 'ok' : 'ko' }}
{{ zeroVar is defined ? 'ok' : 'ko' }}
{{ nullVar is defined ? 'ok' : 'ko' }}
{{ nested.definedVar is defined ? 'ok' : 'ko' }}
{{ nested.definedVar is not defined ? 'ko' : 'ok' }}
{{ nested.undefinedVar is defined ? 'ko' : 'ok' }}
{{ nested.undefinedVar is not defined ? 'ok' : 'ko' }}
{{ nested.zeroVar is defined ? 'ok' : 'ko' }}
{{ nested.nullVar is defined ? 'ok' : 'ko' }}
--DATA--
return array(
'definedVar' => 'bar',
'nullVar' => null,
'definedVar' => 'defined',
'zeroVar' => 0,
'nested' => array(
'definedVar' => 'foo',
'zeroVar' => 0
'nullVar' => null,
'nested' => array(
'definedVar' => 'defined',
'zeroVar' => 0,
'nullVar' => null,
)
);
--EXPECT--
@@ -34,3 +36,4 @@ ok
ok
ok
ok
ok
+134 -96
View File
@@ -10,78 +10,116 @@
*/
class Twig_Tests_TemplateTest extends PHPUnit_Framework_TestCase
{
public function getUnknownPropertyOnArrayTests()
{
$tests = array(
array(array('foo' => 'foo', 'bar' => 'value')),
array(new Twig_TemplateObjectArrayAccess()),
);
return $tests;
}
/**
* @dataProvider getUnknownPropertyOnArrayTests
* @expectedException Twig_Error_Runtime
* @dataProvider getGetAttributeTests
*/
public function testUnknownPropertyOnArray($array)
public function testGetAttribute($defined, $value, $object, $item, $arguments, $type)
{
$env = new Twig_Environment(null, array('strict_variables' => true));
$template = new Twig_TemplateTest($env);
$template = new Twig_TemplateTest(new Twig_Environment());
$template->getAttribute($array, 'unknown', array(), Twig_TemplateInterface::ARRAY_CALL);
$this->assertEquals($value, $template->getAttribute($object, $item, $arguments, $type));
}
/**
* @dataProvider getGetAttributeTests
*/
public function testGetAttribute($expected, $object, $item, $arguments, $type)
public function testGetAttributeStrict($defined, $value, $object, $item, $arguments, $type)
{
$template = new Twig_TemplateTest(
new Twig_Environment(null, array('strict_variables' => true))
);
if ($defined) {
$this->assertEquals($value, $template->getAttribute($object, $item, $arguments, $type));
} else {
try {
$this->assertEquals($value, $template->getAttribute($object, $item, $arguments, $type));
throw new Exception('Expected Twig_Error_Runtime exception.');
} catch (Twig_Error_Runtime $e) { }
}
}
/**
* @dataProvider getGetAttributeTests
*/
public function testGetAttributeDefined($defined, $value, $object, $item, $arguments, $type)
{
$template = new Twig_TemplateTest(new Twig_Environment());
$this->assertEquals($expected, $template->getAttribute($object, $item, $arguments, $type));
$this->assertEquals($defined, $template->getAttribute($object, $item, $arguments, $type, true));
}
/**
* @dataProvider getGetAttributeTests
*/
public function testGetAttributeDefinedStrict($defined, $value, $object, $item, $arguments, $type)
{
$template = new Twig_TemplateTest(
new Twig_Environment(null, array('strict_variables' => true))
);
$this->assertEquals($defined, $template->getAttribute($object, $item, $arguments, $type, true));
}
public function getGetAttributeTests()
{
$array = array('foo' => 'foo');
$object = new Twig_TemplateObject();
$objectArray = new Twig_TemplateObjectArrayAccess();
$objectMagic = new Twig_TemplateObjectMagic();
$anyType = Twig_TemplateInterface::ANY_CALL;
$methodType = Twig_TemplateInterface::METHOD_CALL;
$arrayType = Twig_TemplateInterface::ARRAY_CALL;
$tests = array(
// ARRAY
array('foo', $array, 'foo', array(), $arrayType),
array(null, $array, 'foobar', array(), $arrayType),
array('foo', $objectArray, 'foo', array(), $arrayType),
array(null, $objectArray, 'foobar', array(), $arrayType),
// METHOD
array('bar', $object, 'bar', array(), $methodType),
array('bar', $object, 'getBar', array(), $methodType),
array('bar', $object, 'getbar', array(), $methodType),
array('foobar', $object, 'foobar', array(), $methodType),
array('babar', $object, 'babar', array(), $methodType),
array('babarStatic', $object, 'babarStatic', array(), $methodType),
array('__call_baz', $objectMagic, 'baz', array(), $methodType),
array('__call_Baz', $objectMagic, 'Baz', array(), $methodType),
// ANY
array('foo', $object, 'foo', array(), $anyType),
array('foo', $objectMagic, 'foo', array(), $anyType),
array('Foo', $objectMagic, 'Foo', array(), $anyType),
array('babar', $object, 'babar', array(), $anyType),
array(null, $object, 'null', array(), $anyType),
$array = array(
'defined' => 'defined',
'zero' => 0,
'null' => null,
);
$objectArray = new Twig_TemplateArrayAccessObject;
$stdObject = (object) $array;
$magicPropertyObject = new Twig_TemplateMagicPropertyObject;
$methodObject = new Twig_TemplateMethodObject;
$magicMethodObject = new Twig_TemplateMagicMethodObject;
$anyType = Twig_TemplateInterface::ANY_CALL;
$methodType = Twig_TemplateInterface::METHOD_CALL;
$arrayType = Twig_TemplateInterface::ARRAY_CALL;
$basicTests = array(
// array(defined, value, property to fetch)
array(true, 'defined', 'defined'),
array(false, null, 'undefined'),
array(true, 0, 'zero'),
array(true, null, 'null'),
);
$testObjects = array(
// array(object, type of fetch)
array($array, $arrayType),
array($objectArray, $arrayType),
array($stdObject, $anyType),
array($magicPropertyObject, $anyType),
array($methodObject, $methodType),
);
$tests = array();
foreach ($testObjects as $testObject) {
foreach ($basicTests as $test) {
$tests[] = array($test[0], $test[1], $testObject[0], $test[2], array(), $testObject[1]);
}
}
// additional method tests
$tests = array_merge($tests, array(
array(true, 'defined', $methodObject, 'defined', array(), $methodType),
array(true, 'defined', $methodObject, 'DEFINED', array(), $methodType),
array(true, 'defined', $methodObject, 'getDefined', array(), $methodType),
array(true, 'defined', $methodObject, 'GETDEFINED', array(), $methodType),
array(true, 'static', $methodObject, 'static', array(), $methodType),
array(true, 'static', $methodObject, 'getStatic', array(), $methodType),
array(true, '__call_undefined', $magicMethodObject, 'undefined', array(), $methodType),
array(true, '__call_UNDEFINED', $magicMethodObject, 'UNDEFINED', array(), $methodType),
));
// add the same tests for the any type
foreach ($tests as $test) {
if ($anyType !== $test[4]) {
$test[4] = $anyType;
if ($anyType !== $test[5]) {
$test[5] = $anyType;
$tests[] = $test;
}
}
@@ -96,57 +134,28 @@ class Twig_TemplateTest extends Twig_Template
{
}
public function getAttribute($object, $item, array $arguments = array(), $type = Twig_TemplateInterface::ANY_CALL, $noStrictCheck = false, $lineno = -1)
public function getAttribute($object, $item, array $arguments = array(), $type = Twig_TemplateInterface::ANY_CALL, $isDefinedTest = false)
{
return parent::getAttribute($object, $item, $arguments, $type);
return parent::getAttribute($object, $item, $arguments, $type, $isDefinedTest);
}
}
class Twig_TemplateObject
class Twig_TemplateArrayAccessObject implements ArrayAccess
{
public $foo = 'foo';
public $null = null;
protected $babar = 'babar...';
static protected $babarStatic = 'babarStatic...';
static public function getBabarStatic()
{
return 'babarStatic';
}
public function getBabar()
{
return 'babar';
}
public function getNull()
{
return 'null...';
}
public function getBar()
{
return 'bar';
}
public function fooBar()
{
return 'foobar';
}
}
class Twig_TemplateObjectArrayAccess implements ArrayAccess
{
public $attributes = array('foo' => 'foo');
public $attributes = array(
'defined' => 'defined',
'zero' => 0,
'null' => null,
);
public function offsetExists($name)
{
return isset($this->attributes[$name]);
return array_key_exists($name, $this->attributes);
}
public function offsetGet($name)
{
return isset($this->attributes[$name]) ? $this->attributes[$name] : null;
return array_key_exists($name, $this->attributes) ? $this->attributes[$name] : null;
}
public function offsetSet($name, $value)
@@ -158,22 +167,51 @@ class Twig_TemplateObjectArrayAccess implements ArrayAccess
}
}
class Twig_TemplateObjectMagic
class Twig_TemplateMagicPropertyObject
{
public $attributes = array('foo' => 'foo', 'Foo' => 'Foo');
public $attributes = array(
'defined' => 'defined',
'zero' => 0,
'null' => null,
);
public function __isset($name)
{
return isset($this->attributes[$name]);
return array_key_exists($name, $this->attributes);
}
public function __get($name)
{
return isset($this->attributes[$name]) ? $this->attributes[$name] : null;
return array_key_exists($name, $this->attributes) ? $this->attributes[$name] : null;
}
}
class Twig_TemplateMethodObject
{
public function getDefined()
{
return 'defined';
}
public function __call($method, $arguments)
public function getZero()
{
return 0;
}
public function getNull()
{
return null;
}
static public function getStatic()
{
return 'static';
}
}
class Twig_TemplateMagicMethodObject
{
public function __call($method, $arguments) {
return '__call_'.$method;
}
}