diff --git a/Annotation/ApiDoc.php b/Annotation/ApiDoc.php index 031814b..1c9c9d0 100644 --- a/Annotation/ApiDoc.php +++ b/Annotation/ApiDoc.php @@ -26,7 +26,7 @@ class ApiDoc /** * @var string */ - private $formType = null; + private $input = null; /** * @var string @@ -70,8 +70,8 @@ class ApiDoc public function __construct(array $data) { - if (isset($data['formType'])) { - $this->formType = $data['formType']; + if (isset($data['input'])) { + $this->input = $data['input']; } elseif (isset($data['filters'])) { foreach ($data['filters'] as $filter) { if (!isset($filter['name'])) { @@ -121,9 +121,9 @@ class ApiDoc /** * @return string|null */ - public function getFormType() + public function getInput() { - return $this->formType; + return $this->input; } /** diff --git a/DependencyInjection/RegisterExtractorParsersPass.php b/DependencyInjection/RegisterExtractorParsersPass.php new file mode 100644 index 0000000..f9d4564 --- /dev/null +++ b/DependencyInjection/RegisterExtractorParsersPass.php @@ -0,0 +1,39 @@ +hasDefinition('nelmio_api_doc.extractor.api_doc_extractor')) { + return; + } + + $definition = $container->getDefinition('nelmio_api_doc.extractor.api_doc_extractor'); + + //find registered parsers and sort by priority + $sortedParsers = array(); + foreach ($container->findTaggedServiceIds('nelmio_api_doc.extractor.parser') as $id => $tagAttributes) { + foreach ($tagAttributes as $attributes) { + $priority = isset($attributes['priority']) ? $attributes['priority'] : 0; + $sortedParsers[$priority][] = $id; + } + } + + //add parsers if any + if (!empty($sortedParsers)) { + krsort($sortedParsers); + $sortedParsers = call_user_func_array('array_merge', $sortedParsers); + + //add method call for each registered parsers + foreach ($sortedParsers as $id) { + $definition->addMethodCall('addParser', array(new Reference($id))); + } + } + } +} diff --git a/Extractor/ApiDocExtractor.php b/Extractor/ApiDocExtractor.php index a45dcf6..dcc8af1 100644 --- a/Extractor/ApiDocExtractor.php +++ b/Extractor/ApiDocExtractor.php @@ -13,7 +13,7 @@ namespace Nelmio\ApiDocBundle\Extractor; use Doctrine\Common\Annotations\Reader; use Nelmio\ApiDocBundle\Annotation\ApiDoc; -use Nelmio\ApiDocBundle\Parser\FormTypeParser; +use Nelmio\ApiDocBundle\Parser\ParserInterface; use Symfony\Component\Routing\Route; use Symfony\Component\Routing\RouterInterface; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -41,16 +41,15 @@ class ApiDocExtractor private $reader; /** - * @var \Nelmio\ApiDocBundle\Parser\FormTypeParser + * @var array \Nelmio\ApiDocBundle\Parser\ParserInterface */ - private $parser; + private $parsers = array(); - public function __construct(ContainerInterface $container, RouterInterface $router, Reader $reader, FormTypeParser $parser) + public function __construct(ContainerInterface $container, RouterInterface $router, Reader $reader) { $this->container = $container; $this->router = $router; $this->reader = $reader; - $this->parser = $parser; } /** @@ -122,8 +121,6 @@ class ApiDocExtractor return strcmp($a['resource'], $b['resource']); }); - - return $array; } @@ -179,6 +176,16 @@ class ApiDocExtractor return null; } + /** + * Registers a class parser to use for parsing input class metadata + * + * @param ParserInterface $parser + */ + public function addParser(ParserInterface $parser) + { + $this->parsers[] = $parser; + } + /** * Returns a new ApiDoc instance with more data. * @@ -215,9 +222,15 @@ class ApiDocExtractor // doc $annotation->setDocumentation($this->getDocCommentText($method)); - // formType - if (null !== $formType = $annotation->getFormType()) { - $parameters = $this->parser->parse($formType); + // input + if (null !== $input = $annotation->getInput()) { + $parameters = array(); + + foreach ($this->parsers as $parser) { + if ($parser->supports($input)) { + $parameters = $parser->parse($input); + } + } if ('PUT' === $method) { // All parameters are optional with PUT (update) @@ -280,7 +293,7 @@ class ApiDocExtractor } /** - * @param Reflector $reflected + * @param Reflector $reflected * @return string */ protected function getDocComment(\Reflector $reflected) @@ -300,7 +313,7 @@ class ApiDocExtractor } /** - * @param Reflector $reflected + * @param Reflector $reflected * @return string */ protected function getDocCommentText(\Reflector $reflected) diff --git a/Formatter/FormatterInterface.php b/Formatter/FormatterInterface.php index dcbadd5..1a3302f 100644 --- a/Formatter/FormatterInterface.php +++ b/Formatter/FormatterInterface.php @@ -26,7 +26,7 @@ interface FormatterInterface /** * Format documentation data for one route. * - * @param ApiDoc $annotation + * @param ApiDoc $annotation * return string|array */ public function formatOne(ApiDoc $annotation); diff --git a/NelmioApiDocBundle.php b/NelmioApiDocBundle.php index b42aba2..fbfe504 100644 --- a/NelmioApiDocBundle.php +++ b/NelmioApiDocBundle.php @@ -3,7 +3,15 @@ namespace Nelmio\ApiDocBundle; use Symfony\Component\HttpKernel\Bundle\Bundle; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Nelmio\ApiDocBundle\DependencyInjection\RegisterExtractorParsersPass; class NelmioApiDocBundle extends Bundle { + public function build(ContainerBuilder $container) + { + parent::build($container); + + $container->addCompilerPass(new RegisterExtractorParsersPass()); + } } diff --git a/Parser/FormTypeParser.php b/Parser/FormTypeParser.php index 8f62eab..6a25257 100644 --- a/Parser/FormTypeParser.php +++ b/Parser/FormTypeParser.php @@ -11,10 +11,10 @@ namespace Nelmio\ApiDocBundle\Parser; -use Symfony\Component\Form\FormBuilder; use Symfony\Component\Form\FormFactoryInterface; +use Symfony\Component\Form\Exception\FormException; -class FormTypeParser +class FormTypeParser implements ParserInterface { /** * @var \Symfony\Component\Form\FormFactoryInterface @@ -42,13 +42,25 @@ class FormTypeParser } /** - * Returns an array of data where each data is an array with the following keys: - * - dataType - * - required - * - description - * - * @param string|\Symfony\Component\Form\FormTypeInterface $type - * @return array + * {@inheritdoc} + */ + public function supports($item) + { + try { + if (is_string($item) && class_exists($item)) { + $item = new $item(); + } + + $form = $this->formFactory->create($item); + } catch (FormException $e) { + return false; + } + + return true; + } + + /** + * {@inheritdoc} */ public function parse($type) { @@ -93,6 +105,7 @@ class FormTypeParser 'dataType' => $bestType, 'required' => $config->getRequired(), 'description' => $config->getAttribute('description'), + 'readonly' => $config->getDisabled(), ); } diff --git a/Parser/ParserInterface.php b/Parser/ParserInterface.php new file mode 100644 index 0000000..8df98f5 --- /dev/null +++ b/Parser/ParserInterface.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Nelmio\ApiDocBundle\Parser; + +/** + * This is the interface parsers must implement in order to be registered in the ApiDocExtractor. + */ +interface ParserInterface +{ + /** + * Return true/false whether this class supports parsing the given class. + * + * @param string $item The string type of input to parse. + * @return boolean + */ + public function supports($item); + + /** + * Returns an array of class property metadata where each item is a key (the property name) and + * an array of data with the following keys: + * - dataType string + * - required boolean + * - description string + * - readonly boolean + * + * @param string $item The string type of input to parse. + * @return array + */ + public function parse($item); + +} diff --git a/README.md b/README.md index 56772e9..ae19234 100644 --- a/README.md +++ b/README.md @@ -82,7 +82,7 @@ class YourController extends Controller /** * @ApiDoc( * description="Create a new Object", - * formType="Your\Namespace\Form\Type\YourType" + * input="Your\Namespace\Form\Type\YourType" * ) */ public function postAction() @@ -99,16 +99,16 @@ The following properties are available: * `filters`: an array of filters; -* `formType`: the Form Type associated to the method, useful for POST|PUT methods, either as FQCN or +* `input`: the input type associated to the method, currently this only supports Form Types, useful for POST|PUT methods, either as FQCN or as form type (if it is registered in the form factory in the container) Each _filter_ has to define a `name` parameter, but other parameters are free. Filters are often optional parameters, and you can document them as you want, but keep in mind to be consistent for the whole documentation. -If you set a `formType`, then the bundle automatically extracts parameters based on the given type, +If you set `input`, then the bundle automatically extracts parameters based on the given type, and determines for each parameter its data type, and if it's required or not. -You can add an extra option named `description` on each field: +For Form Types, you can add an extra option named `description` on each field: ``` php + - diff --git a/Tests/Annotation/ApiDocTest.php b/Tests/Annotation/ApiDocTest.php index d1904a6..d8f3212 100644 --- a/Tests/Annotation/ApiDocTest.php +++ b/Tests/Annotation/ApiDocTest.php @@ -27,7 +27,7 @@ class ApiDocTest extends TestCase $this->assertFalse(isset($array['filters'])); $this->assertFalse($annot->isResource()); $this->assertFalse(isset($array['description'])); - $this->assertNull($annot->getFormType()); + $this->assertNull($annot->getInput()); } public function testConstructWithInvalidData() @@ -44,7 +44,7 @@ class ApiDocTest extends TestCase $this->assertFalse(isset($array['filters'])); $this->assertFalse($annot->isResource()); $this->assertFalse(isset($array['description'])); - $this->assertNull($annot->getFormType()); + $this->assertNull($annot->getInput()); } public function testConstruct() @@ -60,14 +60,14 @@ class ApiDocTest extends TestCase $this->assertFalse(isset($array['filters'])); $this->assertFalse($annot->isResource()); $this->assertEquals($data['description'], $array['description']); - $this->assertNull($annot->getFormType()); + $this->assertNull($annot->getInput()); } public function testConstructDefinesAFormType() { $data = array( 'description' => 'Heya', - 'formType' => 'My\Form\Type', + 'input' => 'My\Form\Type', ); $annot = new ApiDoc($data); @@ -77,7 +77,7 @@ class ApiDocTest extends TestCase $this->assertFalse(isset($array['filters'])); $this->assertFalse($annot->isResource()); $this->assertEquals($data['description'], $array['description']); - $this->assertEquals($data['formType'], $annot->getFormType()); + $this->assertEquals($data['input'], $annot->getInput()); } public function testConstructMethodIsResource() @@ -85,7 +85,7 @@ class ApiDocTest extends TestCase $data = array( 'resource' => true, 'description' => 'Heya', - 'formType' => 'My\Form\Type', + 'input' => 'My\Form\Type', ); $annot = new ApiDoc($data); @@ -95,7 +95,7 @@ class ApiDocTest extends TestCase $this->assertFalse(isset($array['filters'])); $this->assertTrue($annot->isResource()); $this->assertEquals($data['description'], $array['description']); - $this->assertEquals($data['formType'], $annot->getFormType()); + $this->assertEquals($data['input'], $annot->getInput()); } public function testConstructMethodResourceIsFalse() @@ -103,7 +103,7 @@ class ApiDocTest extends TestCase $data = array( 'resource' => false, 'description' => 'Heya', - 'formType' => 'My\Form\Type', + 'input' => 'My\Form\Type', ); $annot = new ApiDoc($data); @@ -113,7 +113,7 @@ class ApiDocTest extends TestCase $this->assertFalse(isset($array['filters'])); $this->assertFalse($annot->isResource()); $this->assertEquals($data['description'], $array['description']); - $this->assertEquals($data['formType'], $annot->getFormType()); + $this->assertEquals($data['input'], $annot->getInput()); } public function testConstructMethodHasFilters() @@ -135,7 +135,7 @@ class ApiDocTest extends TestCase $this->assertEquals(array('a-filter' => array()), $array['filters']); $this->assertTrue($annot->isResource()); $this->assertEquals($data['description'], $array['description']); - $this->assertNull($annot->getFormType()); + $this->assertNull($annot->getInput()); } /** @@ -158,7 +158,7 @@ class ApiDocTest extends TestCase $data = array( 'resource' => true, 'description' => 'Heya', - 'formType' => 'My\Form\Type', + 'input' => 'My\Form\Type', 'filters' => array( array('name' => 'a-filter'), ), @@ -171,6 +171,6 @@ class ApiDocTest extends TestCase $this->assertFalse(isset($array['filters'])); $this->assertTrue($annot->isResource()); $this->assertEquals($data['description'], $array['description']); - $this->assertEquals($data['formType'], $annot->getFormType()); + $this->assertEquals($data['input'], $annot->getInput()); } } diff --git a/Tests/Extractor/ApiDocExtratorTest.php b/Tests/Extractor/ApiDocExtratorTest.php index a71a955..b092643 100644 --- a/Tests/Extractor/ApiDocExtratorTest.php +++ b/Tests/Extractor/ApiDocExtratorTest.php @@ -39,28 +39,28 @@ class ApiDocExtractorTest extends WebTestCase $this->assertTrue($a1->isResource()); $this->assertEquals('index action', $a1->getDescription()); $this->assertTrue(is_array($array1['filters'])); - $this->assertNull($a1->getFormType()); + $this->assertNull($a1->getInput()); $a1 = $data[1]['annotation']; $array1 = $a1->toArray(); $this->assertTrue($a1->isResource()); $this->assertEquals('index action', $a1->getDescription()); $this->assertTrue(is_array($array1['filters'])); - $this->assertNull($a1->getFormType()); + $this->assertNull($a1->getInput()); $a2 = $data[2]['annotation']; $array2 = $a2->toArray(); $this->assertFalse($a2->isResource()); $this->assertEquals('create test', $a2->getDescription()); $this->assertFalse(isset($array2['filters'])); - $this->assertEquals('Nelmio\ApiDocBundle\Tests\Fixtures\Form\TestType', $a2->getFormType()); + $this->assertEquals('Nelmio\ApiDocBundle\Tests\Fixtures\Form\TestType', $a2->getInput()); $a2 = $data[3]['annotation']; $array2 = $a2->toArray(); $this->assertFalse($a2->isResource()); $this->assertEquals('create test', $a2->getDescription()); $this->assertFalse(isset($array2['filters'])); - $this->assertEquals('Nelmio\ApiDocBundle\Tests\Fixtures\Form\TestType', $a2->getFormType()); + $this->assertEquals('Nelmio\ApiDocBundle\Tests\Fixtures\Form\TestType', $a2->getInput()); } public function testGet() @@ -76,7 +76,7 @@ class ApiDocExtractorTest extends WebTestCase $array = $annotation->toArray(); $this->assertTrue(is_array($array['filters'])); - $this->assertNull($annotation->getFormType()); + $this->assertNull($annotation->getInput()); $annotation2 = $extractor->get('nemlio.test.controller:indexAction', 'test_service_route_1'); $annotation2->getRoute() diff --git a/Tests/Fixtures/Controller/TestController.php b/Tests/Fixtures/Controller/TestController.php index 1995a77..d39a02a 100644 --- a/Tests/Fixtures/Controller/TestController.php +++ b/Tests/Fixtures/Controller/TestController.php @@ -35,7 +35,7 @@ class TestController /** * @ApiDoc( * description="create test", - * formType="Nelmio\ApiDocBundle\Tests\Fixtures\Form\TestType" + * input="Nelmio\ApiDocBundle\Tests\Fixtures\Form\TestType" * ) */ public function postTestAction() @@ -76,7 +76,7 @@ class TestController /** * @ApiDoc( * description="create another test", - * formType="dependency_type" + * input="dependency_type" * ) */ public function anotherPostAction() diff --git a/Tests/Formatter/SimpleFormatterTest.php b/Tests/Formatter/SimpleFormatterTest.php index f717228..9a1e93a 100644 --- a/Tests/Formatter/SimpleFormatterTest.php +++ b/Tests/Formatter/SimpleFormatterTest.php @@ -81,18 +81,21 @@ class SimpleFormatterTest extends WebTestCase 'dataType' => 'string', 'required' => true, 'description' => 'A nice description', + 'readonly' => false ), 'b' => array( 'dataType' => 'string', 'required' => false, 'description' => '', + 'readonly' => false ), 'c' => array( 'dataType' => 'boolean', 'required' => true, 'description' => '', + 'readonly' => false ), ), 'description' => 'create test', @@ -108,18 +111,21 @@ class SimpleFormatterTest extends WebTestCase 'dataType' => 'string', 'required' => true, 'description' => 'A nice description', + 'readonly' => false ), 'b' => array( 'dataType' => 'string', 'required' => false, 'description' => '', + 'readonly' => false ), 'c' => array( 'dataType' => 'boolean', 'required' => true, 'description' => '', + 'readonly' => false ), ), 'description' => 'create test', @@ -138,6 +144,7 @@ class SimpleFormatterTest extends WebTestCase 'dataType' => 'string', 'required' => true, 'description' => 'A nice description', + 'readonly' => false ), ), 'description' => 'create another test',