Skip to content

Commit

Permalink
[PropertyAccess] Fix setting public property on a class having a magi…
Browse files Browse the repository at this point in the history
…c getter

| Q             | A
| ------------- | ---
| Bug fix?      | yes
| New feature?  | no
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | N/A
| License       | MIT
| Doc PR        | N/A

When using PropertyAccessor with an object having both a public property
and a magic getter, and one wants to update this property,
PropertyAccessor may lose the property reference.
This occurs when the public property value is a hash:

```php
class Foo
{
    /**
     * Example: $this->someProperty['foo']['bar'] = 'baz'
     * @var array
     */
    public $someProperty;

    public function __get($name)
    {
        // ...
    }
}

$obj = new Foo();
$obj->someProperty = ['foo' => ['bar' => 'some_value']];

$propertyAccessor->setValue($obj, 'someProperty[foo][bar]', 'another_value');

echo $obj->someProperty['foo']['bar'];
// Before this patch: 'some_value' => fail
// After this patch: 'another_value' => correct
```

Furthermore, public properties are always used before `__get()` by PHP.

This bug is visible since v2.6.5 as d733a88 changed the way
`setValue()` works.
  • Loading branch information
lolautruche committed May 11, 2015
1 parent c7bb672 commit 8b8feff
Show file tree
Hide file tree
Showing 3 changed files with 14 additions and 4 deletions.
8 changes: 4 additions & 4 deletions src/Symfony/Component/PropertyAccess/PropertyAccessor.php
Expand Up @@ -314,11 +314,11 @@ private function &readProperty(&$object, $property)
$result[self::VALUE] = $object->$isser();
} elseif ($reflClass->hasMethod($hasser) && $reflClass->getMethod($hasser)->isPublic()) {
$result[self::VALUE] = $object->$hasser();
} elseif ($reflClass->hasMethod('__get') && $reflClass->getMethod('__get')->isPublic()) {
$result[self::VALUE] = $object->$property;
} elseif ($classHasProperty && $reflClass->getProperty($property)->isPublic()) {
$result[self::VALUE] = &$object->$property;
$result[self::IS_REF] = true;
} elseif ($reflClass->hasMethod('__get') && $reflClass->getMethod('__get')->isPublic()) {
$result[self::VALUE] = $object->$property;
} elseif (!$classHasProperty && property_exists($object, $property)) {
// Needed to support \stdClass instances. We need to explicitly
// exclude $classHasProperty, otherwise if in the previous clause
Expand Down Expand Up @@ -410,10 +410,10 @@ private function writeProperty(&$object, $property, $value)
$object->$setter($value);
} elseif ($this->isMethodAccessible($reflClass, $getsetter, 1)) {
$object->$getsetter($value);
} elseif ($this->isMethodAccessible($reflClass, '__set', 2)) {
$object->$property = $value;
} elseif ($classHasProperty && $reflClass->getProperty($property)->isPublic()) {
$object->$property = $value;
} elseif ($this->isMethodAccessible($reflClass, '__set', 2)) {
$object->$property = $value;
} elseif (!$classHasProperty && property_exists($object, $property)) {
// Needed to support \stdClass instances. We need to explicitly
// exclude $classHasProperty, otherwise if in the previous clause
Expand Down
Expand Up @@ -15,6 +15,8 @@ class TestClassMagicGet
{
private $magicProperty;

public $publicProperty;

public function __construct($value)
{
$this->magicProperty = $value;
Expand Down
Expand Up @@ -438,4 +438,12 @@ public function testTicket5755()

$this->assertEquals('foobar', $object->getProperty());
}

public function testSetValueDeepWithMagicGetter()
{
$obj = new TestClassMagicGet('foo');
$obj->publicProperty = array('foo' => array('bar' => 'some_value'));
$this->propertyAccessor->setValue($obj, 'publicProperty[foo][bar]', 'Updated');
$this->assertSame('Updated', $obj->publicProperty['foo']['bar']);
}
}

0 comments on commit 8b8feff

Please sign in to comment.