Skip to content

[PropertyAccess] Dynamic properties are ignored or cause an exception on set #45681

@kiler129

Description

@kiler129

Symfony version(s) affected

6.0.5

Description

This may be either a documentation problem or a bug in the PropertyAccess itself (hoping for the later ;)). In essence I have a case where an object is designated to be used like an array and has no __set(), relying on properties being just set on it. PropertyAccess throws NoSuchPropertyException when a non-existent property is used in setValue() and isExceptionOnInvalidPropertyPath() === true:

PHP Fatal error:  Uncaught Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException: Could not determine access type for property "foo" in class "stdClass". in /app/vendor/symfony/property-access/PropertyAccessor.php:537
Stack trace:
#0 /app/vendor/symfony/property-access/PropertyAccessor.php(136): Symfony\Component\PropertyAccess\PropertyAccessor->writeProperty(Array, 'foo', 'bar')
#1 /app/test.php(20): Symfony\Component\PropertyAccess\PropertyAccessor->setValue(Object(stdClass), 'foo', 'bar')
#2 {main}
  thrown in /app/vendor/symfony/property-access/PropertyAccessor.php on line 537

When isExceptionOnInvalidPropertyPath() === false the errors is swallowed quietly and no property is set on the object.

This behavior seems to contradict PropertyAccessorBuilder:

//...
     * This has no influence on writing non-existing indices with PropertyAccessorInterface::setValue()
     * which are always created on-the-fly.
//...
    public function enableExceptionOnInvalidPropertyPath(): static

The PropertyAccessorInterface::setValue() however explicitly states that, upon chain of options being exhausted, "If neither is found, an exception is thrown.".

How to reproduce

<?php
use Symfony\Component\PropertyAccess\PropertyAccess;
require_once __DIR__ . '/vendor/autoload.php';

$obj = new stdClass();
$builder = PropertyAccess::createPropertyAccessorBuilder();

$builder->disableExceptionOnInvalidPropertyPath();
$builder->getPropertyAccessor()->setValue($obj, 'foo', 'bar');
var_dump($obj); //empty object results


$builder->enableExceptionOnInvalidPropertyPath();
$builder->getPropertyAccessor()->setValue($obj, 'foo', 'bar'); //exception

Possible Solution

For me the most logical solution will be to allow writing non-existent properties only when accessor was built with isExceptionOnInvalidPropertyPath() === false and throw as currently is done with isExceptionOnInvalidPropertyPath() === true.

Alternatively the docblock of the builder (with maybe some not in the docs itself?) can be improved to state the property will never be created. However, this imho creates a strange situation where an exception is silenced but the property is silently ignored.

Additional Context

I believe allowing for dynamic properties creation, when it's explicitly intended, should be supported. While controversial in the internals itself it will become a first-class citizen in PHP 8.2 where intended: https://3v4l.org/Vj7Zu/rfc#vgit.master

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions