diff --git a/src/Symfony/Component/PropertyAccess/PropertyAccessor.php b/src/Symfony/Component/PropertyAccess/PropertyAccessor.php index 44a4105c06..f10755f0f6 100644 --- a/src/Symfony/Component/PropertyAccess/PropertyAccessor.php +++ b/src/Symfony/Component/PropertyAccess/PropertyAccessor.php @@ -314,12 +314,15 @@ class PropertyAccessor implements PropertyAccessorInterface $camelProp = $this->camelize($property); $reflClass = new \ReflectionClass($object); $getter = 'get'.$camelProp; + $getter2 = lcfirst($camelProp); $isser = 'is'.$camelProp; $hasser = 'has'.$camelProp; $classHasProperty = $reflClass->hasProperty($property); if ($reflClass->hasMethod($getter) && $reflClass->getMethod($getter)->isPublic()) { $result[self::VALUE] = $object->$getter(); + } elseif ($this->isMethodAccessible($reflClass, $getter2, 0)) { + $result[self::VALUE] = $object->$getter2(); } elseif ($reflClass->hasMethod($isser) && $reflClass->getMethod($isser)->isPublic()) { $result[self::VALUE] = $object->$isser(); } elseif ($reflClass->hasMethod($hasser) && $reflClass->getMethod($hasser)->isPublic()) { @@ -341,7 +344,7 @@ class PropertyAccessor implements PropertyAccessorInterface // we call the getter and hope the __call do the job $result[self::VALUE] = $object->$getter(); } else { - $methods = array($getter, $isser, $hasser, '__get'); + $methods = array($getter, $getter2, $isser, $hasser, '__get'); if ($this->magicCall) { $methods[] = '__call'; } @@ -413,10 +416,13 @@ class PropertyAccessor implements PropertyAccessorInterface } $setter = 'set'.$this->camelize($property); + $setter2 = lcfirst($plural); $classHasProperty = $reflClass->hasProperty($property); if ($this->isMethodAccessible($reflClass, $setter, 1)) { $object->$setter($value); + } elseif ($this->isMethodAccessible($reflClass, $setter2, 1)) { + $object->$setter2($value); } elseif ($this->isMethodAccessible($reflClass, '__set', 2)) { $object->$property = $value; } elseif ($classHasProperty && $reflClass->getProperty($property)->isPublic()) { @@ -433,13 +439,14 @@ class PropertyAccessor implements PropertyAccessorInterface $object->$setter($value); } else { throw new NoSuchPropertyException(sprintf( - 'Neither the property "%s" nor one of the methods %s"%s()", '. + 'Neither the property "%s" nor one of the methods %s"%s()", "%s()", '. '"__set()" or "__call()" exist and have public access in class "%s".', $property, implode('', array_map(function ($singular) { return '"add'.$singular.'()"/"remove'.$singular.'()", '; }, $singulars)), $setter, + $setter2, $reflClass->name )); } @@ -508,9 +515,11 @@ class PropertyAccessor implements PropertyAccessorInterface $reflClass = new \ReflectionClass($object); $setter = 'set'.$this->camelize($property); + $setter2 = lcfirst($this->camelize($property)); $classHasProperty = $reflClass->hasProperty($property); if ($this->isMethodAccessible($reflClass, $setter, 1) + || $this->isMethodAccessible($reflClass, $setter2, 1) || $this->isMethodAccessible($reflClass, '__set', 2) || ($classHasProperty && $reflClass->getProperty($property)->isPublic()) || (!$classHasProperty && property_exists($object, $property)) diff --git a/src/Symfony/Component/PropertyAccess/Tests/Fixtures/TestClass.php b/src/Symfony/Component/PropertyAccess/Tests/Fixtures/TestClass.php index 9765c77da2..6b99251557 100644 --- a/src/Symfony/Component/PropertyAccess/Tests/Fixtures/TestClass.php +++ b/src/Symfony/Component/PropertyAccess/Tests/Fixtures/TestClass.php @@ -18,6 +18,8 @@ class TestClass private $privateProperty; private $publicAccessor; + private $publicMethodAccessor; + private $publicMethodMutator; private $publicAccessorWithDefaultValue; private $publicAccessorWithRequiredAndDefaultValue; private $publicAccessorWithMoreRequiredParameters; @@ -28,6 +30,8 @@ class TestClass { $this->publicProperty = $value; $this->publicAccessor = $value; + $this->publicMethodAccessor = $value; + $this->publicMethodMutator = $value; $this->publicAccessorWithDefaultValue = $value; $this->publicAccessorWithRequiredAndDefaultValue = $value; $this->publicAccessorWithMoreRequiredParameters = $value; @@ -95,6 +99,21 @@ class TestClass return $this->publicHasAccessor; } + public function publicMethodAccessor() + { + return $this->publicMethodAccessor; + } + + public function publicMethodMutator($value) + { + $this->publicMethodMutator = $value; + } + + public function getPublicMethodMutator() + { + return $this->publicMethodMutator; + } + protected function setProtectedAccessor($value) { } diff --git a/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorCollectionTest.php b/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorCollectionTest.php index 7e4a49247c..60bd9dead8 100644 --- a/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorCollectionTest.php +++ b/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorCollectionTest.php @@ -172,7 +172,7 @@ abstract class PropertyAccessorCollectionTest extends \PHPUnit_Framework_TestCas /** * @expectedException \Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException - * @expectedExceptionMessage Neither the property "axes" nor one of the methods "addAx()"/"removeAx()", "addAxe()"/"removeAxe()", "addAxis()"/"removeAxis()", "setAxes()", "__set()" or "__call()" exist and have public access in class "Mock_PropertyAccessorCollectionTest_CarNoAdderAndRemover + * @expectedExceptionMessage Neither the property "axes" nor one of the methods "addAx()"/"removeAx()", "addAxe()"/"removeAxe()", "addAxis()"/"removeAxis()", "setAxes()", "axes()", "__set()" or "__call()" exist and have public access in class "Mock_PropertyAccessorCollectionTest_CarNoAdderAndRemover */ public function testSetValueFailsIfNoAdderNorRemoverFound() { diff --git a/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorTest.php b/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorTest.php index 6fc5f7022f..4ddf10234b 100644 --- a/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorTest.php +++ b/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorTest.php @@ -28,38 +28,23 @@ class PropertyAccessorTest extends \PHPUnit_Framework_TestCase $this->propertyAccessor = new PropertyAccessor(); } - public function getValidPropertyPaths() + public function getValidGetPropertyPaths() { - return array( - array(array('Bernhard', 'Schussek'), '[0]', 'Bernhard'), - array(array('Bernhard', 'Schussek'), '[1]', 'Schussek'), - array(array('firstName' => 'Bernhard'), '[firstName]', 'Bernhard'), - array(array('index' => array('firstName' => 'Bernhard')), '[index][firstName]', 'Bernhard'), - array((object) array('firstName' => 'Bernhard'), 'firstName', 'Bernhard'), - array((object) array('property' => array('firstName' => 'Bernhard')), 'property[firstName]', 'Bernhard'), - array(array('index' => (object) array('firstName' => 'Bernhard')), '[index].firstName', 'Bernhard'), - array((object) array('property' => (object) array('firstName' => 'Bernhard')), 'property.firstName', 'Bernhard'), + return array_merge( + array( + array(new TestClass('Bernhard'), 'publicMethodAccessor', 'Bernhard', 'Bernhard'), + ), + $this->getValidPropertyPaths() + ); + } - // Accessor methods - array(new TestClass('Bernhard'), 'publicProperty', 'Bernhard'), - array(new TestClass('Bernhard'), 'publicAccessor', 'Bernhard'), - array(new TestClass('Bernhard'), 'publicAccessorWithDefaultValue', 'Bernhard'), - array(new TestClass('Bernhard'), 'publicAccessorWithRequiredAndDefaultValue', 'Bernhard'), - array(new TestClass('Bernhard'), 'publicIsAccessor', 'Bernhard'), - array(new TestClass('Bernhard'), 'publicHasAccessor', 'Bernhard'), - - // Methods are camelized - array(new TestClass('Bernhard'), 'public_accessor', 'Bernhard'), - - // Missing indices - array(array('index' => array()), '[index][firstName]', null), - array(array('root' => array('index' => array())), '[root][index][firstName]', null), - - // Special chars - array(array('%!@$§.' => 'Bernhard'), '[%!@$§.]', 'Bernhard'), - array(array('index' => array('%!@$§.' => 'Bernhard')), '[index][%!@$§.]', 'Bernhard'), - array((object) array('%!@$§' => 'Bernhard'), '%!@$§', 'Bernhard'), - array((object) array('property' => (object) array('%!@$§' => 'Bernhard')), 'property.%!@$§', 'Bernhard'), + public function getValidSetPropertyPaths() + { + return array_merge( + array( + array(new TestClass('Bernhard'), 'publicMethodMutator', 'Bernhard', 'Bernhard'), + ), + $this->getValidPropertyPaths() ); } @@ -95,7 +80,7 @@ class PropertyAccessorTest extends \PHPUnit_Framework_TestCase } /** - * @dataProvider getValidPropertyPaths + * @dataProvider getValidGetPropertyPaths */ public function testGetValue($objectOrArray, $path, $value) { @@ -196,7 +181,7 @@ class PropertyAccessorTest extends \PHPUnit_Framework_TestCase } /** - * @dataProvider getValidPropertyPaths + * @dataProvider getValidSetPropertyPaths */ public function testSetValue($objectOrArray, $path) { @@ -312,7 +297,7 @@ class PropertyAccessorTest extends \PHPUnit_Framework_TestCase } /** - * @dataProvider getValidPropertyPaths + * @dataProvider getValidGetPropertyPaths */ public function testIsReadable($objectOrArray, $path) { @@ -380,7 +365,7 @@ class PropertyAccessorTest extends \PHPUnit_Framework_TestCase } /** - * @dataProvider getValidPropertyPaths + * @dataProvider getValidSetPropertyPaths */ public function testIsWritable($objectOrArray, $path) { @@ -446,4 +431,39 @@ class PropertyAccessorTest extends \PHPUnit_Framework_TestCase { $this->assertFalse($this->propertyAccessor->isWritable('', 'foobar', 'Updated')); } + + private function getValidPropertyPaths() + { + return array( + array(array('Bernhard', 'Schussek'), '[0]', 'Bernhard'), + array(array('Bernhard', 'Schussek'), '[1]', 'Schussek'), + array(array('firstName' => 'Bernhard'), '[firstName]', 'Bernhard'), + array(array('index' => array('firstName' => 'Bernhard')), '[index][firstName]', 'Bernhard'), + array((object) array('firstName' => 'Bernhard'), 'firstName', 'Bernhard'), + array((object) array('property' => array('firstName' => 'Bernhard')), 'property[firstName]', 'Bernhard'), + array(array('index' => (object) array('firstName' => 'Bernhard')), '[index].firstName', 'Bernhard'), + array((object) array('property' => (object) array('firstName' => 'Bernhard')), 'property.firstName', 'Bernhard'), + + // Accessor methods + array(new TestClass('Bernhard'), 'publicProperty', 'Bernhard'), + array(new TestClass('Bernhard'), 'publicAccessor', 'Bernhard'), + array(new TestClass('Bernhard'), 'publicAccessorWithDefaultValue', 'Bernhard'), + array(new TestClass('Bernhard'), 'publicAccessorWithRequiredAndDefaultValue', 'Bernhard'), + array(new TestClass('Bernhard'), 'publicIsAccessor', 'Bernhard'), + array(new TestClass('Bernhard'), 'publicHasAccessor', 'Bernhard'), + + // Methods are camelized + array(new TestClass('Bernhard'), 'public_accessor', 'Bernhard'), + + // Missing indices + array(array('index' => array()), '[index][firstName]', null), + array(array('root' => array('index' => array())), '[root][index][firstName]', null), + + // Special chars + array(array('%!@$§.' => 'Bernhard'), '[%!@$§.]', 'Bernhard'), + array(array('index' => array('%!@$§.' => 'Bernhard')), '[index][%!@$§.]', 'Bernhard'), + array((object) array('%!@$§' => 'Bernhard'), '%!@$§', 'Bernhard'), + array((object) array('property' => (object) array('%!@$§' => 'Bernhard')), 'property.%!@$§', 'Bernhard'), + ); + } }