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
|
||||
------
|
||||
|
||||
* 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
|
||||
method even if a non-public match was found. Before, a PropertyAccessDeniedException
|
||||
was thrown in this case. Class PropertyAccessDeniedException was removed
|
||||
|
|
|
@ -28,6 +28,16 @@ final class PropertyAccess
|
|||
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.
|
||||
*/
|
||||
|
|
|
@ -24,12 +24,15 @@ class PropertyAccessor implements PropertyAccessorInterface
|
|||
const VALUE = 0;
|
||||
const IS_REF = 1;
|
||||
|
||||
private $magicCall;
|
||||
|
||||
/**
|
||||
* Should not be used by application code. Use
|
||||
* {@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.
|
||||
$result[self::VALUE] =& $object->$property;
|
||||
$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 {
|
||||
throw new NoSuchPropertyException(sprintf(
|
||||
'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".',
|
||||
$property,
|
||||
$getter,
|
||||
|
@ -348,10 +354,13 @@ class PropertyAccessor implements PropertyAccessorInterface
|
|||
// returns true, consequently the following line will result in a
|
||||
// fatal error.
|
||||
$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 {
|
||||
throw new NoSuchPropertyException(sprintf(
|
||||
'Neither the property "%s" nor one of the methods %s"%s()" or '.
|
||||
'"__set()" exist and have public access in class "%s".',
|
||||
'Neither the property "%s" nor one of the methods %s"%s()", '.
|
||||
'"__set()" or "__call()" exist and have public access in class "%s".',
|
||||
$property,
|
||||
$guessedAdders,
|
||||
$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';
|
||||
$expectedMessage = sprintf(
|
||||
'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".',
|
||||
get_class($car)
|
||||
);
|
||||
|
@ -313,7 +313,7 @@ abstract class PropertyAccessorCollectionTest extends \PHPUnit_Framework_TestCas
|
|||
$propertyPath = 'axes';
|
||||
$expectedMessage = sprintf(
|
||||
'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".',
|
||||
get_class($car)
|
||||
);
|
||||
|
|
|
@ -14,6 +14,7 @@ namespace Symfony\Component\PropertyAccess\Tests;
|
|||
use Symfony\Component\PropertyAccess\PropertyAccessor;
|
||||
use Symfony\Component\PropertyAccess\Tests\Fixtures\Author;
|
||||
use Symfony\Component\PropertyAccess\Tests\Fixtures\Magician;
|
||||
use Symfony\Component\PropertyAccess\Tests\Fixtures\MagicianCall;
|
||||
|
||||
class PropertyAccessorTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
|
@ -331,4 +332,52 @@ class PropertyAccessorTest extends \PHPUnit_Framework_TestCase
|
|||
|
||||
$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