bug #14605 [PropertyAccess] Fix setting public property on a class having a magic getter (lolautruche)

This PR was merged into the 2.6 branch.

Discussion
----------

[PropertyAccess] Fix setting public property on a class having a magic 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 d733a887 changed the way
`setValue()` works.

Commits
-------

8b8feff [PropertyAccess] Fix setting public property on a class having a magic getter
This commit is contained in:
Tobias Schultze 2015-05-11 17:24:49 +02:00
commit 49ea81b2ff
3 changed files with 14 additions and 4 deletions

View File

@ -314,11 +314,11 @@ class PropertyAccessor implements PropertyAccessorInterface
$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
@ -410,10 +410,10 @@ class PropertyAccessor implements PropertyAccessorInterface
$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

View File

@ -15,6 +15,8 @@ class TestClassMagicGet
{
private $magicProperty;
public $publicProperty;
public function __construct($value)
{
$this->magicProperty = $value;

View File

@ -438,4 +438,12 @@ class PropertyAccessorTest extends \PHPUnit_Framework_TestCase
$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']);
}
}