mirror of
https://github.com/twigphp/Twig.git
synced 2026-09-20 14:36:57 +00:00
ef7ade8ee3
* 3.x: (30 commits)
Make {} optional for the types tag
Remove the note about types mapping argument
Add a note on the types locality
Fix CS
Fix typos in the docs
Use spl_object_id() instead of spl_object_hash()
Add `LastModifiedExtensionInterface` and implementation in `AbstractExtension` to track modification of runtime classes
Bump version
Optimize NameExpression compilation
Fix typo in enum twig doc functions
Remove dead method in ModuleNode
Bump version
Prepare the 3.18.0 release
Add support for `Twig\Markup`
Fix unary operator precedence change
Replace Twigfiddle by the new Twig playground
Fix MacroNode $name argument can be overridden
Ignore exceptions from undefined handlers when using the guard tag
[Doc] Update the docs about attribute() function
Fix doc
...
740 lines
24 KiB
PHP
740 lines
24 KiB
PHP
<?php
|
|
|
|
namespace Twig\Tests;
|
|
|
|
/*
|
|
* This file is part of Twig.
|
|
*
|
|
* (c) Fabien Potencier
|
|
*
|
|
* For the full copyright and license information, please view the LICENSE
|
|
* file that was distributed with this source code.
|
|
*/
|
|
|
|
use PHPUnit\Framework\Attributes\DataProvider;
|
|
use PHPUnit\Framework\TestCase;
|
|
use Twig\Environment;
|
|
use Twig\Error\RuntimeError;
|
|
use Twig\Extension\CoreExtension;
|
|
use Twig\Extension\SandboxExtension;
|
|
use Twig\Loader\ArrayLoader;
|
|
use Twig\Sandbox\SecurityError;
|
|
use Twig\Sandbox\SecurityPolicy;
|
|
use Twig\Source;
|
|
use Twig\Template;
|
|
use Twig\TemplateWrapper;
|
|
|
|
class TemplateTest extends TestCase
|
|
{
|
|
public function testDisplayBlocksAcceptTemplateOnlyAsBlocks()
|
|
{
|
|
$twig = new Environment(new ArrayLoader());
|
|
$template = new TemplateForTest($twig);
|
|
|
|
$this->expectException(\LogicException::class);
|
|
$template->renderBlock('foo', [], ['foo' => [new \stdClass(), 'foo']]);
|
|
}
|
|
|
|
#[DataProvider('getAttributeExceptions')]
|
|
public function testGetAttributeExceptions($template, $message)
|
|
{
|
|
$templates = ['index' => $template];
|
|
$env = new Environment(new ArrayLoader($templates), ['strict_variables' => true]);
|
|
$template = $env->load('index');
|
|
|
|
$context = [
|
|
'string' => 'foo',
|
|
'null' => null,
|
|
'empty_array' => [],
|
|
'array' => ['foo' => 'foo'],
|
|
'array_access' => new TemplateArrayAccessObject(),
|
|
'magic_exception' => new TemplateMagicPropertyObjectWithException(),
|
|
'object' => new \stdClass(),
|
|
];
|
|
|
|
try {
|
|
$template->render($context);
|
|
$this->fail('Accessing an invalid attribute should throw an exception.');
|
|
} catch (RuntimeError $e) {
|
|
$this->assertSame(\sprintf($message, 'index'), $e->getMessage());
|
|
}
|
|
}
|
|
|
|
public static function getAttributeExceptions()
|
|
{
|
|
return [
|
|
['{{ string["a"] }}', 'Impossible to access a key ("a") on a string variable ("foo") in "%s" at line 1.'],
|
|
['{{ null["a"] }}', 'Impossible to access a key ("a") on a null variable in "%s" at line 1.'],
|
|
['{{ empty_array["a"] }}', 'Key "a" does not exist as the sequence/mapping is empty in "%s" at line 1.'],
|
|
['{{ array["a"] }}', 'Key "a" for sequence/mapping with keys "foo" does not exist in "%s" at line 1.'],
|
|
['{{ array_access["a"] }}', 'Key "a" in object with ArrayAccess of class "Twig\Tests\TemplateArrayAccessObject" does not exist in "%s" at line 1.'],
|
|
['{{ string.a }}', 'Impossible to access an attribute ("a") on a string variable ("foo") in "%s" at line 1.'],
|
|
['{{ string.a() }}', 'Impossible to invoke a method ("a") on a string variable ("foo") in "%s" at line 1.'],
|
|
['{{ null.a }}', 'Impossible to access an attribute ("a") on a null variable in "%s" at line 1.'],
|
|
['{{ null.a() }}', 'Impossible to invoke a method ("a") on a null variable in "%s" at line 1.'],
|
|
['{{ array.a() }}', 'Impossible to invoke a method ("a") on a sequence/mapping in "%s" at line 1.'],
|
|
['{{ empty_array.a }}', 'Key "a" does not exist as the sequence/mapping is empty in "%s" at line 1.'],
|
|
['{{ array.a }}', 'Key "a" for sequence/mapping with keys "foo" does not exist in "%s" at line 1.'],
|
|
['{{ array.(-10) }}', 'Key "-10" for sequence/mapping with keys "foo" does not exist in "%s" at line 1.'],
|
|
['{{ array_access.a }}', 'Neither the property "a" nor one of the methods "a()", "geta()"/"isa()"/"hasa()" or "__call()" exist and have public access in class "Twig\Tests\TemplateArrayAccessObject" in "%s" at line 1.'],
|
|
['{% 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()"/"hasmissing_method()" or "__call()" exist and have public access in class "Twig\Tests\TemplateArrayAccessObject" in "%s" at line 1.'],
|
|
['{{ 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.'],
|
|
['{{ 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.'],
|
|
];
|
|
}
|
|
|
|
#[DataProvider('getGetAttributeWithSandbox')]
|
|
public function testGetAttributeWithSandbox($object, $item, $allowed)
|
|
{
|
|
$twig = new Environment(new ArrayLoader());
|
|
$policy = new SecurityPolicy([], [], [/* method */], [/* prop */], []);
|
|
$twig->addExtension(new SandboxExtension($policy, !$allowed));
|
|
$template = new TemplateForTest($twig);
|
|
|
|
try {
|
|
CoreExtension::getAttribute($twig, $template->getSourceContext(), $object, $item, [], 'any', false, false, true);
|
|
|
|
if (!$allowed) {
|
|
$this->fail();
|
|
} else {
|
|
$this->addToAssertionCount(1);
|
|
}
|
|
} catch (SecurityError $e) {
|
|
if ($allowed) {
|
|
$this->fail();
|
|
} else {
|
|
$this->addToAssertionCount(1);
|
|
}
|
|
|
|
$this->assertStringContainsString('is not allowed', $e->getMessage());
|
|
}
|
|
}
|
|
|
|
public static function getGetAttributeWithSandbox()
|
|
{
|
|
return [
|
|
[new TemplatePropertyObject(), 'defined', false],
|
|
[new TemplatePropertyObject(), 'defined', true],
|
|
[new TemplateMethodObject(), 'defined', false],
|
|
[new TemplateMethodObject(), 'defined', true],
|
|
];
|
|
}
|
|
|
|
#[DataProvider('getRenderTemplateWithoutOutputData')]
|
|
public function testRenderTemplateWithoutOutput(string $template)
|
|
{
|
|
$twig = new Environment(new ArrayLoader(['index' => $template]));
|
|
$this->assertSame('', $twig->render('index'));
|
|
}
|
|
|
|
public static function getRenderTemplateWithoutOutputData()
|
|
{
|
|
return [
|
|
[''],
|
|
['{% for var in [] %}{% endfor %}'],
|
|
['{% if false %}{% endif %}'],
|
|
];
|
|
}
|
|
|
|
public function testRenderBlockWithUndefinedBlock()
|
|
{
|
|
$twig = new Environment(new ArrayLoader());
|
|
$template = new TemplateForTest($twig, 'index.twig');
|
|
|
|
$this->expectException(RuntimeError::class);
|
|
$this->expectExceptionMessage('Block "unknown" on template "index.twig" does not exist in "index.twig".');
|
|
|
|
$template->renderBlock('unknown', []);
|
|
}
|
|
|
|
public function testRenderBlockWithUndefinedParentBlock()
|
|
{
|
|
$twig = new Environment(new ArrayLoader());
|
|
$template = new TemplateForTest($twig, 'parent.twig');
|
|
|
|
$this->expectException(RuntimeError::class);
|
|
$this->expectExceptionMessage('Block "foo" should not call parent() in "index.twig" as the block does not exist in the parent template "parent.twig"');
|
|
$template->renderBlock('foo', [], ['foo' => [new TemplateForTest($twig, 'index.twig'), 'block_foo']], false);
|
|
}
|
|
|
|
public function testGetAttributeOnArrayWithConfusableKey()
|
|
{
|
|
$twig = new Environment(new ArrayLoader());
|
|
$template = new TemplateForTest($twig);
|
|
|
|
$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']);
|
|
$this->assertSame('FloatButString', $array['1.5']);
|
|
$this->assertSame('IntegerButStringWithLeadingZeros', $array['01']);
|
|
$this->assertSame('EmptyString', $array[null]);
|
|
|
|
$this->assertSame('Zero', CoreExtension::getAttribute($twig, $template->getSourceContext(), $array, false), 'false is treated as 0 when accessing a sequence/mapping (equals PHP behavior)');
|
|
$this->assertSame('One', CoreExtension::getAttribute($twig, $template->getSourceContext(), $array, true), 'true is treated as 1 when accessing a sequence/mapping (equals PHP behavior)');
|
|
$this->assertSame('One', CoreExtension::getAttribute($twig, $template->getSourceContext(), $array, 1.5), 'float is casted to int when accessing a sequence/mapping (equals PHP behavior)');
|
|
$this->assertSame('One', CoreExtension::getAttribute($twig, $template->getSourceContext(), $array, '1'), '"1" is treated as integer 1 when accessing a sequence/mapping (equals PHP behavior)');
|
|
$this->assertSame('MinusOne', CoreExtension::getAttribute($twig, $template->getSourceContext(), $array, -1.5), 'negative float is casted to int when accessing a sequence/mapping (equals PHP behavior)');
|
|
$this->assertSame('FloatButString', CoreExtension::getAttribute($twig, $template->getSourceContext(), $array, '1.5'), '"1.5" is treated as-is when accessing a sequence/mapping (equals PHP behavior)');
|
|
$this->assertSame('IntegerButStringWithLeadingZeros', CoreExtension::getAttribute($twig, $template->getSourceContext(), $array, '01'), '"01" is treated as-is when accessing a sequence/mapping (equals PHP behavior)');
|
|
$this->assertSame('EmptyString', CoreExtension::getAttribute($twig, $template->getSourceContext(), $array, null), 'null is treated as "" when accessing a sequence/mapping (equals PHP behavior)');
|
|
}
|
|
|
|
#[DataProvider('getGetAttributeTests')]
|
|
public function testGetAttribute($defined, $value, $object, $item, $arguments, $type)
|
|
{
|
|
$twig = new Environment(new ArrayLoader());
|
|
$template = new TemplateForTest($twig);
|
|
|
|
$this->assertEquals($value, CoreExtension::getAttribute($twig, $template->getSourceContext(), $object, $item, $arguments, $type));
|
|
}
|
|
|
|
#[DataProvider('getGetAttributeTests')]
|
|
public function testGetAttributeStrict($defined, $value, $object, $item, $arguments, $type, $exceptionMessage = null)
|
|
{
|
|
$twig = new Environment(new ArrayLoader(), ['strict_variables' => true]);
|
|
$template = new TemplateForTest($twig);
|
|
|
|
if ($defined) {
|
|
$this->assertEquals($value, CoreExtension::getAttribute($twig, $template->getSourceContext(), $object, $item, $arguments, $type));
|
|
} else {
|
|
$this->expectException(RuntimeError::class);
|
|
if (null !== $exceptionMessage) {
|
|
$this->expectExceptionMessage($exceptionMessage);
|
|
}
|
|
$this->assertEquals($value, CoreExtension::getAttribute($twig, $template->getSourceContext(), $object, $item, $arguments, $type));
|
|
}
|
|
}
|
|
|
|
#[DataProvider('getGetAttributeTests')]
|
|
public function testGetAttributeDefined($defined, $value, $object, $item, $arguments, $type)
|
|
{
|
|
$twig = new Environment(new ArrayLoader());
|
|
$template = new TemplateForTest($twig);
|
|
|
|
$this->assertEquals($defined, CoreExtension::getAttribute($twig, $template->getSourceContext(), $object, $item, $arguments, $type, true));
|
|
}
|
|
|
|
#[DataProvider('getGetAttributeTests')]
|
|
public function testGetAttributeDefinedStrict($defined, $value, $object, $item, $arguments, $type)
|
|
{
|
|
$twig = new Environment(new ArrayLoader(), ['strict_variables' => true]);
|
|
$template = new TemplateForTest($twig);
|
|
|
|
$this->assertEquals($defined, CoreExtension::getAttribute($twig, $template->getSourceContext(), $object, $item, $arguments, $type, true));
|
|
}
|
|
|
|
public function testGetAttributeCallExceptions()
|
|
{
|
|
$twig = new Environment(new ArrayLoader());
|
|
$template = new TemplateForTest($twig);
|
|
|
|
$object = new TemplateMagicMethodExceptionObject();
|
|
|
|
$this->assertNull(CoreExtension::getAttribute($twig, $template->getSourceContext(), $object, 'foo'));
|
|
}
|
|
|
|
public static function getGetAttributeTests()
|
|
{
|
|
$array = [
|
|
'defined' => 'defined',
|
|
'zero' => 0,
|
|
'null' => null,
|
|
'1' => 1,
|
|
'bar' => true,
|
|
'foo' => true,
|
|
'baz' => 'baz',
|
|
'baf' => 'baf',
|
|
'09' => '09',
|
|
'+4' => '+4',
|
|
];
|
|
|
|
$objectArray = new TemplateArrayAccessObject();
|
|
$arrayObject = new \ArrayObject($array);
|
|
$stdObject = (object) $array;
|
|
$magicPropertyObject = new TemplateMagicPropertyObject();
|
|
$propertyObject = new TemplatePropertyObject();
|
|
$propertyObject1 = new TemplatePropertyObjectAndIterator();
|
|
$propertyObject2 = new TemplatePropertyObjectAndArrayAccess();
|
|
$propertyObject3 = new TemplatePropertyObjectDefinedWithUndefinedValue();
|
|
$methodObject = new TemplateMethodObject();
|
|
$magicMethodObject = new TemplateMagicMethodObject();
|
|
|
|
$anyType = Template::ANY_CALL;
|
|
$methodType = Template::METHOD_CALL;
|
|
$arrayType = Template::ARRAY_CALL;
|
|
|
|
$basicTests = [
|
|
// array(defined, value, property to fetch)
|
|
[true, 'defined', 'defined'],
|
|
[false, null, 'undefined'],
|
|
[false, null, 'protected'],
|
|
[true, 0, 'zero'],
|
|
[true, 1, 1],
|
|
[true, 1, 1.0],
|
|
[true, null, 'null'],
|
|
[true, true, 'bar'],
|
|
[true, true, 'foo'],
|
|
[true, 'baz', 'baz'],
|
|
[true, 'baf', 'baf'],
|
|
[true, '09', '09'],
|
|
[true, '+4', '+4'],
|
|
];
|
|
$testObjects = [
|
|
// array(object, type of fetch)
|
|
[$array, $arrayType],
|
|
[$objectArray, $arrayType],
|
|
[$arrayObject, $anyType],
|
|
[$stdObject, $anyType],
|
|
[$magicPropertyObject, $anyType],
|
|
[$methodObject, $methodType],
|
|
[$methodObject, $anyType],
|
|
[$propertyObject, $anyType],
|
|
[$propertyObject1, $anyType],
|
|
[$propertyObject2, $anyType],
|
|
];
|
|
|
|
$tests = [];
|
|
foreach ($testObjects as $testObject) {
|
|
foreach ($basicTests as $test) {
|
|
// properties cannot be numbers
|
|
if (($testObject[0] instanceof \stdClass || $testObject[0] instanceof TemplatePropertyObject) && is_numeric($test[2])) {
|
|
continue;
|
|
}
|
|
|
|
if ('+4' === $test[2] && $methodObject === $testObject[0]) {
|
|
continue;
|
|
}
|
|
|
|
$tests[] = [$test[0], $test[1], $testObject[0], $test[2], [], $testObject[1]];
|
|
}
|
|
}
|
|
|
|
// additional properties tests
|
|
$tests = array_merge($tests, [
|
|
[true, null, $propertyObject3, 'foo', [], $anyType],
|
|
]);
|
|
|
|
// additional method tests
|
|
$tests = array_merge($tests, [
|
|
[true, 'defined', $methodObject, 'defined', [], $methodType],
|
|
[true, 'defined', $methodObject, 'DEFINED', [], $methodType],
|
|
[true, 'defined', $methodObject, 'getDefined', [], $methodType],
|
|
[true, 'defined', $methodObject, 'GETDEFINED', [], $methodType],
|
|
[true, 'static', $methodObject, 'static', [], $methodType],
|
|
[true, 'static', $methodObject, 'getStatic', [], $methodType],
|
|
|
|
[true, '__call_undefined', $magicMethodObject, 'undefined', [], $methodType],
|
|
[true, '__call_UNDEFINED', $magicMethodObject, 'UNDEFINED', [], $methodType],
|
|
]);
|
|
|
|
// add the same tests for the any type
|
|
foreach ($tests as $test) {
|
|
if ($anyType !== $test[5]) {
|
|
$test[5] = $anyType;
|
|
$tests[] = $test;
|
|
}
|
|
}
|
|
|
|
$methodAndPropObject = new TemplateMethodAndPropObject();
|
|
|
|
// additional method tests
|
|
$tests = array_merge($tests, [
|
|
[true, 'a', $methodAndPropObject, 'a', [], $anyType],
|
|
[true, 'a', $methodAndPropObject, 'a', [], $methodType],
|
|
[false, null, $methodAndPropObject, 'a', [], $arrayType],
|
|
|
|
[true, 'b_prop', $methodAndPropObject, 'b', [], $anyType],
|
|
[true, 'b', $methodAndPropObject, 'B', [], $anyType],
|
|
[true, 'b', $methodAndPropObject, 'b', [], $methodType],
|
|
[true, 'b', $methodAndPropObject, 'B', [], $methodType],
|
|
[false, null, $methodAndPropObject, 'b', [], $arrayType],
|
|
|
|
[false, null, $methodAndPropObject, 'c', [], $anyType],
|
|
[false, null, $methodAndPropObject, 'c', [], $methodType],
|
|
[false, null, $methodAndPropObject, 'c', [], $arrayType],
|
|
]);
|
|
|
|
$arrayAccess = new TemplateArrayAccess();
|
|
$tests = array_merge($tests, [
|
|
[true, ['foo' => 'bar'], $arrayAccess, 'vars', [], $anyType],
|
|
]);
|
|
|
|
// test for Closure::__invoke()
|
|
$tests[] = [true, 'closure called', fn (): string => 'closure called', '__invoke', [], $anyType];
|
|
$tests[] = [true, 'closure called', fn (): string => 'closure called', '__invoke', [], $methodType];
|
|
|
|
// tests when input is not an array or object
|
|
$tests = array_merge($tests, [
|
|
[false, null, 42, 'a', [], $anyType, 'Impossible to access an attribute ("a") on a int variable ("42") in "index.twig".'],
|
|
[false, null, 'string', 'a', [], $anyType, 'Impossible to access an attribute ("a") on a string variable ("string") in "index.twig".'],
|
|
[false, null, [], 'a', [], $anyType, 'Key "a" does not exist as the sequence/mapping is empty in "index.twig".'],
|
|
]);
|
|
|
|
return $tests;
|
|
}
|
|
|
|
public function testGetIsMethods()
|
|
{
|
|
$twig = new Environment(new ArrayLoader());
|
|
|
|
$getIsObject = new TemplateGetIsMethods();
|
|
$template = new TemplateForTest($twig, 'index.twig');
|
|
// first time should not create a cache for "get"
|
|
$this->assertNull(CoreExtension::getAttribute($twig, $template->getSourceContext(), $getIsObject, 'get'));
|
|
// 0 should be in the method cache now, so this should fail
|
|
$this->assertNull(CoreExtension::getAttribute($twig, $template->getSourceContext(), $getIsObject, 0));
|
|
}
|
|
}
|
|
|
|
class TemplateForTest extends Template
|
|
{
|
|
private $name;
|
|
|
|
public function __construct(Environment $env, $name = 'index.twig')
|
|
{
|
|
parent::__construct($env);
|
|
$this->name = $name;
|
|
}
|
|
|
|
public function getZero()
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
public function getEmpty()
|
|
{
|
|
return '';
|
|
}
|
|
|
|
public function getString()
|
|
{
|
|
return 'some_string';
|
|
}
|
|
|
|
public function getTrue()
|
|
{
|
|
return true;
|
|
}
|
|
|
|
public function getTemplateName(): string
|
|
{
|
|
return $this->name;
|
|
}
|
|
|
|
public function getDebugInfo(): array
|
|
{
|
|
return [];
|
|
}
|
|
|
|
public function getSourceContext(): Source
|
|
{
|
|
return new Source('', $this->getTemplateName());
|
|
}
|
|
|
|
protected function doGetParent(array $context): bool|string|Template|TemplateWrapper
|
|
{
|
|
return false;
|
|
}
|
|
|
|
protected function doDisplay(array $context, array $blocks = []): iterable
|
|
{
|
|
}
|
|
|
|
public function block_name($context, array $blocks = [])
|
|
{
|
|
}
|
|
}
|
|
|
|
class TemplateArrayAccessObject implements \ArrayAccess
|
|
{
|
|
protected $protected = 'protected';
|
|
|
|
public $attributes = [
|
|
'defined' => 'defined',
|
|
'zero' => 0,
|
|
'null' => null,
|
|
'1' => 1,
|
|
'bar' => true,
|
|
'foo' => true,
|
|
'baz' => 'baz',
|
|
'baf' => 'baf',
|
|
'09' => '09',
|
|
'+4' => '+4',
|
|
];
|
|
|
|
public function offsetExists($name): bool
|
|
{
|
|
return \array_key_exists($name, $this->attributes);
|
|
}
|
|
|
|
public function offsetGet($name): mixed
|
|
{
|
|
return \array_key_exists($name, $this->attributes) ? $this->attributes[$name] : null;
|
|
}
|
|
|
|
public function offsetSet($name, $value): void
|
|
{
|
|
}
|
|
|
|
public function offsetUnset($name): void
|
|
{
|
|
}
|
|
}
|
|
|
|
class TemplateMagicPropertyObject
|
|
{
|
|
public $defined = 'defined';
|
|
|
|
public $attributes = [
|
|
'zero' => 0,
|
|
'null' => null,
|
|
'1' => 1,
|
|
'bar' => true,
|
|
'foo' => true,
|
|
'baz' => 'baz',
|
|
'baf' => 'baf',
|
|
'09' => '09',
|
|
'+4' => '+4',
|
|
];
|
|
|
|
protected $protected = 'protected';
|
|
|
|
public function __isset($name): bool
|
|
{
|
|
return \array_key_exists($name, $this->attributes);
|
|
}
|
|
|
|
public function __get($name)
|
|
{
|
|
return \array_key_exists($name, $this->attributes) ? $this->attributes[$name] : null;
|
|
}
|
|
}
|
|
|
|
class TemplateMagicPropertyObjectWithException
|
|
{
|
|
public function __isset($key): bool
|
|
{
|
|
throw new \Exception('Hey! Don\'t try to isset me!');
|
|
}
|
|
}
|
|
|
|
class TemplatePropertyObject
|
|
{
|
|
public $defined = 'defined';
|
|
public $zero = 0;
|
|
public $null = null;
|
|
public $bar = true;
|
|
public $foo = true;
|
|
public $baz = 'baz';
|
|
public $baf = 'baf';
|
|
|
|
protected $protected = 'protected';
|
|
}
|
|
|
|
class TemplatePropertyObjectAndIterator extends TemplatePropertyObject implements \IteratorAggregate
|
|
{
|
|
public function getIterator(): \Traversable
|
|
{
|
|
return new \ArrayIterator(['foo', 'bar']);
|
|
}
|
|
}
|
|
|
|
class TemplatePropertyObjectAndArrayAccess extends TemplatePropertyObject implements \ArrayAccess
|
|
{
|
|
private $data = [
|
|
'defined' => 'defined',
|
|
'zero' => 0,
|
|
'null' => null,
|
|
'bar' => true,
|
|
'foo' => true,
|
|
'baz' => 'baz',
|
|
'baf' => 'baf',
|
|
];
|
|
|
|
public function offsetExists($offset): bool
|
|
{
|
|
return \array_key_exists($offset, $this->data);
|
|
}
|
|
|
|
public function offsetGet($offset): mixed
|
|
{
|
|
return $this->offsetExists($offset) ? $this->data[$offset] : 'n/a';
|
|
}
|
|
|
|
public function offsetSet($offset, $value): void
|
|
{
|
|
}
|
|
|
|
public function offsetUnset($offset): void
|
|
{
|
|
}
|
|
}
|
|
|
|
class TemplatePropertyObjectDefinedWithUndefinedValue
|
|
{
|
|
public $foo;
|
|
|
|
public function __construct()
|
|
{
|
|
$this->foo = @$notExist;
|
|
}
|
|
}
|
|
|
|
class TemplateMethodObject
|
|
{
|
|
public function getDefined()
|
|
{
|
|
return 'defined';
|
|
}
|
|
|
|
public function get1()
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
public function get09()
|
|
{
|
|
return '09';
|
|
}
|
|
|
|
public function getZero()
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
public function getNull()
|
|
{
|
|
}
|
|
|
|
public function isBar()
|
|
{
|
|
return true;
|
|
}
|
|
|
|
public function hasFoo()
|
|
{
|
|
return true;
|
|
}
|
|
|
|
public function hasBaz()
|
|
{
|
|
return 'should never be returned (has)';
|
|
}
|
|
|
|
public function isBaz()
|
|
{
|
|
return 'should never be returned (is)';
|
|
}
|
|
|
|
public function getBaz()
|
|
{
|
|
return 'Baz';
|
|
}
|
|
|
|
public function baz()
|
|
{
|
|
return 'baz';
|
|
}
|
|
|
|
public function hasBaf()
|
|
{
|
|
return 'should never be returned (has)';
|
|
}
|
|
|
|
public function isBaf()
|
|
{
|
|
return 'baf';
|
|
}
|
|
|
|
protected function getProtected()
|
|
{
|
|
return 'protected';
|
|
}
|
|
|
|
public static function getStatic()
|
|
{
|
|
return 'static';
|
|
}
|
|
}
|
|
|
|
class TemplateGetIsMethods
|
|
{
|
|
public function get()
|
|
{
|
|
}
|
|
|
|
public function is()
|
|
{
|
|
}
|
|
}
|
|
|
|
class TemplateMethodAndPropObject
|
|
{
|
|
private $a = 'a_prop';
|
|
|
|
public function getA()
|
|
{
|
|
return 'a';
|
|
}
|
|
|
|
public $b = 'b_prop';
|
|
|
|
public function getB()
|
|
{
|
|
return 'b';
|
|
}
|
|
|
|
private $c = 'c_prop';
|
|
|
|
private function getC()
|
|
{
|
|
return 'c';
|
|
}
|
|
}
|
|
|
|
class TemplateArrayAccess implements \ArrayAccess
|
|
{
|
|
public $vars = [
|
|
'foo' => 'bar',
|
|
];
|
|
private $children = [];
|
|
|
|
public function offsetExists($offset): bool
|
|
{
|
|
return \array_key_exists($offset, $this->children);
|
|
}
|
|
|
|
public function offsetGet($offset): mixed
|
|
{
|
|
return $this->children[$offset];
|
|
}
|
|
|
|
public function offsetSet($offset, $value): void
|
|
{
|
|
$this->children[$offset] = $value;
|
|
}
|
|
|
|
public function offsetUnset($offset): void
|
|
{
|
|
unset($this->children[$offset]);
|
|
}
|
|
}
|
|
|
|
class TemplateMagicMethodObject
|
|
{
|
|
public function __call($method, $arguments)
|
|
{
|
|
return '__call_'.$method;
|
|
}
|
|
}
|
|
|
|
class TemplateMagicMethodExceptionObject
|
|
{
|
|
public function __call($method, $arguments)
|
|
{
|
|
throw new \BadMethodCallException(\sprintf('Unknown method "%s".', $method));
|
|
}
|
|
}
|