1<?php 2 3/* 4 * This file is part of Twig. 5 * 6 * (c) Fabien Potencier 7 * 8 * For the full copyright and license information, please view the LICENSE 9 * file that was distributed with this source code. 10 */ 11 12use Twig\Environment; 13use Twig\Error\RuntimeError; 14use Twig\Extension\SandboxExtension; 15use Twig\Loader\ArrayLoader; 16use Twig\Loader\LoaderInterface; 17use Twig\Loader\SourceContextLoaderInterface; 18use Twig\Node\Expression\GetAttrExpression; 19use Twig\NodeVisitor\NodeVisitorInterface; 20use Twig\Sandbox\SecurityError; 21use Twig\Sandbox\SecurityPolicy; 22use Twig\Template; 23 24class Twig_Tests_TemplateTest extends \PHPUnit\Framework\TestCase 25{ 26 /** 27 * @expectedException \LogicException 28 */ 29 public function testDisplayBlocksAcceptTemplateOnlyAsBlocks() 30 { 31 $template = $this->getMockForAbstractClass('\Twig\Template', [], '', false); 32 $template->displayBlock('foo', [], ['foo' => [new \stdClass(), 'foo']]); 33 } 34 35 /** 36 * @dataProvider getAttributeExceptions 37 */ 38 public function testGetAttributeExceptions($template, $message) 39 { 40 $templates = ['index' => $template]; 41 $env = new Environment(new ArrayLoader($templates), ['strict_variables' => true]); 42 $template = $env->load('index'); 43 44 $context = [ 45 'string' => 'foo', 46 'null' => null, 47 'empty_array' => [], 48 'array' => ['foo' => 'foo'], 49 'array_access' => new Twig_TemplateArrayAccessObject(), 50 'magic_exception' => new Twig_TemplateMagicPropertyObjectWithException(), 51 'object' => new \stdClass(), 52 ]; 53 54 try { 55 $template->render($context); 56 $this->fail('Accessing an invalid attribute should throw an exception.'); 57 } catch (RuntimeError $e) { 58 $this->assertSame(sprintf($message, 'index'), $e->getMessage()); 59 } 60 } 61 62 public function getAttributeExceptions() 63 { 64 return [ 65 ['{{ string["a"] }}', 'Impossible to access a key ("a") on a string variable ("foo") in "%s" at line 1.'], 66 ['{{ null["a"] }}', 'Impossible to access a key ("a") on a null variable in "%s" at line 1.'], 67 ['{{ empty_array["a"] }}', 'Key "a" does not exist as the array is empty in "%s" at line 1.'], 68 ['{{ array["a"] }}', 'Key "a" for array with keys "foo" does not exist in "%s" at line 1.'], 69 ['{{ array_access["a"] }}', 'Key "a" in object with ArrayAccess of class "Twig_TemplateArrayAccessObject" does not exist in "%s" at line 1.'], 70 ['{{ string.a }}', 'Impossible to access an attribute ("a") on a string variable ("foo") in "%s" at line 1.'], 71 ['{{ string.a() }}', 'Impossible to invoke a method ("a") on a string variable ("foo") in "%s" at line 1.'], 72 ['{{ null.a }}', 'Impossible to access an attribute ("a") on a null variable in "%s" at line 1.'], 73 ['{{ null.a() }}', 'Impossible to invoke a method ("a") on a null variable in "%s" at line 1.'], 74 ['{{ array.a() }}', 'Impossible to invoke a method ("a") on an array in "%s" at line 1.'], 75 ['{{ empty_array.a }}', 'Key "a" does not exist as the array is empty in "%s" at line 1.'], 76 ['{{ array.a }}', 'Key "a" for array with keys "foo" does not exist in "%s" at line 1.'], 77 ['{{ attribute(array, -10) }}', 'Key "-10" for array with keys "foo" does not exist in "%s" at line 1.'], 78 ['{{ array_access.a }}', 'Neither the property "a" nor one of the methods "a()", "geta()"/"isa()" or "__call()" exist and have public access in class "Twig_TemplateArrayAccessObject" in "%s" at line 1.'], 79 ['{% from _self import foo %}{% macro foo(obj) %}{{ obj.missing_method() }}{% endmacro %}{{ foo(array_access) }}', 'Neither the property "missing_method" nor one of the methods "missing_method()", "getmissing_method()"/"ismissing_method()" or "__call()" exist and have public access in class "Twig_TemplateArrayAccessObject" in "%s" at line 1.'], 80 ['{{ magic_exception.test }}', 'An exception has been thrown during the rendering of a template ("Hey! Don\'t try to isset me!") in "%s" at line 1.'], 81 ['{{ object["a"] }}', 'Impossible to access a key "a" on an object of class "stdClass" that does not implement ArrayAccess interface in "%s" at line 1.'], 82 ]; 83 } 84 85 /** 86 * @dataProvider getGetAttributeWithSandbox 87 */ 88 public function testGetAttributeWithSandbox($object, $item, $allowed) 89 { 90 $twig = new Environment($this->getMockBuilder('\Twig\Loader\LoaderInterface')->getMock()); 91 $policy = new SecurityPolicy([], [], [/*method*/], [/*prop*/], []); 92 $twig->addExtension(new SandboxExtension($policy, !$allowed)); 93 $template = new Twig_TemplateTest($twig); 94 95 try { 96 $template->getAttribute($object, $item, [], 'any'); 97 98 if (!$allowed) { 99 $this->fail(); 100 } else { 101 $this->addToAssertionCount(1); 102 } 103 } catch (SecurityError $e) { 104 if ($allowed) { 105 $this->fail(); 106 } else { 107 $this->addToAssertionCount(1); 108 } 109 110 $this->assertContains('is not allowed', $e->getMessage()); 111 } 112 } 113 114 public function getGetAttributeWithSandbox() 115 { 116 return [ 117 [new Twig_TemplatePropertyObject(), 'defined', false], 118 [new Twig_TemplatePropertyObject(), 'defined', true], 119 [new Twig_TemplateMethodObject(), 'defined', false], 120 [new Twig_TemplateMethodObject(), 'defined', true], 121 ]; 122 } 123 124 /** 125 * @group legacy 126 */ 127 public function testGetAttributeWithTemplateAsObject() 128 { 129 // to be removed in 2.0 130 $twig = new Environment($this->getMockBuilder('Twig_TemplateTestLoaderInterface')->getMock()); 131 //$twig = new Environment($this->getMockBuilder('\Twig\Loader\LoaderInterface', '\Twig\Loader\SourceContextLoaderInterface')->getMock()); 132 133 $template = new Twig_TemplateTest($twig, 'index.twig'); 134 $template1 = new Twig_TemplateTest($twig, 'index1.twig'); 135 136 $this->assertInstanceOf('\Twig\Markup', $template->getAttribute($template1, 'string')); 137 $this->assertEquals('some_string', $template->getAttribute($template1, 'string')); 138 139 $this->assertInstanceOf('\Twig\Markup', $template->getAttribute($template1, 'true')); 140 $this->assertEquals('1', $template->getAttribute($template1, 'true')); 141 142 $this->assertInstanceOf('\Twig\Markup', $template->getAttribute($template1, 'zero')); 143 $this->assertEquals('0', $template->getAttribute($template1, 'zero')); 144 145 $this->assertNotInstanceof('\Twig\Markup', $template->getAttribute($template1, 'empty')); 146 $this->assertSame('', $template->getAttribute($template1, 'empty')); 147 148 $this->assertFalse($template->getAttribute($template1, 'env', [], Template::ANY_CALL, true)); 149 $this->assertFalse($template->getAttribute($template1, 'environment', [], Template::ANY_CALL, true)); 150 $this->assertFalse($template->getAttribute($template1, 'getEnvironment', [], Template::METHOD_CALL, true)); 151 $this->assertFalse($template->getAttribute($template1, 'displayWithErrorHandling', [], Template::METHOD_CALL, true)); 152 } 153 154 /** 155 * @group legacy 156 * @expectedDeprecation Calling "string" on template "index1.twig" from template "index.twig" is deprecated since version 1.28 and won't be supported anymore in 2.0. 157 * @expectedDeprecation Calling "string" on template "index1.twig" from template "index.twig" is deprecated since version 1.28 and won't be supported anymore in 2.0. 158 * @expectedDeprecation Calling "true" on template "index1.twig" from template "index.twig" is deprecated since version 1.28 and won't be supported anymore in 2.0. 159 * @expectedDeprecation Calling "true" on template "index1.twig" from template "index.twig" is deprecated since version 1.28 and won't be supported anymore in 2.0. 160 * @expectedDeprecation Calling "zero" on template "index1.twig" from template "index.twig" is deprecated since version 1.28 and won't be supported anymore in 2.0. 161 * @expectedDeprecation Calling "zero" on template "index1.twig" from template "index.twig" is deprecated since version 1.28 and won't be supported anymore in 2.0. 162 * @expectedDeprecation Calling "empty" on template "index1.twig" from template "index.twig" is deprecated since version 1.28 and won't be supported anymore in 2.0. 163 * @expectedDeprecation Calling "empty" on template "index1.twig" from template "index.twig" is deprecated since version 1.28 and won't be supported anymore in 2.0. 164 * @expectedDeprecation Calling "renderBlock" on template "index.twig" from template "index.twig" is deprecated since version 1.28 and won't be supported anymore in 2.0. Use block("name") instead). 165 * @expectedDeprecation Calling "displayBlock" on template "index.twig" from template "index.twig" is deprecated since version 1.28 and won't be supported anymore in 2.0. Use block("name") instead). 166 * @expectedDeprecation Calling "hasBlock" on template "index.twig" from template "index.twig" is deprecated since version 1.28 and won't be supported anymore in 2.0. Use "block("name") is defined" instead). 167 * @expectedDeprecation Calling "render" on template "index.twig" from template "index.twig" is deprecated since version 1.28 and won't be supported anymore in 2.0. Use include("index.twig") instead). 168 * @expectedDeprecation Calling "display" on template "index.twig" from template "index.twig" is deprecated since version 1.28 and won't be supported anymore in 2.0. Use include("index.twig") instead). 169 * @expectedDeprecation Calling "renderBlock" on template "index1.twig" from template "index.twig" is deprecated since version 1.28 and won't be supported anymore in 2.0. Use block("name", template) instead). 170 * @expectedDeprecation Calling "displayBlock" on template "index1.twig" from template "index.twig" is deprecated since version 1.28 and won't be supported anymore in 2.0. Use block("name", template) instead). 171 * @expectedDeprecation Calling "hasBlock" on template "index1.twig" from template "index.twig" is deprecated since version 1.28 and won't be supported anymore in 2.0. Use "block("name", template) is defined" instead). 172 * @expectedDeprecation Calling "render" on template "index1.twig" from template "index.twig" is deprecated since version 1.28 and won't be supported anymore in 2.0. Use include("index1.twig") instead). 173 * @expectedDeprecation Calling "display" on template "index1.twig" from template "index.twig" is deprecated since version 1.28 and won't be supported anymore in 2.0. Use include("index1.twig") instead). 174 */ 175 public function testGetAttributeWithTemplateAsObjectForDeprecations() 176 { 177 // to be removed in 2.0 178 $twig = new Environment($this->getMockBuilder('Twig_TemplateTestLoaderInterface')->getMock()); 179 //$twig = new Environment($this->getMockBuilder('\Twig\Loader\LoaderInterface', '\Twig\Loader\SourceContextLoaderInterface')->getMock()); 180 181 $template = new Twig_TemplateTest($twig, 'index.twig'); 182 $template1 = new Twig_TemplateTest($twig, 'index1.twig'); 183 184 $this->assertInstanceOf('\Twig\Markup', $template->getAttribute($template1, 'string')); 185 $this->assertEquals('some_string', $template->getAttribute($template1, 'string')); 186 187 $this->assertInstanceOf('\Twig\Markup', $template->getAttribute($template1, 'true')); 188 $this->assertEquals('1', $template->getAttribute($template1, 'true')); 189 190 $this->assertInstanceOf('\Twig\Markup', $template->getAttribute($template1, 'zero')); 191 $this->assertEquals('0', $template->getAttribute($template1, 'zero')); 192 193 $this->assertNotInstanceof('\Twig\Markup', $template->getAttribute($template1, 'empty')); 194 $this->assertSame('', $template->getAttribute($template1, 'empty')); 195 196 $blocks = ['name' => [$template1, 'block_name']]; 197 198 // trigger some deprecation notice messages to check them with @expectedDeprecation 199 $template->getAttribute($template, 'renderBlock', ['name', [], $blocks]); 200 $template->getAttribute($template, 'displayBlock', ['name', [], $blocks]); 201 $template->getAttribute($template, 'hasBlock', ['name', []]); 202 $template->getAttribute($template, 'render', [[]]); 203 $template->getAttribute($template, 'display', [[]]); 204 205 $template->getAttribute($template1, 'renderBlock', ['name', [], $blocks]); 206 $template->getAttribute($template1, 'displayBlock', ['name', [], $blocks]); 207 $template->getAttribute($template1, 'hasBlock', ['name', []]); 208 $template->getAttribute($template1, 'render', [[]]); 209 $template->getAttribute($template1, 'display', [[]]); 210 211 $this->assertFalse($template->getAttribute($template1, 'env', [], Template::ANY_CALL, true)); 212 $this->assertFalse($template->getAttribute($template1, 'environment', [], Template::ANY_CALL, true)); 213 $this->assertFalse($template->getAttribute($template1, 'getEnvironment', [], Template::METHOD_CALL, true)); 214 $this->assertFalse($template->getAttribute($template1, 'displayWithErrorHandling', [], Template::METHOD_CALL, true)); 215 } 216 217 /** 218 * @group legacy 219 * @expectedDeprecation Silent display of undefined block "unknown" in template "index.twig" is deprecated since version 1.29 and will throw an exception in 2.0. Use the "block('unknown') is defined" expression to test for block existence. 220 * @expectedDeprecation Silent display of undefined block "unknown" in template "index.twig" is deprecated since version 1.29 and will throw an exception in 2.0. Use the "block('unknown') is defined" expression to test for block existence. 221 */ 222 public function testRenderBlockWithUndefinedBlock() 223 { 224 $twig = new Environment($this->getMockBuilder('Twig_TemplateTestLoaderInterface')->getMock()); 225 226 $template = new Twig_TemplateTest($twig, 'index.twig'); 227 $template->renderBlock('unknown', []); 228 $template->displayBlock('unknown', []); 229 } 230 231 public function testGetAttributeOnArrayWithConfusableKey() 232 { 233 $template = new Twig_TemplateTest(new Environment($this->getMockBuilder('\Twig\Loader\LoaderInterface')->getMock())); 234 235 $array = ['Zero', 'One', -1 => 'MinusOne', '' => 'EmptyString', '1.5' => 'FloatButString', '01' => 'IntegerButStringWithLeadingZeros']; 236 237 $this->assertSame('Zero', $array[false]); 238 $this->assertSame('One', $array[true]); 239 $this->assertSame('One', $array[1.5]); 240 $this->assertSame('One', $array['1']); 241 $this->assertSame('MinusOne', $array[-1.5]); 242 $this->assertSame('FloatButString', $array['1.5']); 243 $this->assertSame('IntegerButStringWithLeadingZeros', $array['01']); 244 $this->assertSame('EmptyString', $array[null]); 245 246 $this->assertSame('Zero', $template->getAttribute($array, false), 'false is treated as 0 when accessing an array (equals PHP behavior)'); 247 $this->assertSame('One', $template->getAttribute($array, true), 'true is treated as 1 when accessing an array (equals PHP behavior)'); 248 $this->assertSame('One', $template->getAttribute($array, 1.5), 'float is casted to int when accessing an array (equals PHP behavior)'); 249 $this->assertSame('One', $template->getAttribute($array, '1'), '"1" is treated as integer 1 when accessing an array (equals PHP behavior)'); 250 $this->assertSame('MinusOne', $template->getAttribute($array, -1.5), 'negative float is casted to int when accessing an array (equals PHP behavior)'); 251 $this->assertSame('FloatButString', $template->getAttribute($array, '1.5'), '"1.5" is treated as-is when accessing an array (equals PHP behavior)'); 252 $this->assertSame('IntegerButStringWithLeadingZeros', $template->getAttribute($array, '01'), '"01" is treated as-is when accessing an array (equals PHP behavior)'); 253 $this->assertSame('EmptyString', $template->getAttribute($array, null), 'null is treated as "" when accessing an array (equals PHP behavior)'); 254 } 255 256 /** 257 * @dataProvider getGetAttributeTests 258 */ 259 public function testGetAttribute($defined, $value, $object, $item, $arguments, $type) 260 { 261 $template = new Twig_TemplateTest(new Environment($this->getMockBuilder('\Twig\Loader\LoaderInterface')->getMock())); 262 263 $this->assertEquals($value, $template->getAttribute($object, $item, $arguments, $type)); 264 } 265 266 /** 267 * @dataProvider getGetAttributeTests 268 */ 269 public function testGetAttributeStrict($defined, $value, $object, $item, $arguments, $type, $exceptionMessage = null) 270 { 271 $template = new Twig_TemplateTest(new Environment($this->getMockBuilder('\Twig\Loader\LoaderInterface')->getMock(), ['strict_variables' => true])); 272 273 if ($defined) { 274 $this->assertEquals($value, $template->getAttribute($object, $item, $arguments, $type)); 275 } else { 276 if (method_exists($this, 'expectException')) { 277 $this->expectException('\Twig\Error\RuntimeError'); 278 if (null !== $exceptionMessage) { 279 $this->expectExceptionMessage($exceptionMessage); 280 } 281 } else { 282 $this->setExpectedException('\Twig\Error\RuntimeError', $exceptionMessage); 283 } 284 $this->assertEquals($value, $template->getAttribute($object, $item, $arguments, $type)); 285 } 286 } 287 288 /** 289 * @dataProvider getGetAttributeTests 290 */ 291 public function testGetAttributeDefined($defined, $value, $object, $item, $arguments, $type) 292 { 293 $template = new Twig_TemplateTest(new Environment($this->getMockBuilder('\Twig\Loader\LoaderInterface')->getMock())); 294 295 $this->assertEquals($defined, $template->getAttribute($object, $item, $arguments, $type, true)); 296 } 297 298 /** 299 * @dataProvider getGetAttributeTests 300 */ 301 public function testGetAttributeDefinedStrict($defined, $value, $object, $item, $arguments, $type) 302 { 303 $template = new Twig_TemplateTest(new Environment($this->getMockBuilder('\Twig\Loader\LoaderInterface')->getMock(), ['strict_variables' => true])); 304 305 $this->assertEquals($defined, $template->getAttribute($object, $item, $arguments, $type, true)); 306 } 307 308 public function testGetAttributeCallExceptions() 309 { 310 $template = new Twig_TemplateTest(new Environment($this->getMockBuilder('\Twig\Loader\LoaderInterface')->getMock())); 311 312 $object = new Twig_TemplateMagicMethodExceptionObject(); 313 314 $this->assertNull($template->getAttribute($object, 'foo')); 315 } 316 317 public function getGetAttributeTests() 318 { 319 $array = [ 320 'defined' => 'defined', 321 'zero' => 0, 322 'null' => null, 323 '1' => 1, 324 'bar' => true, 325 'baz' => 'baz', 326 '09' => '09', 327 '+4' => '+4', 328 ]; 329 330 $objectArray = new Twig_TemplateArrayAccessObject(); 331 $arrayObject = new \ArrayObject($array); 332 $stdObject = (object) $array; 333 $magicPropertyObject = new Twig_TemplateMagicPropertyObject(); 334 $propertyObject = new Twig_TemplatePropertyObject(); 335 $propertyObject1 = new Twig_TemplatePropertyObjectAndIterator(); 336 $propertyObject2 = new Twig_TemplatePropertyObjectAndArrayAccess(); 337 $propertyObject3 = new Twig_TemplatePropertyObjectDefinedWithUndefinedValue(); 338 $methodObject = new Twig_TemplateMethodObject(); 339 $magicMethodObject = new Twig_TemplateMagicMethodObject(); 340 341 $anyType = Template::ANY_CALL; 342 $methodType = Template::METHOD_CALL; 343 $arrayType = Template::ARRAY_CALL; 344 345 $basicTests = [ 346 // array(defined, value, property to fetch) 347 [true, 'defined', 'defined'], 348 [false, null, 'undefined'], 349 [false, null, 'protected'], 350 [true, 0, 'zero'], 351 [true, 1, 1], 352 [true, 1, 1.0], 353 [true, null, 'null'], 354 [true, true, 'bar'], 355 [true, 'baz', 'baz'], 356 [true, '09', '09'], 357 [true, '+4', '+4'], 358 ]; 359 $testObjects = [ 360 // array(object, type of fetch) 361 [$array, $arrayType], 362 [$objectArray, $arrayType], 363 [$arrayObject, $anyType], 364 [$stdObject, $anyType], 365 [$magicPropertyObject, $anyType], 366 [$methodObject, $methodType], 367 [$methodObject, $anyType], 368 [$propertyObject, $anyType], 369 [$propertyObject1, $anyType], 370 [$propertyObject2, $anyType], 371 ]; 372 373 $tests = []; 374 foreach ($testObjects as $testObject) { 375 foreach ($basicTests as $test) { 376 // properties cannot be numbers 377 if (($testObject[0] instanceof \stdClass || $testObject[0] instanceof Twig_TemplatePropertyObject) && is_numeric($test[2])) { 378 continue; 379 } 380 381 if ('+4' === $test[2] && $methodObject === $testObject[0]) { 382 continue; 383 } 384 385 $tests[] = [$test[0], $test[1], $testObject[0], $test[2], [], $testObject[1]]; 386 } 387 } 388 389 // additional properties tests 390 $tests = array_merge($tests, [ 391 [true, null, $propertyObject3, 'foo', [], $anyType], 392 ]); 393 394 // additional method tests 395 $tests = array_merge($tests, [ 396 [true, 'defined', $methodObject, 'defined', [], $methodType], 397 [true, 'defined', $methodObject, 'DEFINED', [], $methodType], 398 [true, 'defined', $methodObject, 'getDefined', [], $methodType], 399 [true, 'defined', $methodObject, 'GETDEFINED', [], $methodType], 400 [true, 'static', $methodObject, 'static', [], $methodType], 401 [true, 'static', $methodObject, 'getStatic', [], $methodType], 402 403 [true, '__call_undefined', $magicMethodObject, 'undefined', [], $methodType], 404 [true, '__call_UNDEFINED', $magicMethodObject, 'UNDEFINED', [], $methodType], 405 ]); 406 407 // add the same tests for the any type 408 foreach ($tests as $test) { 409 if ($anyType !== $test[5]) { 410 $test[5] = $anyType; 411 $tests[] = $test; 412 } 413 } 414 415 $methodAndPropObject = new Twig_TemplateMethodAndPropObject(); 416 417 // additional method tests 418 $tests = array_merge($tests, [ 419 [true, 'a', $methodAndPropObject, 'a', [], $anyType], 420 [true, 'a', $methodAndPropObject, 'a', [], $methodType], 421 [false, null, $methodAndPropObject, 'a', [], $arrayType], 422 423 [true, 'b_prop', $methodAndPropObject, 'b', [], $anyType], 424 [true, 'b', $methodAndPropObject, 'B', [], $anyType], 425 [true, 'b', $methodAndPropObject, 'b', [], $methodType], 426 [true, 'b', $methodAndPropObject, 'B', [], $methodType], 427 [false, null, $methodAndPropObject, 'b', [], $arrayType], 428 429 [false, null, $methodAndPropObject, 'c', [], $anyType], 430 [false, null, $methodAndPropObject, 'c', [], $methodType], 431 [false, null, $methodAndPropObject, 'c', [], $arrayType], 432 ]); 433 434 $arrayAccess = new Twig_TemplateArrayAccess(); 435 $tests = array_merge($tests, [ 436 [true, ['foo' => 'bar'], $arrayAccess, 'vars', [], $anyType], 437 ]); 438 439 // tests when input is not an array or object 440 $tests = array_merge($tests, [ 441 [false, null, 42, 'a', [], $anyType, 'Impossible to access an attribute ("a") on a integer variable ("42") in "index.twig".'], 442 [false, null, 'string', 'a', [], $anyType, 'Impossible to access an attribute ("a") on a string variable ("string") in "index.twig".'], 443 [false, null, [], 'a', [], $anyType, 'Key "a" does not exist as the array is empty in "index.twig".'], 444 ]); 445 446 return $tests; 447 } 448 449 /** 450 * @expectedException \Twig\Error\RuntimeError 451 */ 452 public function testGetIsMethods() 453 { 454 $getIsObject = new Twig_TemplateGetIsMethods(); 455 $template = new Twig_TemplateTest(new Environment($this->getMockBuilder('\Twig\Loader\LoaderInterface')->getMock(), ['strict_variables' => true])); 456 // first time should not create a cache for "get" 457 $this->assertNull($template->getAttribute($getIsObject, 'get')); 458 // 0 should be in the method cache now, so this should fail 459 $this->assertNull($template->getAttribute($getIsObject, 0)); 460 } 461} 462 463class Twig_TemplateTest extends Template 464{ 465 private $name; 466 467 public function __construct(Environment $env, $name = 'index.twig') 468 { 469 parent::__construct($env); 470 self::$cache = []; 471 $this->name = $name; 472 } 473 474 public function getZero() 475 { 476 return 0; 477 } 478 479 public function getEmpty() 480 { 481 return ''; 482 } 483 484 public function getString() 485 { 486 return 'some_string'; 487 } 488 489 public function getTrue() 490 { 491 return true; 492 } 493 494 public function getTemplateName() 495 { 496 return $this->name; 497 } 498 499 public function getDebugInfo() 500 { 501 return []; 502 } 503 504 protected function doGetParent(array $context) 505 { 506 return false; 507 } 508 509 protected function doDisplay(array $context, array $blocks = []) 510 { 511 } 512 513 public function getAttribute($object, $item, array $arguments = [], $type = Template::ANY_CALL, $isDefinedTest = false, $ignoreStrictCheck = false) 514 { 515 if (\function_exists('twig_template_get_attributes')) { 516 return twig_template_get_attributes($this, $object, $item, $arguments, $type, $isDefinedTest, $ignoreStrictCheck); 517 } else { 518 return parent::getAttribute($object, $item, $arguments, $type, $isDefinedTest, $ignoreStrictCheck); 519 } 520 } 521 522 public function block_name($context, array $blocks = []) 523 { 524 } 525} 526 527class Twig_TemplateArrayAccessObject implements \ArrayAccess 528{ 529 protected $protected = 'protected'; 530 531 public $attributes = [ 532 'defined' => 'defined', 533 'zero' => 0, 534 'null' => null, 535 '1' => 1, 536 'bar' => true, 537 'baz' => 'baz', 538 '09' => '09', 539 '+4' => '+4', 540 ]; 541 542 public function offsetExists($name) 543 { 544 return \array_key_exists($name, $this->attributes); 545 } 546 547 public function offsetGet($name) 548 { 549 return \array_key_exists($name, $this->attributes) ? $this->attributes[$name] : null; 550 } 551 552 public function offsetSet($name, $value) 553 { 554 } 555 556 public function offsetUnset($name) 557 { 558 } 559} 560 561class Twig_TemplateMagicPropertyObject 562{ 563 public $defined = 'defined'; 564 565 public $attributes = [ 566 'zero' => 0, 567 'null' => null, 568 '1' => 1, 569 'bar' => true, 570 'baz' => 'baz', 571 '09' => '09', 572 '+4' => '+4', 573 ]; 574 575 protected $protected = 'protected'; 576 577 public function __isset($name) 578 { 579 return \array_key_exists($name, $this->attributes); 580 } 581 582 public function __get($name) 583 { 584 return \array_key_exists($name, $this->attributes) ? $this->attributes[$name] : null; 585 } 586} 587 588class Twig_TemplateMagicPropertyObjectWithException 589{ 590 public function __isset($key) 591 { 592 throw new \Exception('Hey! Don\'t try to isset me!'); 593 } 594} 595 596class Twig_TemplatePropertyObject 597{ 598 public $defined = 'defined'; 599 public $zero = 0; 600 public $null = null; 601 public $bar = true; 602 public $baz = 'baz'; 603 604 protected $protected = 'protected'; 605} 606 607class Twig_TemplatePropertyObjectAndIterator extends Twig_TemplatePropertyObject implements \IteratorAggregate 608{ 609 public function getIterator() 610 { 611 return new \ArrayIterator(['foo', 'bar']); 612 } 613} 614 615class Twig_TemplatePropertyObjectAndArrayAccess extends Twig_TemplatePropertyObject implements \ArrayAccess 616{ 617 private $data = [ 618 'defined' => 'defined', 619 'zero' => 0, 620 'null' => null, 621 'bar' => true, 622 'foo' => true, 623 'baz' => 'baz', 624 'baf' => 'baf', 625 ]; 626 627 public function offsetExists($offset) 628 { 629 return \array_key_exists($offset, $this->data); 630 } 631 632 public function offsetGet($offset) 633 { 634 return $this->offsetExists($offset) ? $this->data[$offset] : 'n/a'; 635 } 636 637 public function offsetSet($offset, $value) 638 { 639 } 640 641 public function offsetUnset($offset) 642 { 643 } 644} 645 646class Twig_TemplatePropertyObjectDefinedWithUndefinedValue 647{ 648 public $foo; 649 650 public function __construct() 651 { 652 $this->foo = @$notExist; 653 } 654} 655 656class Twig_TemplateMethodObject 657{ 658 public function getDefined() 659 { 660 return 'defined'; 661 } 662 663 public function get1() 664 { 665 return 1; 666 } 667 668 public function get09() 669 { 670 return '09'; 671 } 672 673 public function getZero() 674 { 675 return 0; 676 } 677 678 public function getNull() 679 { 680 } 681 682 public function isBar() 683 { 684 return true; 685 } 686 687 public function isBaz() 688 { 689 return 'should never be returned'; 690 } 691 692 public function getBaz() 693 { 694 return 'baz'; 695 } 696 697 protected function getProtected() 698 { 699 return 'protected'; 700 } 701 702 public static function getStatic() 703 { 704 return 'static'; 705 } 706} 707 708class Twig_TemplateGetIsMethods 709{ 710 public function get() 711 { 712 } 713 714 public function is() 715 { 716 } 717} 718 719class Twig_TemplateMethodAndPropObject 720{ 721 private $a = 'a_prop'; 722 723 public function getA() 724 { 725 return 'a'; 726 } 727 728 public $b = 'b_prop'; 729 730 public function getB() 731 { 732 return 'b'; 733 } 734 735 private $c = 'c_prop'; 736 737 private function getC() 738 { 739 return 'c'; 740 } 741} 742 743class Twig_TemplateArrayAccess implements \ArrayAccess 744{ 745 public $vars = [ 746 'foo' => 'bar', 747 ]; 748 private $children = []; 749 750 public function offsetExists($offset) 751 { 752 return \array_key_exists($offset, $this->children); 753 } 754 755 public function offsetGet($offset) 756 { 757 return $this->children[$offset]; 758 } 759 760 public function offsetSet($offset, $value) 761 { 762 $this->children[$offset] = $value; 763 } 764 765 public function offsetUnset($offset) 766 { 767 unset($this->children[$offset]); 768 } 769} 770 771class Twig_TemplateMagicMethodObject 772{ 773 public function __call($method, $arguments) 774 { 775 return '__call_'.$method; 776 } 777} 778 779class Twig_TemplateMagicMethodExceptionObject 780{ 781 public function __call($method, $arguments) 782 { 783 throw new \BadMethodCallException(sprintf('Unknown method "%s".', $method)); 784 } 785} 786 787class CExtDisablingNodeVisitor implements NodeVisitorInterface 788{ 789 public function enterNode(Twig_NodeInterface $node, Environment $env) 790 { 791 if ($node instanceof GetAttrExpression) { 792 $node->setAttribute('disable_c_ext', true); 793 } 794 795 return $node; 796 } 797 798 public function leaveNode(Twig_NodeInterface $node, Environment $env) 799 { 800 return $node; 801 } 802 803 public function getPriority() 804 { 805 return 0; 806 } 807} 808 809// to be removed in 2.0 810interface Twig_TemplateTestLoaderInterface extends LoaderInterface, SourceContextLoaderInterface 811{ 812} 813