Skip to content

[Bug]: Variable evaluation fails for objects using magic getters #41

@tillschander

Description

@tillschander

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:

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions