diff --git a/Command/DumpCommand.php b/Command/DumpCommand.php index 8091deb..59e204b 100644 --- a/Command/DumpCommand.php +++ b/Command/DumpCommand.php @@ -34,6 +34,7 @@ class DumpCommand extends ContainerAwareCommand 'Output format like: ' . implode(', ', $this->availableFormats), $this->availableFormats[0] ) + ->addOption('api-version', null, InputOption::VALUE_REQUIRED, 'The API version') ->addOption('view', '', InputOption::VALUE_OPTIONAL, '', ApiDoc::DEFAULT_VIEW) ->addOption('no-sandbox', '', InputOption::VALUE_NONE) ->setName('api:doc:dump') @@ -66,7 +67,11 @@ class DumpCommand extends ContainerAwareCommand $this->getContainer()->set('request', new Request(), 'request'); } - $extractedDoc = $this->getContainer()->get('nelmio_api_doc.extractor.api_doc_extractor')->all($view); + $extractor = $this->getContainer()->get('nelmio_api_doc.extractor.api_doc_extractor'); + $extractedDoc = $input->hasOption('api-version') ? + $extractor->allForVersion($input->getOption('api-version'), $view) : + $extractor->all($view); + $formattedDoc = $formatter->format($extractedDoc); if ('json' === $format) { diff --git a/Controller/ApiDocController.php b/Controller/ApiDocController.php index f10b28a..5e134aa 100644 --- a/Controller/ApiDocController.php +++ b/Controller/ApiDocController.php @@ -20,9 +20,11 @@ use Symfony\Component\HttpFoundation\Response; class ApiDocController extends Controller { - public function indexAction($view = ApiDoc::DEFAULT_VIEW) + public function indexAction(Request $request, $view = ApiDoc::DEFAULT_VIEW) { - $extractedDoc = $this->get('nelmio_api_doc.extractor.api_doc_extractor')->all($view); + $extractor = $this->get('nelmio_api_doc.extractor.api_doc_extractor'); + $apiVersion = $request->query->get('_version', null); + $extractedDoc = $apiVersion ? $extractor->allForVersion($apiVersion, $view) : $extractor->all($view); $htmlContent = $this->get('nelmio_api_doc.formatter.html_formatter')->format($extractedDoc); return new Response($htmlContent, 200, array('Content-Type' => 'text/html')); diff --git a/Extractor/ApiDocExtractor.php b/Extractor/ApiDocExtractor.php index 59ca74c..64f73c8 100644 --- a/Extractor/ApiDocExtractor.php +++ b/Extractor/ApiDocExtractor.php @@ -101,6 +101,29 @@ class ApiDocExtractor return $this->extractAnnotations($this->getRoutes(), $view); } + /** + * Extracts annotations from routes for specific version + * + * @param string $apiVersion API version + * @param string $view + * + * @return array + */ + public function allForVersion($apiVersion, $view = ApiDoc::DEFAULT_VIEW) + { + $data = $this->all($view); + foreach ($data as $k => $a) { + // ignore other api version's routes + if ( + $a['annotation']->getRoute()->getDefault('_version') && + !version_compare($apiVersion, $a['annotation']->getRoute()->getDefault('_version'), '=') + ) { + unset($data[$k]); + } + } + return $data; + } + /** * Returns an array of data where each data is an array with the following keys: * - annotation diff --git a/Resources/doc/index.rst b/Resources/doc/index.rst index 360404a..ac4c793 100644 --- a/Resources/doc/index.rst +++ b/Resources/doc/index.rst @@ -119,3 +119,26 @@ By calling an URL with the parameter ``?_doc=1``, you will get the corresponding documentation if available. .. _`installation chapter`: https://getcomposer.org/doc/00-intro.md + +Route versions +~~~~~~~~~~~~~~ + +You can define version for the API routes: + +.. code-block:: yaml + api_v3_products_list: + pattern: /api/v3/products.{_format} + defaults: { _controller: NelmioApiDocTestBundle:Test:routeVersion, _format: json, _version: "3.0" } + requirements: + _method: GET + api_v1_orders: + resource: "@AcmeOrderBundle/Resources/config/routing/orders_v1.yml" + defaults: { _version: "1.0" } + prefix: /api/v1/orders + +And generate documentation for specific version by the command: + +.. code-block:: bash + php app/console api:doc:dump --format=html --api-version=3.0 > api.html + +Or by adding `?_version={version}` to API documentation page URL. diff --git a/Tests/Extractor/ApiDocExtractorTest.php b/Tests/Extractor/ApiDocExtractorTest.php index fae0146..8264bfb 100644 --- a/Tests/Extractor/ApiDocExtractorTest.php +++ b/Tests/Extractor/ApiDocExtractorTest.php @@ -19,7 +19,7 @@ class ApiDocExtractorTest extends WebTestCase { const NB_ROUTES_ADDED_BY_DUNGLAS_API_BUNDLE = 5; - private static $ROUTES_QUANTITY_DEFAULT = 34; // Routes in the default view + private static $ROUTES_QUANTITY_DEFAULT = 35; // Routes in the default view private static $ROUTES_QUANTITY_PREMIUM = 6; // Routes in the premium view private static $ROUTES_QUANTITY_TEST = 2; // Routes in the test view @@ -96,6 +96,18 @@ class ApiDocExtractorTest extends WebTestCase $this->assertEquals('test.dev|test.com', $a5requirements['domain']['requirement']); } + public function testRouteVersionChecking() + { + $container = $this->getContainer(); + $extractor = $container->get('nelmio_api_doc.extractor.api_doc_extractor'); + $data = $extractor->allForVersion('1.5'); + $this->assertTrue(is_array($data)); + $this->assertCount(self::$ROUTES_QUANTITY_DEFAULT, $data); + $data = $extractor->allForVersion('1.4'); + $this->assertTrue(is_array($data)); + $this->assertCount(self::$ROUTES_QUANTITY_DEFAULT - 1, $data); + } + public function testGet() { $container = $this->getContainer(); diff --git a/Tests/Fixtures/Controller/TestController.php b/Tests/Fixtures/Controller/TestController.php index f4e4b16..01e127b 100644 --- a/Tests/Fixtures/Controller/TestController.php +++ b/Tests/Fixtures/Controller/TestController.php @@ -84,6 +84,13 @@ class TestController { } + /** + * @ApiDoc() + */ + public function routeVersionAction() + { + } + /** * @ApiDoc(description="Action without HTTP verb") */ diff --git a/Tests/Fixtures/app/config/routing.yml b/Tests/Fixtures/app/config/routing.yml index 338640b..9971411 100644 --- a/Tests/Fixtures/app/config/routing.yml +++ b/Tests/Fixtures/app/config/routing.yml @@ -253,3 +253,9 @@ test_route_31: path: /z-query-requirement-param-not-set methods: [GET] defaults: { _controller: NelmioApiDocTestBundle:Test:zActionWithRequirementParamNotSet } + +test_route_version_checking: + path: /zz-tests-route-version.{_format} + methods: [GET] + defaults: { _controller: NelmioApiDocTestBundle:Test:routeVersion, _format: json, _version: "1.5" } + diff --git a/Tests/Formatter/testFormat-result-no-dunglas.markdown b/Tests/Formatter/testFormat-result-no-dunglas.markdown index c22632c..5739788 100644 --- a/Tests/Formatter/testFormat-result-no-dunglas.markdown +++ b/Tests/Formatter/testFormat-result-no-dunglas.markdown @@ -933,3 +933,12 @@ related[b]: ### `POST` /zsecured ### + + + +### `GET` /zz-tests-route-version.{_format} ### + + +#### Requirements #### + +**_format** diff --git a/Tests/Formatter/testFormat-result-no-dunglas.php b/Tests/Formatter/testFormat-result-no-dunglas.php index b43e58a..a1853f6 100644 --- a/Tests/Formatter/testFormat-result-no-dunglas.php +++ b/Tests/Formatter/testFormat-result-no-dunglas.php @@ -2316,5 +2316,20 @@ With multiple lines.', ), 'deprecated' => false, ), + 22 => array( + 'method' => 'GET', + 'uri' => '/zz-tests-route-version.{_format}', + 'requirements' => array( + '_format' => array( + 'requirement' => '', + 'dataType' => '', + 'description' => '', + ), + ), + 'https' => false, + 'authentication' => false, + 'authenticationRoles' => array(), + 'deprecated' => false, + ), ), ); diff --git a/Tests/Formatter/testFormat-result.markdown b/Tests/Formatter/testFormat-result.markdown index 068d0f2..cf3f8d9 100644 --- a/Tests/Formatter/testFormat-result.markdown +++ b/Tests/Formatter/testFormat-result.markdown @@ -1017,3 +1017,12 @@ related[b]: ### `POST` /zsecured ### + + + +### `GET` /zz-tests-route-version.{_format} ### + + +#### Requirements #### + +**_format** diff --git a/Tests/Formatter/testFormat-result.php b/Tests/Formatter/testFormat-result.php index 46c6d87..41192f6 100644 --- a/Tests/Formatter/testFormat-result.php +++ b/Tests/Formatter/testFormat-result.php @@ -2470,5 +2470,23 @@ With multiple lines.', ), 'deprecated' => false, ), + 27 => + array( + 'authentication' => false, + 'method' => 'GET', + 'uri' => '/zz-tests-route-version.{_format}', + 'https' => false, + 'authenticationRoles' => array(), + 'deprecated' => false, + 'requirements' => + array( + '_format' => + array( + 'requirement' => '', + 'dataType' => '', + 'description' => '', + ), + ), + ), ), ); diff --git a/Tests/Parser/JmsMetadataParserTest.php b/Tests/Parser/JmsMetadataParserTest.php index b11f5ec..c59fc63 100644 --- a/Tests/Parser/JmsMetadataParserTest.php +++ b/Tests/Parser/JmsMetadataParserTest.php @@ -25,7 +25,7 @@ class JmsMetadataParserTest extends \PHPUnit_Framework_TestCase */ public function testParserWithNestedType($type) { - $metadataFactory = $this->getMock('Metadata\MetadataFactoryInterface'); + $metadataFactory = $this->createMock('Metadata\MetadataFactoryInterface'); $docCommentExtractor = $this->getMockBuilder('Nelmio\ApiDocBundle\Util\DocCommentExtractor') ->disableOriginalConstructor() ->getMock(); @@ -56,7 +56,7 @@ class JmsMetadataParserTest extends \PHPUnit_Framework_TestCase $metadata->addPropertyMetadata($propertyMetadataBar); $metadata->addPropertyMetadata($propertyMetadataBaz); - $propertyNamingStrategy = $this->getMock('JMS\Serializer\Naming\PropertyNamingStrategyInterface'); + $propertyNamingStrategy = $this->createMock('JMS\Serializer\Naming\PropertyNamingStrategyInterface'); $propertyNamingStrategy ->expects($this->at(0)) @@ -131,7 +131,7 @@ class JmsMetadataParserTest extends \PHPUnit_Framework_TestCase public function testParserWithGroups() { - $metadataFactory = $this->getMock('Metadata\MetadataFactoryInterface'); + $metadataFactory = $this->createMock('Metadata\MetadataFactoryInterface'); $docCommentExtractor = $this->getMockBuilder('Nelmio\ApiDocBundle\Util\DocCommentExtractor') ->disableOriginalConstructor() ->getMock(); @@ -332,7 +332,7 @@ class JmsMetadataParserTest extends \PHPUnit_Framework_TestCase public function testNestedGroups() { - $metadataFactory = $this->getMock('Metadata\MetadataFactoryInterface'); + $metadataFactory = $this->createMock('Metadata\MetadataFactoryInterface'); $docCommentExtractor = $this->getMockBuilder('Nelmio\ApiDocBundle\Util\DocCommentExtractor') ->disableOriginalConstructor() ->getMock(); @@ -419,7 +419,7 @@ class JmsMetadataParserTest extends \PHPUnit_Framework_TestCase public function testParserWithVersion() { - $metadataFactory = $this->getMock('Metadata\MetadataFactoryInterface'); + $metadataFactory = $this->createMock('Metadata\MetadataFactoryInterface'); $docCommentExtractor = $this->getMockBuilder('Nelmio\ApiDocBundle\Util\DocCommentExtractor') ->disableOriginalConstructor() ->getMock(); @@ -501,7 +501,7 @@ class JmsMetadataParserTest extends \PHPUnit_Framework_TestCase public function testParserWithInline() { - $metadataFactory = $this->getMock('Metadata\MetadataFactoryInterface'); + $metadataFactory = $this->createMock('Metadata\MetadataFactoryInterface'); $docCommentExtractor = $this->getMockBuilder('Nelmio\ApiDocBundle\Util\DocCommentExtractor') ->disableOriginalConstructor() ->getMock(); diff --git a/Tests/WebTestCase.php b/Tests/WebTestCase.php index 9f7ebc3..0dfd1ef 100644 --- a/Tests/WebTestCase.php +++ b/Tests/WebTestCase.php @@ -12,6 +12,7 @@ namespace Nelmio\ApiDocBundle\Tests; use Symfony\Bundle\FrameworkBundle\Test\WebTestCase as BaseWebTestCase; +use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\HttpKernel\Kernel; abstract class WebTestCase extends BaseWebTestCase @@ -36,6 +37,10 @@ abstract class WebTestCase extends BaseWebTestCase return \PHPUnit_Util_ErrorHandler::handleError($errorNumber, $message, $file, $line); } + /** + * @param array $options + * @return ContainerInterface + */ protected function getContainer(array $options = array()) { if (!static::$kernel) {