merged branch jaugustin/property-access-add-magic-call (PR #7263)
This PR was merged into the master branch.
Discussion
----------
[PropertyAccess] add support for magic call
Hi,
I add support for magic call with the `PropertyAccess`
the is basic implementation, if no `getter`, `isser`, or `hasser` or `_get` is found and there is `__call` then the PropertyAccess call the getter
the same for setter.
This functionality is disable by default
| Q | A
| ------------- | ---
| Bug fix? | no
| New feature? | yes
| BC breaks? | no
| Deprecations? | no
| Tests pass? | seems OK (failure/errors are the same on master)
| Fixed tickets | #4683, #6413, #5309
| License | MIT
| Doc PR | https://github.com/symfony/symfony-docs/pull/2472
- [x] submit changes to the documentation
@bschussek is this ok ?
Commits
-------
a785baa
[PropertyAccess] add support for magic call, related to #4683
This commit is contained in:
commit
d8ac478b19
@ -4,6 +4,8 @@ CHANGELOG
|
|||||||
2.3.0
|
2.3.0
|
||||||
------
|
------
|
||||||
|
|
||||||
|
* added PropertyAccessorBuilder, to enable or disable the support of "__call"
|
||||||
|
* added support for "__call" in the PropertyAccessor (disabled by default)
|
||||||
* [BC BREAK] changed PropertyAccessor to continue its search for a property or
|
* [BC BREAK] changed PropertyAccessor to continue its search for a property or
|
||||||
method even if a non-public match was found. Before, a PropertyAccessDeniedException
|
method even if a non-public match was found. Before, a PropertyAccessDeniedException
|
||||||
was thrown in this case. Class PropertyAccessDeniedException was removed
|
was thrown in this case. Class PropertyAccessDeniedException was removed
|
||||||
|
@ -28,6 +28,16 @@ final class PropertyAccess
|
|||||||
return new PropertyAccessor();
|
return new PropertyAccessor();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a property accessor builder.
|
||||||
|
*
|
||||||
|
* @return PropertyAccessorBuilder The new property accessor builder
|
||||||
|
*/
|
||||||
|
public static function getPropertyAccessorBuilder()
|
||||||
|
{
|
||||||
|
return new PropertyAccessorBuilder();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class cannot be instantiated.
|
* This class cannot be instantiated.
|
||||||
*/
|
*/
|
||||||
|
@ -24,12 +24,15 @@ class PropertyAccessor implements PropertyAccessorInterface
|
|||||||
const VALUE = 0;
|
const VALUE = 0;
|
||||||
const IS_REF = 1;
|
const IS_REF = 1;
|
||||||
|
|
||||||
|
private $magicCall;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Should not be used by application code. Use
|
* Should not be used by application code. Use
|
||||||
* {@link PropertyAccess::getPropertyAccessor()} instead.
|
* {@link PropertyAccess::getPropertyAccessor()} instead.
|
||||||
*/
|
*/
|
||||||
public function __construct()
|
public function __construct($magicCall = false)
|
||||||
{
|
{
|
||||||
|
$this->magicCall = $magicCall;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -221,10 +224,13 @@ class PropertyAccessor implements PropertyAccessorInterface
|
|||||||
// fatal error.
|
// fatal error.
|
||||||
$result[self::VALUE] =& $object->$property;
|
$result[self::VALUE] =& $object->$property;
|
||||||
$result[self::IS_REF] = true;
|
$result[self::IS_REF] = true;
|
||||||
|
} elseif ($this->magicCall && $reflClass->hasMethod('__call') && $reflClass->getMethod('__call')->isPublic()) {
|
||||||
|
// we call the getter and hope the __call do the job
|
||||||
|
$result[self::VALUE] = $object->$getter();
|
||||||
} else {
|
} else {
|
||||||
throw new NoSuchPropertyException(sprintf(
|
throw new NoSuchPropertyException(sprintf(
|
||||||
'Neither the property "%s" nor one of the methods "%s()", '.
|
'Neither the property "%s" nor one of the methods "%s()", '.
|
||||||
'"%s()", "%s()" or "__get()" exist and have public access in '.
|
'"%s()", "%s()", "__get()" or "__call()" exist and have public access in '.
|
||||||
'class "%s".',
|
'class "%s".',
|
||||||
$property,
|
$property,
|
||||||
$getter,
|
$getter,
|
||||||
@ -348,10 +354,13 @@ class PropertyAccessor implements PropertyAccessorInterface
|
|||||||
// returns true, consequently the following line will result in a
|
// returns true, consequently the following line will result in a
|
||||||
// fatal error.
|
// fatal error.
|
||||||
$object->$property = $value;
|
$object->$property = $value;
|
||||||
|
} elseif ($this->magicCall && $reflClass->hasMethod('__call') && $reflClass->getMethod('__call')->isPublic()) {
|
||||||
|
// we call the getter and hope the __call do the job
|
||||||
|
$object->$setter($value);
|
||||||
} else {
|
} else {
|
||||||
throw new NoSuchPropertyException(sprintf(
|
throw new NoSuchPropertyException(sprintf(
|
||||||
'Neither the property "%s" nor one of the methods %s"%s()" or '.
|
'Neither the property "%s" nor one of the methods %s"%s()", '.
|
||||||
'"__set()" exist and have public access in class "%s".',
|
'"__set()" or "__call()" exist and have public access in class "%s".',
|
||||||
$property,
|
$property,
|
||||||
$guessedAdders,
|
$guessedAdders,
|
||||||
$setter,
|
$setter,
|
||||||
|
@ -0,0 +1,61 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\PropertyAccess;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default implementation of {@link PropertyAccessorBuilderInterface}.
|
||||||
|
*
|
||||||
|
* @author Jérémie Augustin <jeremie.augustin@pixel-cookers.com>
|
||||||
|
*/
|
||||||
|
class PropertyAccessorBuilder implements PropertyAccessorBuilderInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var Boolean
|
||||||
|
*/
|
||||||
|
private $magicCall = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function enableMagicCall()
|
||||||
|
{
|
||||||
|
$this->magicCall = true;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function disableMagicCall()
|
||||||
|
{
|
||||||
|
$this->magicCall = false;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function isMagicCallEnabled()
|
||||||
|
{
|
||||||
|
return $this->magicCall;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getPropertyAccessor()
|
||||||
|
{
|
||||||
|
return new PropertyAccessor($this->magicCall);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,46 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\PropertyAccess;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A configurable builder for PropertyAccessorInterface objects.
|
||||||
|
*
|
||||||
|
* @author Jérémie Augustin <jeremie.augustin@pixel-cookers.com>
|
||||||
|
*/
|
||||||
|
interface PropertyAccessorBuilderInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Enable the use of "__call" by the ProperyAccessor.
|
||||||
|
*
|
||||||
|
* @return PropertyAccessorBuilderInterface The builder object.
|
||||||
|
*/
|
||||||
|
public function enableMagicCall();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disable the use of "__call" by the ProperyAccessor.
|
||||||
|
*
|
||||||
|
* @return PropertyAccessorBuilderInterface The builder object.
|
||||||
|
*/
|
||||||
|
public function disableMagicCall();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Boolean true if the use of "__call" by the ProperyAccessor is enable.
|
||||||
|
*/
|
||||||
|
public function isMagicCallEnabled();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds and returns a new propertyAccessor object.
|
||||||
|
*
|
||||||
|
* @return PropertyAccessorInterface The built propertyAccessor.
|
||||||
|
*/
|
||||||
|
public function getPropertyAccessor();
|
||||||
|
}
|
@ -0,0 +1,29 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\PropertyAccess\Tests\Fixtures;
|
||||||
|
|
||||||
|
class MagicianCall
|
||||||
|
{
|
||||||
|
private $foobar;
|
||||||
|
|
||||||
|
public function __call($name, $args)
|
||||||
|
{
|
||||||
|
$property = lcfirst(substr($name, 3));
|
||||||
|
if ('get' === substr($name, 0, 3)) {
|
||||||
|
|
||||||
|
return isset($this->$property) ? $this->$property : null;
|
||||||
|
} elseif ('set' === substr($name, 0, 3)) {
|
||||||
|
$value = 1 == count($args) ? $args[0] : null;
|
||||||
|
$this->$property = $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,55 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\PropertyAccess\Tests;
|
||||||
|
|
||||||
|
use Symfony\Component\PropertyAccess\PropertyAccessorBuilder;
|
||||||
|
|
||||||
|
class PropertyAccessorBuilderTest extends \PHPUnit_Framework_TestCase
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var PropertyAccessorBuilderInterface
|
||||||
|
*/
|
||||||
|
protected $builder;
|
||||||
|
|
||||||
|
protected function setUp()
|
||||||
|
{
|
||||||
|
$this->builder = new PropertyAccessorBuilder();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function tearDown()
|
||||||
|
{
|
||||||
|
$this->builder = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testEnableMagicCall()
|
||||||
|
{
|
||||||
|
$this->assertSame($this->builder, $this->builder->enableMagicCall());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testDisableMagicCall()
|
||||||
|
{
|
||||||
|
$this->assertSame($this->builder, $this->builder->disableMagicCall());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testIsMagicCallEnable()
|
||||||
|
{
|
||||||
|
$this->assertFalse($this->builder->isMagicCallEnabled());
|
||||||
|
$this->assertTrue($this->builder->enableMagicCall()->isMagicCallEnabled());
|
||||||
|
$this->assertFalse($this->builder->disableMagicCall()->isMagicCallEnabled());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetPropertyAccessor()
|
||||||
|
{
|
||||||
|
$this->assertInstanceOf('Symfony\Component\PropertyAccess\PropertyAccessor', $this->builder->getPropertyAccessor());
|
||||||
|
$this->assertInstanceOf('Symfony\Component\PropertyAccess\PropertyAccessor', $this->builder->enableMagicCall()->getPropertyAccessor());
|
||||||
|
}
|
||||||
|
}
|
@ -289,7 +289,7 @@ abstract class PropertyAccessorCollectionTest extends \PHPUnit_Framework_TestCas
|
|||||||
$propertyPath = 'axes';
|
$propertyPath = 'axes';
|
||||||
$expectedMessage = sprintf(
|
$expectedMessage = sprintf(
|
||||||
'Neither the property "axes" nor one of the methods "addAx()", '.
|
'Neither the property "axes" nor one of the methods "addAx()", '.
|
||||||
'"addAxe()", "addAxis()", "setAxes()" or "__set()" exist and have '.
|
'"addAxe()", "addAxis()", "setAxes()", "__set()" or "__call()" exist and have '.
|
||||||
'public access in class "%s".',
|
'public access in class "%s".',
|
||||||
get_class($car)
|
get_class($car)
|
||||||
);
|
);
|
||||||
@ -313,7 +313,7 @@ abstract class PropertyAccessorCollectionTest extends \PHPUnit_Framework_TestCas
|
|||||||
$propertyPath = 'axes';
|
$propertyPath = 'axes';
|
||||||
$expectedMessage = sprintf(
|
$expectedMessage = sprintf(
|
||||||
'Neither the property "axes" nor one of the methods "addAx()", '.
|
'Neither the property "axes" nor one of the methods "addAx()", '.
|
||||||
'"addAxe()", "addAxis()", "setAxes()" or "__set()" exist and have '.
|
'"addAxe()", "addAxis()", "setAxes()", "__set()" or "__call()" exist and have '.
|
||||||
'public access in class "%s".',
|
'public access in class "%s".',
|
||||||
get_class($car)
|
get_class($car)
|
||||||
);
|
);
|
||||||
|
@ -14,6 +14,7 @@ namespace Symfony\Component\PropertyAccess\Tests;
|
|||||||
use Symfony\Component\PropertyAccess\PropertyAccessor;
|
use Symfony\Component\PropertyAccess\PropertyAccessor;
|
||||||
use Symfony\Component\PropertyAccess\Tests\Fixtures\Author;
|
use Symfony\Component\PropertyAccess\Tests\Fixtures\Author;
|
||||||
use Symfony\Component\PropertyAccess\Tests\Fixtures\Magician;
|
use Symfony\Component\PropertyAccess\Tests\Fixtures\Magician;
|
||||||
|
use Symfony\Component\PropertyAccess\Tests\Fixtures\MagicianCall;
|
||||||
|
|
||||||
class PropertyAccessorTest extends \PHPUnit_Framework_TestCase
|
class PropertyAccessorTest extends \PHPUnit_Framework_TestCase
|
||||||
{
|
{
|
||||||
@ -331,4 +332,52 @@ class PropertyAccessorTest extends \PHPUnit_Framework_TestCase
|
|||||||
|
|
||||||
$this->propertyAccessor->setValue($value, 'foobar', 'bam');
|
$this->propertyAccessor->setValue($value, 'foobar', 'bam');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @expectedException \Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException
|
||||||
|
*/
|
||||||
|
public function testSetValueFailsIfMagicCallDisabled()
|
||||||
|
{
|
||||||
|
$value = new MagicianCall();
|
||||||
|
|
||||||
|
$this->propertyAccessor->setValue($value, 'foobar', 'bam');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @expectedException \Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException
|
||||||
|
*/
|
||||||
|
public function testGetValueFailsIfMagicCallDisabled()
|
||||||
|
{
|
||||||
|
$value = new MagicianCall();
|
||||||
|
|
||||||
|
$this->propertyAccessor->getValue($value, 'foobar', 'bam');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetValueReadsMagicCall()
|
||||||
|
{
|
||||||
|
$propertyAccessor = new PropertyAccessor(true);
|
||||||
|
$object = new MagicianCall();
|
||||||
|
$object->setMagicProperty('foobar');
|
||||||
|
|
||||||
|
$this->assertSame('foobar', $propertyAccessor->getValue($object, 'magicProperty'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetValueReadsMagicCallThatReturnsConstant()
|
||||||
|
{
|
||||||
|
$propertyAccessor = new PropertyAccessor(true);
|
||||||
|
$object = new MagicianCall();
|
||||||
|
|
||||||
|
$this->assertNull($propertyAccessor->getValue($object, 'MagicProperty'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSetValueUpdatesMagicCall()
|
||||||
|
{
|
||||||
|
$propertyAccessor = new PropertyAccessor(true);
|
||||||
|
$object = new MagicianCall();
|
||||||
|
|
||||||
|
$propertyAccessor->setValue($object, 'magicProperty', 'foobar');
|
||||||
|
|
||||||
|
$this->assertEquals('foobar', $object->getMagicProperty());
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user