diff --git a/Parser/JmsMetadataParser.php b/Parser/JmsMetadataParser.php index 0753501..93e3298 100644 --- a/Parser/JmsMetadataParser.php +++ b/Parser/JmsMetadataParser.php @@ -32,8 +32,6 @@ class JmsMetadataParser implements ParserInterface */ private $commentExtractor; - private $parsedClasses = array(); - /** * Constructor, requires JMS Metadata factory */ @@ -63,10 +61,23 @@ class JmsMetadataParser implements ParserInterface */ public function parse($input) { - $meta = $this->factory->getMetadataForClass($input); + return $this->doParse($input); + } + + /** + * Recursively parse all metadata for a class + * + * @param string $className Class to get all metadata for + * @param array $visited Classes we've already visited to prevent infinite recursion. + * @return array metadata for given class + * @throws \InvalidArgumentException + */ + protected function doParse($className, $visited = array()) + { + $meta = $this->factory->getMetadataForClass($className); if (null === $meta) { - throw new \InvalidArgumentException(sprintf("No metadata found for class %s", $input)); + throw new \InvalidArgumentException(sprintf("No metadata found for class %s", $className)); } $params = array(); @@ -81,23 +92,22 @@ class JmsMetadataParser implements ParserInterface $params[$name] = array( '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), + 'description' => $this->getDescription($className, $item), 'readonly' => $item->readOnly ); // if class already parsed, continue, to avoid infinite recursion - if (in_array($dataType['class'], $this->parsedClasses)) { + if (in_array($dataType['class'], $visited)) { continue; } // check for nested classes with JMS metadata if ($dataType['class'] && null !== $this->factory->getMetadataForClass($dataType['class'])) { - $this->parsedClasses[] = $dataType['class']; - $params[$name]['children'] = $this->parse($dataType['class']); + $visited[] = $dataType['class']; + $params[$name]['children'] = $this->doParse($dataType['class'], $visited); } } } - $this->parsedClasses = array(); return $params; } diff --git a/Tests/Fixtures/Model/JmsNested.php b/Tests/Fixtures/Model/JmsNested.php index 90b0348..37162cc 100644 --- a/Tests/Fixtures/Model/JmsNested.php +++ b/Tests/Fixtures/Model/JmsNested.php @@ -26,4 +26,14 @@ class JmsNested */ public $baz; + /** + * @JMS\Type("Nelmio\ApiDocBundle\Tests\Fixtures\Model\JmsNested"); + */ + public $circular; + + /** + * @JMS\Type("Nelmio\ApiDocBundle\Tests\Fixtures\Model\JmsTest"); + */ + public $parent; + } diff --git a/Tests/Formatter/MarkdownFormatterTest.php b/Tests/Formatter/MarkdownFormatterTest.php index 2c5bf06..f972363 100644 --- a/Tests/Formatter/MarkdownFormatterTest.php +++ b/Tests/Formatter/MarkdownFormatterTest.php @@ -205,25 +205,53 @@ nested[baz][]: With multiple lines. -nestedArray[]: +nested[circular]: - * type: array of objects (JmsNested) + * type: object (JmsNested) * required: false * description: No description. -nestedArray[][bar]: +nested[parent]: + + * type: object (JmsTest) + * required: false + * description: No description. + +nested[parent][foo]: * type: string * required: false * description: No description. -nestedArray[][baz][]: +nested[parent][number]: - * type: array of integers + * type: double * required: false - * description: Epic description. + * description: No description. -With multiple lines. +nested[parent][arr]: + + * type: array + * required: false + * description: No description. + +nested[parent][nested]: + + * type: object (JmsNested) + * required: false + * description: No description. + +nested[parent][nestedArray][]: + + * type: array of objects (JmsNested) + * required: false + * description: No description. + +nestedArray[]: + + * type: array of objects (JmsNested) + * required: false + * description: No description. ### `GET` /jms-return-test ### @@ -270,7 +298,7 @@ _This method is useful to test if the getDocComment works._ **id** - - Requirement: \d+ + - Requirement: \\d+ ### `GET` /z-action-with-query-param ### @@ -280,7 +308,7 @@ _This method is useful to test if the getDocComment works._ page: - * Requirement: \d+ + * Requirement: \\d+ * Description: Page of the overview. diff --git a/Tests/Formatter/SimpleFormatterTest.php b/Tests/Formatter/SimpleFormatterTest.php index f56cf85..7da0bf8 100644 --- a/Tests/Formatter/SimpleFormatterTest.php +++ b/Tests/Formatter/SimpleFormatterTest.php @@ -25,156 +25,180 @@ class SimpleFormatterTest extends WebTestCase restore_error_handler(); $result = $container->get('nelmio_api_doc.formatter.simple_formatter')->format($data); - $expected = array( + $expected = array ( '/tests' => - array( + array ( 0 => - array( + array ( 'method' => 'GET', 'uri' => '/tests.{_format}', + 'description' => 'index action', 'filters' => - array( + array ( 'a' => - array( + array ( 'dataType' => 'integer', ), 'b' => - array( + array ( 'dataType' => 'string', 'arbitrary' => - array( + array ( 0 => 'arg1', 1 => 'arg2', ), ), ), - 'description' => 'index action', - 'requirements' => array( - '_format' => array('dataType' => '', 'description' => '', 'requirement' => ''), + 'requirements' => + array ( + '_format' => + array ( + 'requirement' => '', + 'dataType' => '', + 'description' => '', + ), ), 'https' => false, 'authentication' => false, ), 1 => - array( + array ( 'method' => 'GET', 'uri' => '/tests.{_format}', + 'description' => 'index action', 'filters' => - array( + array ( 'a' => - array( + array ( 'dataType' => 'integer', ), 'b' => - array( + array ( 'dataType' => 'string', 'arbitrary' => - array( + array ( 0 => 'arg1', 1 => 'arg2', ), ), ), - 'description' => 'index action', - 'requirements' => array( - '_format' => array('dataType' => '', 'description' => '', 'requirement' => ''), + 'requirements' => + array ( + '_format' => + array ( + 'requirement' => '', + 'dataType' => '', + 'description' => '', + ), ), 'https' => false, 'authentication' => false, ), 2 => - array( + array ( 'method' => 'POST', 'uri' => '/tests.{_format}', + 'description' => 'create test', 'parameters' => - array( + array ( 'a' => - array( + array ( 'dataType' => 'string', 'required' => true, 'description' => 'A nice description', - 'readonly' => false + 'readonly' => false, ), 'b' => - array( + array ( 'dataType' => 'string', 'required' => false, 'description' => '', - 'readonly' => false + 'readonly' => false, ), 'c' => - array( + array ( 'dataType' => 'boolean', 'required' => true, 'description' => '', - 'readonly' => false + 'readonly' => false, ), ), - 'description' => 'create test', - 'requirements' => array( - '_format' => array('dataType' => '', 'description' => '', 'requirement' => ''), + 'requirements' => + array ( + '_format' => + array ( + 'requirement' => '', + 'dataType' => '', + 'description' => '', + ), ), 'https' => false, 'authentication' => false, ), 3 => - array( + array ( 'method' => 'POST', 'uri' => '/tests.{_format}', + 'description' => 'create test', 'parameters' => - array( + array ( 'a' => - array( + array ( 'dataType' => 'string', 'required' => true, 'description' => 'A nice description', - 'readonly' => false + 'readonly' => false, ), 'b' => - array( + array ( 'dataType' => 'string', 'required' => false, 'description' => '', - 'readonly' => false + 'readonly' => false, ), 'c' => - array( + array ( 'dataType' => 'boolean', 'required' => true, 'description' => '', - 'readonly' => false + 'readonly' => false, ), ), - 'description' => 'create test', - 'requirements' => array( - '_format' => array('dataType' => '', 'description' => '', 'requirement' => ''), + 'requirements' => + array ( + '_format' => + array ( + 'requirement' => '', + 'dataType' => '', + 'description' => '', + ), ), 'https' => false, 'authentication' => false, ), ), 'others' => - array( + array ( 0 => - array( + array ( 'method' => 'POST', 'uri' => '/another-post', + 'description' => 'create another test', 'parameters' => - array( + array ( 'a' => - array( + array ( 'dataType' => 'string', 'required' => true, 'description' => 'A nice description', - 'readonly' => false + 'readonly' => false, ), ), - 'description' => 'create another test', 'https' => false, 'authentication' => false, ), 1 => - array( + array ( 'method' => 'ANY', 'uri' => '/any', 'description' => 'Action without HTTP verb', @@ -182,14 +206,19 @@ class SimpleFormatterTest extends WebTestCase 'authentication' => false, ), 2 => - array( + array ( 'method' => 'ANY', 'uri' => '/any/{foo}', - 'requirements' => - array( - 'foo' => array('dataType' => '', 'description' => '', 'requirement' => ''), - ), 'description' => 'Action without HTTP verb', + 'requirements' => + array ( + 'foo' => + array ( + 'requirement' => '', + 'dataType' => '', + 'description' => '', + ), + ), 'https' => false, 'authentication' => false, ), @@ -204,94 +233,137 @@ class SimpleFormatterTest extends WebTestCase array( 'method' => 'POST', 'uri' => '/jms-input-test', + 'description' => 'Testing JMS', 'parameters' => - array( + array ( 'foo' => - array( + array ( 'dataType' => 'string', 'required' => false, 'description' => 'No description.', - 'readonly' => false + 'readonly' => false, ), 'bar' => - array( + array ( 'dataType' => 'DateTime', 'required' => false, 'description' => 'No description.', - 'readonly' => true + 'readonly' => true, ), 'number' => - array( + array ( 'dataType' => 'double', 'required' => false, 'description' => 'No description.', - 'readonly' => false + 'readonly' => false, ), 'arr' => - array( + array ( 'dataType' => 'array', 'required' => false, 'description' => 'No description.', - 'readonly' => false + 'readonly' => false, ), - 'nested' => array( + 'nested' => + array ( 'dataType' => 'object (JmsNested)', 'required' => false, 'description' => 'No description.', 'readonly' => false, - 'children' => array( - 'foo' => array( + 'children' => + array ( + 'foo' => + array ( 'dataType' => 'DateTime', 'required' => false, 'description' => 'No description.', 'readonly' => true, ), - 'bar' => array( + 'bar' => + array ( 'dataType' => 'string', 'required' => false, 'description' => 'No description.', 'readonly' => false, ), - 'baz' => array( + 'baz' => + array ( 'dataType' => 'array of integers', 'required' => false, 'description' => 'Epic description. With multiple lines.', 'readonly' => false, - ) - ) + ), + 'circular' => + array ( + 'dataType' => 'object (JmsNested)', + 'required' => false, + 'description' => 'No description.', + 'readonly' => false, + ), + 'parent' => + array ( + 'dataType' => 'object (JmsTest)', + 'required' => false, + 'description' => 'No description.', + 'readonly' => false, + 'children' => + array ( + 'foo' => + array ( + 'dataType' => 'string', + 'required' => false, + 'description' => 'No description.', + 'readonly' => false, + ), + 'bar' => + array ( + 'dataType' => 'DateTime', + 'required' => false, + 'description' => 'No description.', + 'readonly' => true, + ), + 'number' => + array ( + 'dataType' => 'double', + 'required' => false, + 'description' => 'No description.', + 'readonly' => false, + ), + 'arr' => + array ( + 'dataType' => 'array', + 'required' => false, + 'description' => 'No description.', + 'readonly' => false, + ), + 'nested' => + array ( + 'dataType' => 'object (JmsNested)', + 'required' => false, + 'description' => 'No description.', + 'readonly' => false, + ), + 'nestedArray' => + array ( + 'dataType' => 'array of objects (JmsNested)', + 'required' => false, + 'description' => 'No description.', + 'readonly' => false, + ), + ), + ), + ), ), - 'nestedArray' => array( + '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, - ), - 'baz' => array( - 'dataType' => 'array of integers', - 'required' => false, - 'description' => 'Epic description. - -With multiple lines.', - 'readonly' => false, - ) - ) ), ), - 'description' => 'Testing JMS', 'https' => false, 'authentication' => false, ), @@ -300,13 +372,15 @@ With multiple lines.', 'method' => 'GET', 'uri' => '/jms-return-test', 'description' => 'Testing return', - 'response' => array( - 'a' => array( + 'response' => + array ( + 'a' => + array ( 'dataType' => 'string', 'required' => true, 'description' => 'A nice description', - 'readonly' => false - ) + 'readonly' => false, + ), ), 'https' => false, 'authentication' => false, @@ -315,10 +389,23 @@ With multiple lines.', array( 'method' => 'ANY', 'uri' => '/my-commented/{id}/{page}', + 'description' => 'This method is useful to test if the getDocComment works.', + 'documentation' => 'This method is useful to test if the getDocComment works. +And, it supports multilines until the first \'@\' char.', 'requirements' => - array( - 'id' => array('dataType' => 'int', 'description' => 'A nice comment', 'requirement' => ''), - 'page' => array('dataType' => 'int', 'description' => '', 'requirement' => ''), + array ( + 'id' => + array ( + 'dataType' => 'int', + 'description' => 'A nice comment', + 'requirement' => '', + ), + 'page' => + array ( + 'dataType' => 'int', + 'description' => '', + 'requirement' => '', + ), ), 'https' => false, 'description' => 'This method is useful to test if the getDocComment works.', @@ -329,13 +416,13 @@ With multiple lines.', array( 'method' => 'ANY', 'uri' => '/secure-route', - // 'description' => '[secureRouteAction description]', - // 'documentation' => '[secureRouteAction description]', - 'requirements' => array( - '_scheme' => array( + 'requirements' => + array ( + '_scheme' => + array ( 'requirement' => 'https', - 'dataType' => null, - 'description' => null, + 'dataType' => '', + 'description' => '', ), ), 'https' => true, @@ -346,8 +433,13 @@ With multiple lines.', 'method' => 'ANY', 'uri' => '/yet-another/{id}', 'requirements' => - array( - 'id' => array('dataType' => '', 'description' => '', 'requirement' => '\d+') + array ( + 'id' => + array ( + 'requirement' => '\\d+', + 'dataType' => '', + 'description' => '', + ), ), 'https' => false, 'authentication' => false, @@ -357,8 +449,12 @@ With multiple lines.', 'method' => 'GET', 'uri' => '/z-action-with-query-param', 'filters' => - array( - 'page' => array('description' => 'Page of the overview.', 'requirement' => '\d+') + array ( + 'page' => + array ( + 'requirement' => '\\d+', + 'description' => 'Page of the overview.', + ), ), 'https' => false, 'authentication' => false, @@ -368,8 +464,14 @@ With multiple lines.', 'method' => 'POST', 'uri' => '/z-action-with-request-param', 'parameters' => - array( - 'param1' => array('description' => 'Param1 description.', 'required' => true, 'dataType' => 'string', 'readonly' => false) + array ( + 'param1' => + array ( + 'required' => true, + 'dataType' => 'string', + 'description' => 'Param1 description.', + 'readonly' => false, + ), ), 'https' => false, 'authentication' => false,