-
Notifications
You must be signed in to change notification settings - Fork 1
Description
What happened?
Given a class like this that uses a magic getter...
class User {
public function __construct(
private array $data
) {}
public function __get(string $property): mixed {
return $this->data[$property] ?? "Property '$property' does not exist.";
}
}...with...
$user = new User(["name" => "Foo"]);...i would expect the following template to output "Foo" and not nothing.
{{ user.name }}How to reproduce the bug
Try to access any property of any class with a magic getter.
The models in Laravel use magic getters and setters:
https://github.com/illuminate/database/blob/ab85fdb286ba05bb735ea7d2a01bcdb2938ac609/Eloquent/Model.php#L2273-L2282
So here is a basic example using Laravel and keepsuit/laravel-liquid
// routes/web.php
Route::get('/', function () {
return view('home', [
'user' => User::first()
]);
});{% # resources/views/home.liquid %}
{{ user.name }}Package Version
v0.8.0
PHP Version
8.3.0
Which operating systems does with happen with?
No response
Notes
The reason for why this fails lies in internalContextLookup() of the RenderContext class:
php-liquid/src/Render/RenderContext.php
Line 201 in 96a4e87
| is_object($scope) && property_exists($scope, (string) $key) => $scope->{$key}, |
The call to property_exists() only works for actual class properties. For magic / virtual properties it will always return false.
But maybe we can work around this with isset()? Unlike property_exists() it can work in conjunction with magic properties as its behavior in such cases can be defined via __isset().
An alternative lookup using isset() might look something like this:
- is_object($scope) && property_exists($scope, (string) $key) => $scope->{$key},
+ is_object($scope) && (isset($scope->$key) || is_null($scope->$key ?? false)) => $scope->{$key}, But maybe that line should be broken out into its own method as it is getting a bit complicated 😅
Basically I am checking if the property is set or if it is null (as isset() returns false for null values). The weird null coalescing operator is there to prevent errors in case the property really is undefined.