8000 merged branch jaugustin/property-access-add-magic-call (PR #7263) · symfony/symfony@d8ac478 · GitHub
[go: up one dir, main page]

Skip to content

Commit d8ac478

Browse files
committed
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 | symfony/symfony-docs#2472 - [x] submit changes to the documentation @bschussek is this ok ? Commits ------- a785baa [PropertyAccess] add support for magic call, related to #4683
2 parents 7ceb6e5 + a785baa commit d8ac478

9 files changed

+267
-6
lines changed

src/Symfony/Component/PropertyAccess/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ CHANGELOG
44
2.3.0
55
------
66

7+
* added PropertyAccessorBuilder, to enable or disable the support of "__call"
8+
* added support for "__call" in the PropertyAccessor (disabled by default)
79
* [BC BREAK] changed PropertyAccessor to continue its search for a property or
810
method even if a non-public match was found. Before, a PropertyAccessDeniedException
911
was thrown in this case. Class PropertyAccessDeniedException was removed

src/Symfony/Component/PropertyAccess/PropertyAccess.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,16 @@ public static function getPropertyAccessor()
2828
return new PropertyAccessor();
2929
}
3030

31+
/**
32+
* Creates a property accessor builder.
33+
*
34+
* @return PropertyAccessorBuilder The new property accessor builder
35+
*/
36+
public static function getPropertyAccessorBuilder()
37+
{
38+
return new PropertyAccessorBuilder();
39+
}
40+
3141
/**
3242
* This class cannot be instantiated.
3343
*/

src/Symfony/Component/PropertyAccess/PropertyAccessor.php

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,15 @@ class PropertyAccessor implements PropertyAccessorInterface
2424
const VALUE = 0;
2525
const IS_REF = 1;
2626

27+
private $magicCall;
28+
2729
/**
2830
* Should not be used by application code. Use
2931
* {@link PropertyAccess::getPropertyAccessor()} instead.
3032
*/
31-
public function __construct()
33+
public function __construct($magicCall = false)
3234
{
35+
$this->magicCall = $magicCall;
3336
}
3437

3538
/**
@@ -221,10 +224,13 @@ private function &readProperty(&$object, $property)
221224
// fatal error.
222225
$result[self::VALUE] =& $object->$property;
223226
$result[self::IS_REF] = true;
227+
} elseif ($this->magicCall && $reflClass->hasMethod('__call') && $reflClass->getMethod('__call')->isPublic()) {
228+
// we call the getter and hope the __call do the job
229+
$result[self::VALUE] = $object->$getter();
224230
} else {
225231
throw new NoSuchPropertyException(sprintf(
226232
'Neither the property "%s" nor one of the methods "%s()", '.
227-
'"%s()", "%s()" or "__get()" exist and have public access in '.
233+
'"%s()", "%s()", "__get()" or "__call()" exist and have public access in '.
228234
'class "%s".',
229235
$property,
230236
$getter,
@@ -348,10 +354,13 @@ private function writeProperty(&$object, $property, $singular, $value)
348354
// returns true, consequently the following line will result in a
349355
// fatal error.
350356
$object->$property = $value;
357+
} elseif ($this->magicCall && $reflClass->hasMethod('__call') && $reflClass->getMethod('__call')->isPublic()) {
358+
// we call the getter and hope the __call do the job
359+
$object->$setter($value);
351360
} else {
352361
throw new NoSuchPropertyException(sprintf(
353-
'Neither the property "%s" nor one of the methods %s"%s()" or '.
354-
'"__set()" exist and have public access in class "%s".',
362+
'Neither the property "%s" nor one of the methods %s"%s()", '.
363+
'"__set()" or "__call()" exist and have public access in class "%s".',
355364
$property,
356365
$guessedAdders,
357366
$setter,
Lines changed: 61 additions & 0 deletions
24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\PropertyAccess;
13+
14+
/**
15+
* The default implementation of {@link PropertyAccessorBuilderInterface}.
16+
*
17+
* @author Jérémie Augustin <jeremie.augustin@pixel-cookers.com>
18+
*/
19+
class PropertyAccessorBuilder implements PropertyAccessorBuilderInterface
20+
{
21+
/**
22+
* @var Boolean
23+
*/
+
private $magicCall = false;
25+
26+
/**
27+
* {@inheritdoc}
28+
*/
29+
public function enableMagicCall()
30+
{
31+
$this->magicCall = true;
32+
33+
return $this;
34+
}
35+
36+
/**
37+
* {@inheritdoc}
38+
*/
39+
public function disableMagicCall()
40+
{
41+
$this->magicCall = false;
42+
43+
return $this;
44+
}
45+
46+
/**
47+
* {@inheritdoc}
48+
*/
49+
public function isMagicCallEnabled()
50+
{
51+
return $this->magicCall;
52+
}
53+
54+
/**
55+
* {@inheritdoc}
56+
*/
57+
public function getPropertyAccessor()
58+
{
59+
return new PropertyAccessor($this->magicCall);
60+
}
61+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\PropertyAccess;
13+
14+
/**
15+
* A configurable builder for PropertyAccessorInterface objects.
16+
*
17+
* @author Jérémie Augustin <jeremie.augustin@pixel-cookers.com>
18+
*/
19+
interface PropertyAccessorBuilderInterface
20+
{
21+
/**
22+
* Enable the use of "__call" by the ProperyAccessor.
23+
*
24+
* @return PropertyAccessorBuilderInterface The builder object.
25+
*/
26+
public function enableMagicCall();
27+
28+
/**
29+
* Disable the use of "__call" by the ProperyAccessor.
30+
*
31+
* @return PropertyAccessorBuilderInterface The builder object.
32+
*/
33+
public function disableMagicCall();
34+
35+
/**
36+
* @return Boolean true if the use of "__call" by the ProperyAccessor is enable.
37+
*/
38+
public function isMagicCallEnabled();
39+
40+
/**
41+
* Builds and returns a new propertyAccessor object.
42+
*
43+
* @return PropertyAccessorInterface The built propertyAccessor.
44+
*/
45+
public function getPropertyAccessor();
46+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\PropertyAccess\Tests\Fixtures;
13+
14+
class MagicianCall
15+
{
16+
private $foobar;
17+
18+
public function __call($name, $args)
19+
{
20+
$property = lcfirst(substr($name, 3));
21+
if ('get' === substr($name, 0, 3)) {
22+
23+
return isset($this->$property) ? $this->$property : null;
24+
} elseif ('set' === substr($name, 0, 3)) {
25+
$value = 1 == count($args) ? $args[0] : null;
26+
$this->$property = $value;
27+
}
28+
}
29+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\PropertyAccess\Tests;
13+
14+
use Symfony\Component\PropertyAccess\PropertyAccessorBuilder;
15+
16+
class PropertyAccessorBuilderTest extends \PHPUnit_Framework_TestCase
17+
{
18+
/**
19+
* @var PropertyAccessorBuilderInterface
20+
*/
21+
protected $builder;
22+
23+
protected function setUp()
24+
{
25+
$this->builder = new PropertyAccessorBuilder();
26+
}
27+
28+
protected function tearDown()
29+
{
30+
$this->builder = null;
31+
}
32+
33+
public function testEnableMagicCall()
34+
{
35+
$this->assertSame($this->builder, $this->builder->enableMagicCall());
36+
}
37+
38+
public function testDisableMagicCall()
39+
{
40+
$this->assertSame($this->builder, $this->builder->disableMagicCall());
41+
}
42+
43+
public function testIsMagicCallEnable()
44+
{
45+
$this->assertFalse($this->builder->isMagicCallEnabled());
46+
$this->assertTrue($this->builder->enableMagicCall()->isMagicCallEnabled());
47+
$this->assertFalse($this->builder->disableMagicCall()->isMagicCallEnabled());
48+
}
49+
50+
public function testGetPropertyAccessor()
51+
{
52+
$this->assertInstanceOf('Symfony\Component\PropertyAccess\PropertyAccessor', $this->builder->getPropertyAccessor());
53+
$this->assertInstanceOf('Symfony\Component\PropertyAccess\PropertyAccessor', $this->builder->enableMagicCall()->getPropertyAccessor());
54+
}
55+
}

src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorCollectionTest.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -289,7 +289,7 @@ public function noAdderRemoverData()
289289
$propertyPath = 'axes';
290290
$expectedMessage = sprintf(
291291
'Neither the property "axes" nor one of the methods "addAx()", '.
292-
'"addAxe()", "addAxis()", "setAxes()" or "__set()" exist and have '.
292+
'"addAxe()", "addAxis()", "setAxes()", "__set()" or "__call()" exist and have '.
293293
'public access in class "%s".',
294294
get_class($car)
295295
);
@@ -313,7 +313,7 @@ public function noAdderRemoverData()
313313
$propertyPath = 'axes';
314314
$expectedMessage = sprintf(
315315
'Neither the property "axes" nor one of the methods "addAx()", '.
316-
'"addAxe()", "addAxis()", "setAxes()" or "__set()" exist and have '.
316+
'"addAxe()", "addAxis()", "setAxes()", "__set()" or "__call()" exist and have '.
317317
'public access in class "%s".',
318318
get_class($car)
319319
);

src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorTest.php

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
use Symfony\Component\PropertyAccess\PropertyAccessor;
1515
use Symfony\Component\PropertyAccess\Tests\Fixtures\Author;
1616
use Symfony\Component\PropertyAccess\Tests\Fixtures\Magician;
17+
use Symfony\Component\PropertyAccess\Tests\Fixtures\MagicianCall;
1718

1819
class PropertyAccessorTest extends \PHPUnit_Framework_TestCase
1920
{
@@ -331,4 +332,52 @@ public function testSetValueThrowsExceptionIfEmpty()
331332

332333
$this->propertyAccessor->setValue($value, 'foobar', 'bam');
333334
}
335+
336+
/**
337+
* @expectedException \Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException
338+
*/
339+
public function testSetValueFailsIfMagicCallDisabled()
340+
{
341+
$value = new MagicianCall();
342+
343+
$this->propertyAccessor->setValue($value, 'foobar', 'bam');
344+
}
345+
346+
/**
347+
* @expectedException \Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException
348+
*/
349+
public function testGetValueFailsIfMagicCallDisabled()
350+
{
351+
$value = new MagicianCall();
352+
353+
$this->propertyAccessor->getValue($value, 'foobar', 'bam');
354+
}
355+
356+
public function testGetValueReadsMagicCall()
357+
{
358+
$propertyAccessor = new PropertyAccessor(true);
359+
$object = new MagicianCall();
360+
$object->setMagicProperty('foobar');
361+
362+
$this->assertSame('foobar', $propertyAccessor->getValue($object, 'magicProperty'));
363+
}
364+
365+
public function testGetValueReadsMagicCallThatReturnsConstant()
366+
{
367+
$propertyAccessor = new PropertyAccessor(true);
368+
$object = new MagicianCall();
369+
370+
$this->assertNull($propertyAccessor->getValue($object, 'MagicProperty'));
371+
}
372+
373+
public function testSetValueUpdatesMagicCall()
374+
{
375+
$propertyAccessor = new PropertyAccessor(true);
376+
$object = new MagicianCall();
377+
378+
$propertyAccessor->setValue($object, 'magicProperty', 'foobar');
379+
380+
$this->assertEquals('foobar', $object->getMagicProperty());
381+
}
382+
334383
}

0 commit comments

Comments
 (0)
0