From f3b023f481cf976d7b8b5dc2265e96c959f1ef4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Mon, 28 Mar 2016 11:03:47 +0200 Subject: [PATCH 1/7] [PropertyInfo] Support singula adder and remover. Close #18166. --- .../Extractor/ReflectionExtractor.php | 76 +++++++++++++++---- .../Extractors/ReflectionExtractorTest.php | 8 ++ .../Tests/Fixtures/AdderRemoverDummy.php | 29 +++++++ .../Component/PropertyInfo/composer.json | 4 +- 4 files changed, 103 insertions(+), 14 deletions(-) create mode 100644 src/Symfony/Component/PropertyInfo/Tests/Fixtures/AdderRemoverDummy.php diff --git a/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php b/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php index 8226655f7a139..97559f3bec12d 100644 --- a/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php +++ b/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php @@ -11,6 +11,7 @@ namespace Symfony\Component\PropertyInfo\Extractor; +use Symfony\Component\PropertyAccess\StringUtil; use Symfony\Component\PropertyInfo\PropertyAccessExtractorInterface; use Symfony\Component\PropertyInfo\PropertyListExtractorInterface; use Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface; @@ -40,7 +41,7 @@ class ReflectionExtractor implements PropertyListExtractorInterface, PropertyTyp /** * @internal * - * @var array[] + * @var string[] */ public static $arrayMutatorPrefixes = array('add', 'remove'); @@ -55,13 +56,17 @@ public function getProperties($class, array $context = array()) return; } + $reflectionProperties = $reflectionClass->getProperties(); + $properties = array(); - foreach ($reflectionClass->getProperties(\ReflectionProperty::IS_PUBLIC) as $reflectionProperty) { - $properties[$reflectionProperty->name] = true; + foreach ($reflectionProperties as $reflectionProperty) { + if ($reflectionProperty->isPublic()) { + $properties[$reflectionProperty->name] = true; + } } foreach ($reflectionClass->getMethods(\ReflectionMethod::IS_PUBLIC) as $reflectionMethod) { - $propertyName = $this->getPropertyName($reflectionMethod->name); + $propertyName = $this->getPropertyName($reflectionMethod->name, $reflectionProperties); if (!$propertyName || isset($properties[$propertyName])) { continue; } @@ -312,17 +317,28 @@ private function getAccessorMethod($class, $property) private function getMutatorMethod($class, $property) { $ucProperty = ucfirst($property); + $singulars = $this->getSingulars($ucProperty); foreach (self::$mutatorPrefixes as $prefix) { - try { - $reflectionMethod = new \ReflectionMethod($class, $prefix.$ucProperty); - // Parameter can be optional to allow things like: method(array $foo = null) - if ($reflectionMethod->getNumberOfParameters() >= 1) { - return array($reflectionMethod, $prefix); + if (null !== $singulars && in_array($prefix, self::$arrayMutatorPrefixes)) { + $names = $singulars; + $names[] = $ucProperty; + } else { + $names = array($ucProperty); + } + + foreach ($names as $name) { + try { + $reflectionMethod = new \ReflectionMethod($class, $prefix.$name); + + // Parameter can be optional to allow things like: method(array $foo = null) + if ($reflectionMethod->getNumberOfParameters() >= 1) { + return array($reflectionMethod, $prefix); + } + } catch (\ReflectionException $reflectionException) { + // Try the next prefix if the method doesn't exist } - } catch (\ReflectionException $reflectionException) { - // Try the next prefix if the method doesn't exist } } } @@ -330,16 +346,50 @@ private function getMutatorMethod($class, $property) /** * Extracts a property name from a method name. * - * @param string $methodName + * @param string $methodName + * @param \ReflectionProperty[] $reflectionProperties * * @return string */ - private function getPropertyName($methodName) + private function getPropertyName($methodName, array $reflectionProperties) { $pattern = implode('|', array_merge(self::$accessorPrefixes, self::$mutatorPrefixes)); if (preg_match('/^('.$pattern.')(.+)$/i', $methodName, $matches)) { + if (!in_array($matches[1], self::$arrayMutatorPrefixes)) { + return $matches[2]; + } + + foreach ($reflectionProperties as $reflectionProperty) { + foreach ($this->getSingulars($reflectionProperty->name) as $name) { + if ($name === lcfirst($matches[2])) { + return $reflectionProperty->name; + } + } + } + return $matches[2]; } } + + /** + * Gets singulars of a word. + * + * @param string $plural + * + * @return array|null Returns null if cannot guess. + */ + private function getSingulars($plural) + { + if (!class_exists('Symfony\Component\PropertyAccess\StringUtil')) { + return; + } + + $singulars = StringUtil::singularify($plural); + if (is_string($singulars)) { + $singulars = array($singulars); + } + + return $singulars; + } } diff --git a/src/Symfony/Component/PropertyInfo/Tests/Extractors/ReflectionExtractorTest.php b/src/Symfony/Component/PropertyInfo/Tests/Extractors/ReflectionExtractorTest.php index dfc13c025eb41..dff4e731b43a3 100644 --- a/src/Symfony/Component/PropertyInfo/Tests/Extractors/ReflectionExtractorTest.php +++ b/src/Symfony/Component/PropertyInfo/Tests/Extractors/ReflectionExtractorTest.php @@ -12,6 +12,7 @@ namespace Symfony\Component\PropertyInfo\Tests\Extractor; use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor; +use Symfony\Component\PropertyInfo\Tests\Fixtures\AdderRemoverDummy; use Symfony\Component\PropertyInfo\Type; /** @@ -119,4 +120,11 @@ public function testIsWritable() $this->assertTrue($this->extractor->isWritable('Symfony\Component\PropertyInfo\Tests\Fixtures\Dummy', 'e', array())); $this->assertTrue($this->extractor->isWritable('Symfony\Component\PropertyInfo\Tests\Fixtures\Dummy', 'f', array())); } + + public function testSingularize() + { + $this->assertTrue($this->extractor->isWritable(AdderRemoverDummy::class, 'analyses')); + $this->assertTrue($this->extractor->isWritable(AdderRemoverDummy::class, 'feet')); + $this->assertEquals(array('analyses', 'feet'), $this->extractor->getProperties(AdderRemoverDummy::class)); + } } diff --git a/src/Symfony/Component/PropertyInfo/Tests/Fixtures/AdderRemoverDummy.php b/src/Symfony/Component/PropertyInfo/Tests/Fixtures/AdderRemoverDummy.php new file mode 100644 index 0000000000000..1c2822e5784ca --- /dev/null +++ b/src/Symfony/Component/PropertyInfo/Tests/Fixtures/AdderRemoverDummy.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\PropertyInfo\Tests\Fixtures; + +/** + * @author Kévin Dunglas + */ +class AdderRemoverDummy +{ + private $analyses; + private $feet; + + public function addAnalyse(Dummy $analyse) + { + } + + public function removeFoot(Dummy $foot) + { + } +} diff --git a/src/Symfony/Component/PropertyInfo/composer.json b/src/Symfony/Component/PropertyInfo/composer.json index dfc345fc53415..0926563c680dd 100644 --- a/src/Symfony/Component/PropertyInfo/composer.json +++ b/src/Symfony/Component/PropertyInfo/composer.json @@ -27,6 +27,7 @@ }, "require-dev": { "symfony/serializer": "~2.8|~3.0", + "symfony/property-acccess": "~2.8|~3.0", "symfony/cache": "~3.1", "phpdocumentor/reflection-docblock": "^3.0", "doctrine/annotations": "~1.0" @@ -38,7 +39,8 @@ "psr/cache-implementation": "To cache results", "symfony/doctrine-bridge": "To use Doctrine metadata", "phpdocumentor/reflection-docblock": "To use the PHPDoc", - "symfony/serializer": "To use Serializer metadata" + "symfony/serializer": "To use Serializer metadata", + "symfony/property-access": "To detect singular adders and removers" }, "autoload": { "psr-4": { "Symfony\\Component\\PropertyInfo\\": "" }, From 7d93fb2db7210bf7b7a947842929e835589fa72c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Mon, 28 Mar 2016 11:50:15 +0200 Subject: [PATCH 2/7] Fix require-dev --- src/Symfony/Component/PropertyInfo/composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Component/PropertyInfo/composer.json b/src/Symfony/Component/PropertyInfo/composer.json index 0926563c680dd..7ff4c3eb9dd09 100644 --- a/src/Symfony/Component/PropertyInfo/composer.json +++ b/src/Symfony/Component/PropertyInfo/composer.json @@ -27,7 +27,7 @@ }, "require-dev": { "symfony/serializer": "~2.8|~3.0", - "symfony/property-acccess": "~2.8|~3.0", + "symfony/property-access": "~2.8|~3.0", "symfony/cache": "~3.1", "phpdocumentor/reflection-docblock": "^3.0", "doctrine/annotations": "~1.0" From 205c36fcec9f4c737e99bd5d7549f44682caadef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Mon, 18 Apr 2016 22:45:32 +0200 Subject: [PATCH 3/7] Integrate the new Inflector component --- .../Extractor/ReflectionExtractor.php | 20 ++++++------------- .../Component/PropertyInfo/composer.json | 7 +++---- 2 files changed, 9 insertions(+), 18 deletions(-) diff --git a/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php b/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php index 97559f3bec12d..b84c62ff60dac 100644 --- a/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php +++ b/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php @@ -11,7 +11,7 @@ namespace Symfony\Component\PropertyInfo\Extractor; -use Symfony\Component\PropertyAccess\StringUtil; +use Symfony\Component\Inflector\Inflector; use Symfony\Component\PropertyInfo\PropertyAccessExtractorInterface; use Symfony\Component\PropertyInfo\PropertyListExtractorInterface; use Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface; @@ -317,15 +317,11 @@ private function getAccessorMethod($class, $property) private function getMutatorMethod($class, $property) { $ucProperty = ucfirst($property); - $singulars = $this->getSingulars($ucProperty); + $names = $this->getSingulars($ucProperty); foreach (self::$mutatorPrefixes as $prefix) { - - if (null !== $singulars && in_array($prefix, self::$arrayMutatorPrefixes)) { - $names = $singulars; - $names[] = $ucProperty; - } else { - $names = array($ucProperty); + if (in_array($prefix, self::$arrayMutatorPrefixes)) { + array_unshift($names, $ucProperty); } foreach ($names as $name) { @@ -377,15 +373,11 @@ private function getPropertyName($methodName, array $reflectionProperties) * * @param string $plural * - * @return array|null Returns null if cannot guess. + * @return array */ private function getSingulars($plural) { - if (!class_exists('Symfony\Component\PropertyAccess\StringUtil')) { - return; - } - - $singulars = StringUtil::singularify($plural); + $singulars = Inflector::singularize($plural); if (is_string($singulars)) { $singulars = array($singulars); } diff --git a/src/Symfony/Component/PropertyInfo/composer.json b/src/Symfony/Component/PropertyInfo/composer.json index 7ff4c3eb9dd09..d2b4d0c1473a6 100644 --- a/src/Symfony/Component/PropertyInfo/composer.json +++ b/src/Symfony/Component/PropertyInfo/composer.json @@ -23,11 +23,11 @@ } ], "require": { - "php": ">=5.5.9" + "php": ">=5.5.9", + "symfony/inflector": "~3.1" }, "require-dev": { "symfony/serializer": "~2.8|~3.0", - "symfony/property-access": "~2.8|~3.0", "symfony/cache": "~3.1", "phpdocumentor/reflection-docblock": "^3.0", "doctrine/annotations": "~1.0" @@ -39,8 +39,7 @@ "psr/cache-implementation": "To cache results", "symfony/doctrine-bridge": "To use Doctrine metadata", "phpdocumentor/reflection-docblock": "To use the PHPDoc", - "symfony/serializer": "To use Serializer metadata", - "symfony/property-access": "To detect singular adders and removers" + "symfony/serializer": "To use Serializer metadata" }, "autoload": { "psr-4": { "Symfony\\Component\\PropertyInfo\\": "" }, From 5afca9d619043b5e4c7bc421ae5dec949051899c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Mon, 2 May 2016 22:36:57 +0200 Subject: [PATCH 4/7] Better implementation --- .../Extractor/ReflectionExtractor.php | 26 ++++--------------- 1 file changed, 5 insertions(+), 21 deletions(-) diff --git a/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php b/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php index b84c62ff60dac..dd0a3e76a4d69 100644 --- a/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php +++ b/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php @@ -27,7 +27,7 @@ class ReflectionExtractor implements PropertyListExtractorInterface, PropertyTyp /** * @internal * - * @var string[] + * @var array[] */ public static $mutatorPrefixes = array('add', 'remove', 'set'); @@ -317,11 +317,12 @@ private function getAccessorMethod($class, $property) private function getMutatorMethod($class, $property) { $ucProperty = ucfirst($property); - $names = $this->getSingulars($ucProperty); + $ucSingulars = (array) Inflector::singularize($ucProperty); foreach (self::$mutatorPrefixes as $prefix) { + $names = array($ucProperty); if (in_array($prefix, self::$arrayMutatorPrefixes)) { - array_unshift($names, $ucProperty); + $names = array_merge($names, $ucSingulars); } foreach ($names as $name) { @@ -357,7 +358,7 @@ private function getPropertyName($methodName, array $reflectionProperties) } foreach ($reflectionProperties as $reflectionProperty) { - foreach ($this->getSingulars($reflectionProperty->name) as $name) { + foreach ((array) Inflector::singularize($reflectionProperty->name) as $name) { if ($name === lcfirst($matches[2])) { return $reflectionProperty->name; } @@ -367,21 +368,4 @@ private function getPropertyName($methodName, array $reflectionProperties) return $matches[2]; } } - - /** - * Gets singulars of a word. - * - * @param string $plural - * - * @return array - */ - private function getSingulars($plural) - { - $singulars = Inflector::singularize($plural); - if (is_string($singulars)) { - $singulars = array($singulars); - } - - return $singulars; - } } From 771316bbe894e8c717f5f09a9a138c088d3106e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Mon, 2 May 2016 22:39:11 +0200 Subject: [PATCH 5/7] Revert PHPDoc changes --- .../Component/PropertyInfo/Extractor/ReflectionExtractor.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php b/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php index dd0a3e76a4d69..a82ac4e3987ac 100644 --- a/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php +++ b/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php @@ -27,7 +27,7 @@ class ReflectionExtractor implements PropertyListExtractorInterface, PropertyTyp /** * @internal * - * @var array[] + * @var string[] */ public static $mutatorPrefixes = array('add', 'remove', 'set'); @@ -41,7 +41,7 @@ class ReflectionExtractor implements PropertyListExtractorInterface, PropertyTyp /** * @internal * - * @var string[] + * @var array[] */ public static $arrayMutatorPrefixes = array('add', 'remove'); From f36d34ffe1db1ef17662daed1a6ce467bb0983f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Wed, 4 May 2016 08:39:01 +0200 Subject: [PATCH 6/7] Use strtolower before comp --- .../Component/PropertyInfo/Extractor/ReflectionExtractor.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php b/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php index a82ac4e3987ac..81dbff74b9be4 100644 --- a/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php +++ b/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php @@ -359,7 +359,7 @@ private function getPropertyName($methodName, array $reflectionProperties) foreach ($reflectionProperties as $reflectionProperty) { foreach ((array) Inflector::singularize($reflectionProperty->name) as $name) { - if ($name === lcfirst($matches[2])) { + if (strtolower($name) === strtolower($matches[2])) { return $reflectionProperty->name; } } From c7800ac7125a3bc1993a68c99a01c4c1ac41ec5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Thu, 5 May 2016 20:06:52 +0200 Subject: [PATCH 7/7] Fix comment --- .../Component/PropertyInfo/Extractor/ReflectionExtractor.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php b/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php index 81dbff74b9be4..0a43d018eeca0 100644 --- a/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php +++ b/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php @@ -334,7 +334,7 @@ private function getMutatorMethod($class, $property) return array($reflectionMethod, $prefix); } } catch (\ReflectionException $reflectionException) { - // Try the next prefix if the method doesn't exist + // Try the next one if method does not exist } } }