Skip to content

Commit a0420da

Browse files
authored
Merge pull request #42 from tillschander/magic-getters
Resolve properties of classes with magic getters
2 parents ed804d1 + 06160fd commit a0420da

4 files changed

Lines changed: 135 additions & 2 deletions

File tree

src/Render/RenderContext.php

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -196,9 +196,10 @@ public function internalContextLookup(mixed $scope, int|string $key): mixed
196196
{
197197
try {
198198
$value = match (true) {
199-
is_array($scope) && array_key_exists($key, $scope) => $scope[$key],
200199
$scope instanceof Drop => $scope->{$key},
201-
is_object($scope) && property_exists($scope, (string) $key) => $scope->{$key},
200+
is_array($scope) && array_key_exists($key, $scope) => $scope[$key],
201+
is_object($scope) && $this->objectHasProperty($scope, (string) $key) => $scope->{$key},
202+
is_object($scope) && $this->objectHasStaticProperty($scope, (string) $key) => $scope::$$key,
202203
default => new MissingValue,
203204
};
204205
} catch (UndefinedDropMethodException) {
@@ -208,6 +209,33 @@ public function internalContextLookup(mixed $scope, int|string $key): mixed
208209
return $this->normalizeValue($value);
209210
}
210211

212+
protected function objectHasProperty(object $object, string $property): bool
213+
{
214+
try {
215+
// Check if the property is a public property
216+
if (array_key_exists($property, get_object_vars($object))) {
217+
return true;
218+
}
219+
220+
// Check if the property is accessible via __get() and __isset()
221+
if (isset($object->{$property})) {
222+
return true;
223+
}
224+
225+
return false;
226+
} catch (Throwable) {
227+
return false;
228+
}
229+
}
230+
231+
protected function objectHasStaticProperty(object $object, string $property): bool
232+
{
233+
$className = get_class($object);
234+
$staticProperties = get_class_vars($className);
235+
236+
return array_key_exists($property, $staticProperties);
237+
}
238+
211239
public function normalizeValue(mixed $value): mixed
212240
{
213241
if (is_object($value) && isset($this->sharedState->computedObjectsCache[$value])) {

tests/Integration/ContextTest.php

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
use Keepsuit\Liquid\Tests\Stubs\ContextSensitiveDrop;
1111
use Keepsuit\Liquid\Tests\Stubs\CounterDrop;
1212
use Keepsuit\Liquid\Tests\Stubs\HundredCents;
13+
use Keepsuit\Liquid\Tests\Stubs\MagicClass;
14+
use Keepsuit\Liquid\Tests\Stubs\SimpleClass;
1315

1416
test('variables', function (bool $strict) {
1517
$context = new RenderContext(options: new RenderContextOptions(strictVariables: $strict));
@@ -691,3 +693,45 @@ function () use (&$global) {
691693
'default' => false,
692694
'strict' => true,
693695
]);
696+
697+
test('internal context lookup with simple object', function (bool $strict) {
698+
$context = new RenderContext(options: new RenderContextOptions(strictVariables: $strict));
699+
$context->set('object', new SimpleClass);
700+
701+
expect($context->get('object.simpleProperty'))->toBe('foo');
702+
expect($context->get('object.nullProperty'))->toBe(null);
703+
expect($context->get('object.staticProperty'))->toBe('foo');
704+
expect($context->get('object.staticNullProperty'))->toBe(null);
705+
706+
if ($strict) {
707+
expect($context->get('object.protectedProperty'))->toBeInstanceOf(UndefinedVariable::class);
708+
expect($context->get('object.nonExistingProperty'))->toBeInstanceOf(UndefinedVariable::class);
709+
expect($context->get('object.simpleMethod'))->toBeInstanceOf(UndefinedVariable::class);
710+
} else {
711+
expect($context->get('object.protectedProperty'))->toBe(null);
712+
expect($context->get('object.nonExistingProperty'))->toBe(null);
713+
expect($context->get('object.simpleMethod'))->toBe(null);
714+
}
715+
})->with([
716+
'default' => false,
717+
'strict' => true,
718+
]);
719+
720+
test('internal context lookup magic object', function (bool $strict) {
721+
$context = new RenderContext(options: new RenderContextOptions(strictVariables: $strict));
722+
$context->set('object', new MagicClass);
723+
724+
expect($context->get('object.simpleProperty'))->toBe('foo');
725+
expect($context->get('object.nullProperty'))->toBe(null);
726+
727+
if ($strict) {
728+
expect($context->get('object.nonExistingProperty'))->toBeInstanceOf(UndefinedVariable::class);
729+
expect($context->get('object.simpleMethod'))->toBeInstanceOf(UndefinedVariable::class);
730+
} else {
731+
expect($context->get('object.nonExistingProperty'))->toBe(null);
732+
expect($context->get('object.simpleMethod'))->toBe(null);
733+
}
734+
})->with([
735+
'default' => false,
736+
'strict' => true,
737+
]);

tests/Stubs/MagicClass.php

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<?php
2+
3+
namespace Keepsuit\Liquid\Tests\Stubs;
4+
5+
class MagicClass
6+
{
7+
public array $data = [
8+
'simpleProperty' => 'foo',
9+
'nullProperty' => null,
10+
];
11+
12+
public function simpleMethod(): string
13+
{
14+
return 'foo';
15+
}
16+
17+
public function __get(string $property): mixed
18+
{
19+
if (! array_key_exists($property, $this->data)) {
20+
throw new \Error('Property does not exist: '.$property);
21+
}
22+
23+
return $this->data[$property];
24+
}
25+
26+
public function __set(string $property, mixed $value): void
27+
{
28+
$this->data[$property] = $value;
29+
}
30+
31+
public function __isset(string $property): bool
32+
{
33+
return array_key_exists($property, $this->data);
34+
}
35+
36+
public function __unset(string $property): void
37+
{
38+
unset($this->data[$property]);
39+
}
40+
}

tests/Stubs/SimpleClass.php

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
namespace Keepsuit\Liquid\Tests\Stubs;
4+
5+
class SimpleClass
6+
{
7+
public string $simpleProperty = 'foo';
8+
9+
public ?string $nullProperty = null;
10+
11+
protected string $protectedProperty = 'foo';
12+
13+
public static string $staticProperty = 'foo';
14+
15+
public static ?string $staticNullProperty = null;
16+
17+
public function simpleMethod(): string
18+
{
19+
return 'foo';
20+
}
21+
}

0 commit comments

Comments
 (0)