Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 30 additions & 2 deletions src/Render/RenderContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -196,9 +196,10 @@ public function internalContextLookup(mixed $scope, int|string $key): mixed
{
try {
$value = match (true) {
is_array($scope) && array_key_exists($key, $scope) => $scope[$key],
$scope instanceof Drop => $scope->{$key},
is_object($scope) && property_exists($scope, (string) $key) => $scope->{$key},
is_array($scope) && array_key_exists($key, $scope) => $scope[$key],
is_object($scope) && $this->objectHasProperty($scope, (string) $key) => $scope->{$key},
is_object($scope) && $this->objectHasStaticProperty($scope, (string) $key) => $scope::$$key,
default => new MissingValue,
};
} catch (UndefinedDropMethodException) {
Expand All @@ -208,6 +209,33 @@ public function internalContextLookup(mixed $scope, int|string $key): mixed
return $this->normalizeValue($value);
}

protected function objectHasProperty(object $object, string $property): bool
{
try {
// Check if the property is a public property
if (array_key_exists($property, get_object_vars($object))) {
return true;
}

// Check if the property is accessible via __get() and __isset()
if (isset($object->{$property})) {
return true;
}

return false;
} catch (Throwable) {
return false;
}
}

protected function objectHasStaticProperty(object $object, string $property): bool
{
$className = get_class($object);
$staticProperties = get_class_vars($className);

return array_key_exists($property, $staticProperties);
}

public function normalizeValue(mixed $value): mixed
{
if (is_object($value) && isset($this->sharedState->computedObjectsCache[$value])) {
Expand Down
44 changes: 44 additions & 0 deletions tests/Integration/ContextTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
use Keepsuit\Liquid\Tests\Stubs\ContextSensitiveDrop;
use Keepsuit\Liquid\Tests\Stubs\CounterDrop;
use Keepsuit\Liquid\Tests\Stubs\HundredCents;
use Keepsuit\Liquid\Tests\Stubs\MagicClass;
use Keepsuit\Liquid\Tests\Stubs\SimpleClass;

test('variables', function (bool $strict) {
$context = new RenderContext(options: new RenderContextOptions(strictVariables: $strict));
Expand Down Expand Up @@ -691,3 +693,45 @@ function () use (&$global) {
'default' => false,
'strict' => true,
]);

test('internal context lookup with simple object', function (bool $strict) {
$context = new RenderContext(options: new RenderContextOptions(strictVariables: $strict));
$context->set('object', new SimpleClass);

expect($context->get('object.simpleProperty'))->toBe('foo');
expect($context->get('object.nullProperty'))->toBe(null);
expect($context->get('object.staticProperty'))->toBe('foo');
expect($context->get('object.staticNullProperty'))->toBe(null);

if ($strict) {
expect($context->get('object.protectedProperty'))->toBeInstanceOf(UndefinedVariable::class);
expect($context->get('object.nonExistingProperty'))->toBeInstanceOf(UndefinedVariable::class);
expect($context->get('object.simpleMethod'))->toBeInstanceOf(UndefinedVariable::class);
} else {
expect($context->get('object.protectedProperty'))->toBe(null);
expect($context->get('object.nonExistingProperty'))->toBe(null);
expect($context->get('object.simpleMethod'))->toBe(null);
}
})->with([
'default' => false,
'strict' => true,
]);

test('internal context lookup magic object', function (bool $strict) {
$context = new RenderContext(options: new RenderContextOptions(strictVariables: $strict));
$context->set('object', new MagicClass);

expect($context->get('object.simpleProperty'))->toBe('foo');
expect($context->get('object.nullProperty'))->toBe(null);

if ($strict) {
expect($context->get('object.nonExistingProperty'))->toBeInstanceOf(UndefinedVariable::class);
expect($context->get('object.simpleMethod'))->toBeInstanceOf(UndefinedVariable::class);
} else {
expect($context->get('object.nonExistingProperty'))->toBe(null);
expect($context->get('object.simpleMethod'))->toBe(null);
}
})->with([
'default' => false,
'strict' => true,
]);
40 changes: 40 additions & 0 deletions tests/Stubs/MagicClass.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?php

namespace Keepsuit\Liquid\Tests\Stubs;

class MagicClass
{
public array $data = [
'simpleProperty' => 'foo',
'nullProperty' => null,
];

public function simpleMethod(): string
{
return 'foo';
}

public function __get(string $property): mixed
{
if (! array_key_exists($property, $this->data)) {
throw new \Error('Property does not exist: '.$property);
}

return $this->data[$property];
}

public function __set(string $property, mixed $value): void
{
$this->data[$property] = $value;
}

public function __isset(string $property): bool
{
return array_key_exists($property, $this->data);
}

public function __unset(string $property): void
{
unset($this->data[$property]);
}
}
21 changes: 21 additions & 0 deletions tests/Stubs/SimpleClass.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

namespace Keepsuit\Liquid\Tests\Stubs;

class SimpleClass
{
public string $simpleProperty = 'foo';

public ?string $nullProperty = null;

protected string $protectedProperty = 'foo';

public static string $staticProperty = 'foo';

public static ?string $staticNullProperty = null;

public function simpleMethod(): string
{
return 'foo';
}
}