From 185d0e588b5afb07917b4a4a81edc33143d9ae21 Mon Sep 17 00:00:00 2001 From: Evan Villemez Date: Fri, 24 Aug 2012 11:10:25 -0400 Subject: [PATCH 1/5] updated JMSMetadataParser to support nested models --- Parser/JmsMetadataParser.php | 81 +++++++++++++++++++++-- Parser/ParserInterface.php | 2 + Tests/Fixtures/Model/JmsNested.php | 20 ++++++ Tests/Fixtures/Model/JmsTest.php | 10 +++ Tests/Formatter/MarkdownFormatterTest.php | 12 ++++ Tests/Formatter/SimpleFormatterTest.php | 42 +++++++++++- 6 files changed, 161 insertions(+), 6 deletions(-) create mode 100644 Tests/Fixtures/Model/JmsNested.php diff --git a/Parser/JmsMetadataParser.php b/Parser/JmsMetadataParser.php index 34535bf..ced2ac0 100644 --- a/Parser/JmsMetadataParser.php +++ b/Parser/JmsMetadataParser.php @@ -61,26 +61,97 @@ class JmsMetadataParser implements ParserInterface if (!is_null($item->type)) { $name = isset($item->serializedName) ? $item->serializedName : $item->name; - //TODO: check for nested type - $params[$name] = array( - 'dataType' => $item->type, + 'dataType' => $this->getNormalizedType($item->type), 'required' => false, //TODO: can't think of a good way to specify this one, JMS doesn't have a setting for this 'description' => $this->getDescription($input, $item->name), 'readonly' => $item->readOnly ); + + //check for nested classes w/ JMS metadata + if ($nestedInputClass = $this->getNestedClass($item->type)) { + $params[$name]['children'] = $this->parse($nestedInputClass); + } } } return $params; } + + /** + * There are various ways via JMS to declare arrays of objects, but that's an internal + * implementation detail. + * + * @param string $type + * @return string + */ + protected function getNormalizedType($type) + { + if (in_array($type, array('boolean', 'integer', 'string', 'double', 'array', 'DateTime'))) { + return $type; + } + + if(false !== strpos($type, "array") || false !== strpos($type, "ArrayCollection")) { + if ($nested = $this->getNestedClassInArray($type)) { + $exp = explode("\\", $nested); + return sprintf("array of objects (%s)", end($exp)); + } else { + return "array"; + } + } + + $exp = explode("\\", $type); + return sprintf("object (%s)", end($exp)); + } + + /** + * Check the various ways JMS describes custom classes in arrays, and + * get the name of the class in the array, if available. + * + * @param string $type + * @return string|false + */ + protected function getNestedClassInArray($type) + { + + //could be some type of array with , or + $regEx = "/\<([A-Za-z0-9\\\]*)(\,?\s?(.*))?\>/"; + if (preg_match($regEx, $type, $matches)) { + $matched = (!empty($matches[3])) ? $matches[3] : $matches[1]; + return in_array($matched, array('boolean', 'integer', 'string', 'double', 'array', 'DateTime')) ? false : $matched; + } + + return false; + } + + /** + * Scan the JMS Serializer types for reference to a class. + * + * http://jmsyst.com/bundles/JMSSerializerBundle/master/reference/annotations#type + * + * @param string $type + * @return string|false + */ + protected function getNestedClass($type) + { + if (in_array($type, array('boolean', 'integer', 'string', 'double', 'array', 'DateTime'))) { + return false; + } + + //could be a nested object of some sort + if ($nested = $this->getNestedClassInArray($type)) { + return $nested; + } + + //or could just be a class name (potentially) + return (null === $this->factory->getMetadataForClass($type)) ? false : $type; + } protected function getDescription($className, $propertyName) { $description = "No description."; - //TODO: regex comment to get description - or move doc comment parsing functionality from `ApiDocExtractor` to a new location - //in order to reuse it here + //TODO: abstract docblock parsing utility and implement here return $description; } diff --git a/Parser/ParserInterface.php b/Parser/ParserInterface.php index 8df98f5..fcdadf2 100644 --- a/Parser/ParserInterface.php +++ b/Parser/ParserInterface.php @@ -31,6 +31,8 @@ interface ParserInterface * - required boolean * - description string * - readonly boolean + * - children (optional) array of nested property names mapped to arrays + * in the format described here * * @param string $item The string type of input to parse. * @return array diff --git a/Tests/Fixtures/Model/JmsNested.php b/Tests/Fixtures/Model/JmsNested.php new file mode 100644 index 0000000..86ba05c --- /dev/null +++ b/Tests/Fixtures/Model/JmsNested.php @@ -0,0 +1,20 @@ +"); + */ + public $nestedArray; } diff --git a/Tests/Formatter/MarkdownFormatterTest.php b/Tests/Formatter/MarkdownFormatterTest.php index d058ae5..779f1a9 100644 --- a/Tests/Formatter/MarkdownFormatterTest.php +++ b/Tests/Formatter/MarkdownFormatterTest.php @@ -185,6 +185,18 @@ arr: * required: false * description: No description. +nested: + + * type: object (JmsNested) + * required: false + * description: No description. + +nestedArray: + + * type: array of objects (JmsNested) + * required: false + * description: No description. + ### `ANY` /my-commented/{id}/{page} ### diff --git a/Tests/Formatter/SimpleFormatterTest.php b/Tests/Formatter/SimpleFormatterTest.php index 6868d28..e02f130 100644 --- a/Tests/Formatter/SimpleFormatterTest.php +++ b/Tests/Formatter/SimpleFormatterTest.php @@ -210,7 +210,47 @@ class SimpleFormatterTest extends WebTestCase 'required' => false, 'description' => 'No description.', 'readonly' => false - ) + ), + 'nested' => array( + 'dataType' => 'object (JmsNested)', + 'required' => false, + 'description' => 'No description.', + 'readonly' => false, + 'children' => array( + 'foo' => array( + 'dataType' => 'DateTime', + 'required' => false, + 'description' => 'No description.', + 'readonly' => true, + ), + 'bar' => array( + 'dataType' => 'string', + 'required' => false, + 'description' => 'No description.', + 'readonly' => false, + ) + ) + ), + 'nestedArray' => array( + 'dataType' => 'array of objects (JmsNested)', + 'required' => false, + 'description' => 'No description.', + 'readonly' => false, + 'children' => array( + 'foo' => array( + 'dataType' => 'DateTime', + 'required' => false, + 'description' => 'No description.', + 'readonly' => true, + ), + 'bar' => array( + 'dataType' => 'string', + 'required' => false, + 'description' => 'No description.', + 'readonly' => false, + ) + ) + ), ), 'description' => 'Testing JMS' ), From 9b94ae4b2caba6fc49cdc7b672831ffe57c84620 Mon Sep 17 00:00:00 2001 From: Evan Villemez Date: Fri, 24 Aug 2012 11:12:36 -0400 Subject: [PATCH 2/5] fixed cs --- Parser/JmsMetadataParser.php | 30 ++++++++++++++++-------------- Tests/Fixtures/Model/JmsNested.php | 2 +- Tests/Fixtures/Model/JmsTest.php | 4 ++-- 3 files changed, 19 insertions(+), 17 deletions(-) diff --git a/Parser/JmsMetadataParser.php b/Parser/JmsMetadataParser.php index ced2ac0..b7e57d9 100644 --- a/Parser/JmsMetadataParser.php +++ b/Parser/JmsMetadataParser.php @@ -67,7 +67,7 @@ class JmsMetadataParser implements ParserInterface 'description' => $this->getDescription($input, $item->name), 'readonly' => $item->readOnly ); - + //check for nested classes w/ JMS metadata if ($nestedInputClass = $this->getNestedClass($item->type)) { $params[$name]['children'] = $this->parse($nestedInputClass); @@ -77,12 +77,12 @@ class JmsMetadataParser implements ParserInterface return $params; } - + /** * There are various ways via JMS to declare arrays of objects, but that's an internal * implementation detail. * - * @param string $type + * @param string $type * @return string */ protected function getNormalizedType($type) @@ -90,25 +90,27 @@ class JmsMetadataParser implements ParserInterface if (in_array($type, array('boolean', 'integer', 'string', 'double', 'array', 'DateTime'))) { return $type; } - - if(false !== strpos($type, "array") || false !== strpos($type, "ArrayCollection")) { + + if (false !== strpos($type, "array") || false !== strpos($type, "ArrayCollection")) { if ($nested = $this->getNestedClassInArray($type)) { $exp = explode("\\", $nested); + return sprintf("array of objects (%s)", end($exp)); } else { return "array"; } } - + $exp = explode("\\", $type); + return sprintf("object (%s)", end($exp)); } - + /** * Check the various ways JMS describes custom classes in arrays, and * get the name of the class in the array, if available. * - * @param string $type + * @param string $type * @return string|false */ protected function getNestedClassInArray($type) @@ -118,18 +120,19 @@ class JmsMetadataParser implements ParserInterface $regEx = "/\<([A-Za-z0-9\\\]*)(\,?\s?(.*))?\>/"; if (preg_match($regEx, $type, $matches)) { $matched = (!empty($matches[3])) ? $matches[3] : $matches[1]; + return in_array($matched, array('boolean', 'integer', 'string', 'double', 'array', 'DateTime')) ? false : $matched; } - + return false; } - + /** * Scan the JMS Serializer types for reference to a class. * * http://jmsyst.com/bundles/JMSSerializerBundle/master/reference/annotations#type * - * @param string $type + * @param string $type * @return string|false */ protected function getNestedClass($type) @@ -137,12 +140,12 @@ class JmsMetadataParser implements ParserInterface if (in_array($type, array('boolean', 'integer', 'string', 'double', 'array', 'DateTime'))) { return false; } - + //could be a nested object of some sort if ($nested = $this->getNestedClassInArray($type)) { return $nested; } - + //or could just be a class name (potentially) return (null === $this->factory->getMetadataForClass($type)) ? false : $type; } @@ -152,7 +155,6 @@ class JmsMetadataParser implements ParserInterface $description = "No description."; //TODO: abstract docblock parsing utility and implement here - return $description; } diff --git a/Tests/Fixtures/Model/JmsNested.php b/Tests/Fixtures/Model/JmsNested.php index 86ba05c..3a29503 100644 --- a/Tests/Fixtures/Model/JmsNested.php +++ b/Tests/Fixtures/Model/JmsNested.php @@ -16,5 +16,5 @@ class JmsNested * @JMS\Type("string"); */ public $bar; - + } diff --git a/Tests/Fixtures/Model/JmsTest.php b/Tests/Fixtures/Model/JmsTest.php index b1968ff..d47741c 100644 --- a/Tests/Fixtures/Model/JmsTest.php +++ b/Tests/Fixtures/Model/JmsTest.php @@ -29,12 +29,12 @@ class JmsTest * @JMS\Type("array"); */ public $arr; - + /** * @JMS\Type("Nelmio\ApiDocBundle\Tests\Fixtures\Model\JmsNested"); */ public $nested; - + /** * @JMS\Type("array"); */ From d4fe98230003ad43a27ec6bd12beeec011ca37fb Mon Sep 17 00:00:00 2001 From: Evan Villemez Date: Fri, 24 Aug 2012 13:32:52 -0400 Subject: [PATCH 3/5] rebased, fixed cs --- Parser/JmsMetadataParser.php | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/Parser/JmsMetadataParser.php b/Parser/JmsMetadataParser.php index b7e57d9..d557587 100644 --- a/Parser/JmsMetadataParser.php +++ b/Parser/JmsMetadataParser.php @@ -96,9 +96,9 @@ class JmsMetadataParser implements ParserInterface $exp = explode("\\", $nested); return sprintf("array of objects (%s)", end($exp)); - } else { - return "array"; } + + return "array"; } $exp = explode("\\", $type); @@ -111,11 +111,10 @@ class JmsMetadataParser implements ParserInterface * get the name of the class in the array, if available. * * @param string $type - * @return string|false + * @return string|null */ protected function getNestedClassInArray($type) { - //could be some type of array with , or $regEx = "/\<([A-Za-z0-9\\\]*)(\,?\s?(.*))?\>/"; if (preg_match($regEx, $type, $matches)) { @@ -124,7 +123,7 @@ class JmsMetadataParser implements ParserInterface return in_array($matched, array('boolean', 'integer', 'string', 'double', 'array', 'DateTime')) ? false : $matched; } - return false; + return null; } /** @@ -133,7 +132,7 @@ class JmsMetadataParser implements ParserInterface * http://jmsyst.com/bundles/JMSSerializerBundle/master/reference/annotations#type * * @param string $type - * @return string|false + * @return string|null */ protected function getNestedClass($type) { @@ -147,7 +146,7 @@ class JmsMetadataParser implements ParserInterface } //or could just be a class name (potentially) - return (null === $this->factory->getMetadataForClass($type)) ? false : $type; + return (null === $this->factory->getMetadataForClass($type)) ? null : $type; } protected function getDescription($className, $propertyName) From 01fce41a0e39938546c3207f6c263f041c9bd46c Mon Sep 17 00:00:00 2001 From: Evan Villemez Date: Fri, 24 Aug 2012 14:34:48 -0400 Subject: [PATCH 4/5] more accurate reporting of arrays and their types --- Parser/JmsMetadataParser.php | 92 ++++++++++++------------- Tests/Fixtures/Model/JmsNested.php | 7 ++ Tests/Formatter/SimpleFormatterTest.php | 12 ++++ 3 files changed, 64 insertions(+), 47 deletions(-) diff --git a/Parser/JmsMetadataParser.php b/Parser/JmsMetadataParser.php index d557587..2974c41 100644 --- a/Parser/JmsMetadataParser.php +++ b/Parser/JmsMetadataParser.php @@ -61,16 +61,18 @@ class JmsMetadataParser implements ParserInterface if (!is_null($item->type)) { $name = isset($item->serializedName) ? $item->serializedName : $item->name; + $dataType = $this->processDataType($item->type); + $params[$name] = array( - 'dataType' => $this->getNormalizedType($item->type), + 'dataType' => $dataType['normalized'], 'required' => false, //TODO: can't think of a good way to specify this one, JMS doesn't have a setting for this 'description' => $this->getDescription($input, $item->name), 'readonly' => $item->readOnly ); //check for nested classes w/ JMS metadata - if ($nestedInputClass = $this->getNestedClass($item->type)) { - $params[$name]['children'] = $this->parse($nestedInputClass); + if ($dataType['class'] && null !== $this->factory->getMetadataForClass($dataType['class'])) { + $params[$name]['children'] = $this->parse($dataType['class']); } } } @@ -79,76 +81,72 @@ class JmsMetadataParser implements ParserInterface } /** - * There are various ways via JMS to declare arrays of objects, but that's an internal - * implementation detail. + * Figure out a normalized data type (for documentation), and get a + * nested class name, if available. * * @param string $type - * @return string + * @return array */ - protected function getNormalizedType($type) + protected function processDataType($type) { - if (in_array($type, array('boolean', 'integer', 'string', 'double', 'array', 'DateTime'))) { - return $type; + + //could be basic type + if ($this->isPrimitive($type)) { + return array( + 'normalized' => $type, + 'class' => null + ); } - if (false !== strpos($type, "array") || false !== strpos($type, "ArrayCollection")) { - if ($nested = $this->getNestedClassInArray($type)) { - $exp = explode("\\", $nested); - - return sprintf("array of objects (%s)", end($exp)); + //check for a type inside something that could be treated as an array + if ($nestedType = $this->getNestedTypeInArray($type)) { + if ($this->isPrimitive($nestedType)) { + return array( + 'normalized' => sprintf("array of %ss", $nestedType), + 'class' => null + ); } - - return "array"; + + $exp = explode("\\", $nestedType); + + return array( + 'normalized' => sprintf("array of objects (%s)", end($exp)), + 'class' => $nestedType + ); } + //if we got this far, it's a general class name $exp = explode("\\", $type); - return sprintf("object (%s)", end($exp)); + return array( + 'normalized' => sprintf("object (%s)", end($exp)), + 'class' => $type + ); + } + + protected function isPrimitive($type) + { + return in_array($type, array('boolean', 'integer', 'string', 'double', 'array', 'DateTime')); } /** - * Check the various ways JMS describes custom classes in arrays, and - * get the name of the class in the array, if available. + * Check the various ways JMS describes values in arrays, and + * get the value type in the array * - * @param string $type + * @param string $type * @return string|null */ - protected function getNestedClassInArray($type) + protected function getNestedTypeInArray($type) { //could be some type of array with , or $regEx = "/\<([A-Za-z0-9\\\]*)(\,?\s?(.*))?\>/"; if (preg_match($regEx, $type, $matches)) { - $matched = (!empty($matches[3])) ? $matches[3] : $matches[1]; - - return in_array($matched, array('boolean', 'integer', 'string', 'double', 'array', 'DateTime')) ? false : $matched; + return (!empty($matches[3])) ? $matches[3] : $matches[1]; } return null; } - /** - * Scan the JMS Serializer types for reference to a class. - * - * http://jmsyst.com/bundles/JMSSerializerBundle/master/reference/annotations#type - * - * @param string $type - * @return string|null - */ - protected function getNestedClass($type) - { - if (in_array($type, array('boolean', 'integer', 'string', 'double', 'array', 'DateTime'))) { - return false; - } - - //could be a nested object of some sort - if ($nested = $this->getNestedClassInArray($type)) { - return $nested; - } - - //or could just be a class name (potentially) - return (null === $this->factory->getMetadataForClass($type)) ? null : $type; - } - protected function getDescription($className, $propertyName) { $description = "No description."; diff --git a/Tests/Fixtures/Model/JmsNested.php b/Tests/Fixtures/Model/JmsNested.php index 3a29503..9008446 100644 --- a/Tests/Fixtures/Model/JmsNested.php +++ b/Tests/Fixtures/Model/JmsNested.php @@ -17,4 +17,11 @@ class JmsNested */ public $bar; + /** + * Epic description. + * + * @JMS\Type("array") + */ + public $baz; + } diff --git a/Tests/Formatter/SimpleFormatterTest.php b/Tests/Formatter/SimpleFormatterTest.php index e02f130..fc9df56 100644 --- a/Tests/Formatter/SimpleFormatterTest.php +++ b/Tests/Formatter/SimpleFormatterTest.php @@ -228,6 +228,12 @@ class SimpleFormatterTest extends WebTestCase 'required' => false, 'description' => 'No description.', 'readonly' => false, + ), + 'baz' => array( + 'dataType' => 'array of integers', + 'required' => false, + 'description' => 'No description.', + 'readonly' => false, ) ) ), @@ -248,6 +254,12 @@ class SimpleFormatterTest extends WebTestCase 'required' => false, 'description' => 'No description.', 'readonly' => false, + ), + 'baz' => array( + 'dataType' => 'array of integers', + 'required' => false, + 'description' => 'No description.', + 'readonly' => false, ) ) ), From ea3fb696274b85e36ba7f291a44b3e5fe0eed31e Mon Sep 17 00:00:00 2001 From: Evan Villemez Date: Mon, 27 Aug 2012 12:25:18 -0400 Subject: [PATCH 5/5] fixed cs --- Parser/JmsMetadataParser.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Parser/JmsMetadataParser.php b/Parser/JmsMetadataParser.php index 2974c41..d8b3990 100644 --- a/Parser/JmsMetadataParser.php +++ b/Parser/JmsMetadataParser.php @@ -70,7 +70,7 @@ class JmsMetadataParser implements ParserInterface 'readonly' => $item->readOnly ); - //check for nested classes w/ JMS metadata + //check for nested classes with JMS metadata if ($dataType['class'] && null !== $this->factory->getMetadataForClass($dataType['class'])) { $params[$name]['children'] = $this->parse($dataType['class']); } @@ -89,7 +89,6 @@ class JmsMetadataParser implements ParserInterface */ protected function processDataType($type) { - //could be basic type if ($this->isPrimitive($type)) { return array(