From 670bdfa1d0319056427348dfb2d9d945c0a76161 Mon Sep 17 00:00:00 2001 From: Guilhem Niot Date: Sat, 13 May 2017 17:55:09 +0200 Subject: [PATCH] Add a migration guide --- README.md | 5 + UPGRADE-3.0.md | 288 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 293 insertions(+) create mode 100644 UPGRADE-3.0.md diff --git a/README.md b/README.md index 9f5f17f9b..f93243bbb 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,10 @@ Version](https://poser.pugx.org/nelmio/api-doc-bundle/v/stable)](https://packagi The **NelmioApiDocBundle** bundle allows you to generate a decent documentation for your APIs. +## Migrate from 2.x to 3.0 + +[To migrate from 2.x to 3.0, just follow our guide.](https://github.com/nelmio/NelmioApiDocBundle/blob/dev/UPGRADE-3.0.md) + ## Installation First, open a command console, enter your project directory and execute the following command to download the latest stable version of this bundle: @@ -38,6 +42,7 @@ class AppKernel extends Kernel To access your documentation in your browser, register the following route: ```yml +# app/config/routing.yml NelmioApiDocBundle: resource: "@NelmioApiDocBundle/Resources/config/routing/swaggerui.xml" prefix: /api/doc diff --git a/UPGRADE-3.0.md b/UPGRADE-3.0.md new file mode 100644 index 000000000..c30905ce2 --- /dev/null +++ b/UPGRADE-3.0.md @@ -0,0 +1,288 @@ +Upgrading From 2.x To 3.0 +========================= + +In 3.0 we did major changes. The biggest is the removal of the `@ApiDoc` +annotation. To help you migrate to 3.0, we created this guide. + +Thanks to a command created by @dbu, it should not take too long. + +Step 1: Migrate to Swagger-PHP commands +--------------------------------------- + +First, copy this command in ``src/AppBundle/Command/SwaggerDocblockConvertCommand.php``: + +```php + + * @author Guilhem Niot + */ +class SwaggerDocblockConvertCommand extends ContainerAwareCommand +{ + protected function configure() + { + $this + ->setDescription('') + ->setName('api:doc:convert') + ; + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + $extractor = $this->getContainer()->get('nelmio_api_doc.extractor.api_doc_extractor'); + $apiDocs = $extractor->extractAnnotations($extractor->getRoutes()); + + foreach ($apiDocs as $annotation) { + /** @var ApiDoc $apiDoc */ + $apiDoc = $annotation['annotation']; + + $refl = $extractor->getReflectionMethod($apiDoc->getRoute()->getDefault('_controller')); + + $this->rewriteClass($refl->getFileName(), $refl, $apiDoc); + } + } + + /** + * Rewrite class with correct apidoc. + */ + private function rewriteClass(string $path, \ReflectionMethod $method, ApiDoc $apiDoc) + { + echo "Processing $path::{$method->name}\n"; + $code = file_get_contents($path); + $old = $this->locateNelmioAnnotation($code, $method->name); + + $code = substr_replace($code, $this->renderSwaggerAnnotation($apiDoc, $method), $old['start'], $old['length']); + $code = str_replace('use Nelmio\ApiDocBundle\Annotation\ApiDoc;', "use Nelmio\ApiDocBundle\Annotation\Model;\nuse Swagger\Annotations as SWG;", $code); + + file_put_contents($path, $code); + } + + private function renderSwaggerAnnotation(ApiDoc $apiDoc, \ReflectionMethod $method): string + { + $info = $apiDoc->toArray(); + if ($apiDoc->getResource()) { + throw new \RuntimeException('implement me'); + } + $path = str_replace('.{_format}', '', $apiDoc->getRoute()->getPath()); + + $annotation = '@SWG\\'.ucfirst(strtolower($apiDoc->getMethod())).'( + * tags={"'.$apiDoc->getSection().'"}, + * summary="'.$this->escapeQuotes($apiDoc->getDescription()).'"'; + + foreach ($apiDoc->getFilters() as $name => $parameter) { + $description = array_key_exists('description', $parameter) + ? $this->escapeQuotes($parameter['description']) + : 'todo'; + + $annotation .= ', + * @SWG\Parameter( + * name="'.$name.'", + * in="query", + * description="'.$description.'", + * required='.(array_key_exists($name, $apiDoc->getRequirements()) ? 'true' : 'false').', + * type="'.$this->determineDataType($parameter).'" + * )'; + } + + // Put parameters for POST requests into formData, as Swagger cannot handle more than one body parameter + $in = 'POST' === $apiDoc->getMethod() + ? 'formData' + : 'body'; + + foreach ($apiDoc->getParameters() as $name => $parameter) { + $description = array_key_exists('description', $parameter) + ? $this->escapeQuotes($parameter['description']) + : 'todo'; + + $annotation .= ', + * @SWG\Parameter( + * name="'.$name.'", + * in="'.$in.'", + * description="'.$description.'", + * required='.(array_key_exists($name, $apiDoc->getRequirements()) ? 'true' : 'false').', + * type="'.$this->determineDataType($parameter).'"'; + + if ('POST' !== $apiDoc->getMethod()) { + $annotation .= ', + * schema=""'; + } + + $annotation .= ' + * )'; + } + + if (array_key_exists('statusCodes', $info)) { + $responses = $info['statusCodes']; + foreach ($responses as $code => $description) { + $responses[$code] = reset($description); + } + } else { + $responses = [200 => 'Returned when successful']; + } + + $responseMap = $apiDoc->getResponseMap(); + foreach ($responses as $code => $description) { + $annotation .= ", + * @SWG\\Response( + * response=\"$code\", + * description=\"{$this->escapeQuotes($description)}\""; + if (200 === $code && isset($responseMap[$code]['class'])) { + $model = $responseMap[$code]['class']; + $annotation .= ", + * @Model(type=\"$model\")"; + } + $annotation .= ' + * )'; + } + + $annotation .= ' + * ) + *'; + + return $annotation; + } + + /** + * @return array with `start` position and `length` + */ + private function locateNelmioAnnotation(string $code, string $methodName): array + { + $position = strpos($code, "tion $methodName("); + if (false === $position) { + throw new \RuntimeException("Method $methodName not found in controller."); + } + + $docstart = strrpos(substr($code, 0, $position), '@ApiDoc'); + if (false === $docstart) { + throw new \RuntimeException("Method $methodName has no @ApiDoc annotation around\n".substr($code, $position - 200, 150)); + } + $docend = strpos($code, '* )', $docstart) + 3; + + return [ + 'start' => $docstart, + 'length' => $docend - $docstart, + ]; + } + + private function escapeQuotes(string $str): string + { + $lines = []; + foreach (explode("\n", $str) as $line) { + $lines[] = trim($line, ' *'); + } + + return str_replace('"', '""', implode(' ', $lines)); + } + + private function determineDataType(array $parameter): string + { + $dataType = isset($parameter['dataType']) ? $parameter['dataType'] : 'string'; + $transform = [ + 'float' => 'number', + 'datetime' => 'string', + ]; + if (array_key_exists($dataType, $transform)) { + $dataType = $transform[$dataType]; + } + + return $dataType; + } +} +``` + +Then open a command console, enter your project directory and run: + +``` +bin/console api:doc:convert +``` + +Your annotations should all be converted. + +Note that this tool is here to help you but not all features are supported so +make sure the generated annotations still fit your needs. + +Step 2: Update your routing +--------------------------- + +With NelmioApiDocBundle 2.x, you had to load the +``@NelmioApiDocBundle/Resources/config/routing.yml`` file. In 3.0, it was renamed +to ``@NelmioApiDocBundle/Resources/config/routing/swaggerui.xml``, so you have +to update the ``NelmioApiDocBundle`` route to: + +```yml +# app/config/routing.yml +NelmioApiDocBundle: + resource: "@NelmioApiDocBundle/Resources/config/routing/swaggerui.xml" + prefix: /api/doc +``` + +Step 3: Update your config +-------------------------- + +* ``nelmio_api_doc.name`` was replaced by ``nelmio_api_doc.documentation.info.title``. + + Before: + + ```yml + nelmio_api_doc: + name: My Awesome App! + ``` + + After: + + ```yml + nelmio_api_doc: + documentation: + info: + title: My Awesome App! + ``` + +* ``nelmio_api_doc.swagger.api_version`` was replaced by ``nelmio_api_doc.documentation.info.version``. + +* ``nelmio_api_doc.swagger.info.title`` was replaced by ``nelmio_api_doc.documentation.info.title``. + +* ``nelmio_api_doc.swagger.info.description`` was replaced by ``nelmio_api_doc.documentation.info.description``. + +* Other options were removed. + +Step 4: Update the bundle +------------------------- + +Change the constraint of ``nelmio/api-doc-bundle`` in your ``composer.json`` file +to ``dev-dev``: + +```json +{ + "require": { + "nelmio/api-doc-bundle": "dev-dev" + } +} +``` + +Then update your dependencies: + +``` +composer update +``` + +Step 5: Review the changes +-------------------------- + +It's almost finished! + +As most of the changes were automated you should check that they did not break +anything. Run your test suite, review, do whatever you think is useful before +pushing the changes. + +Then, commit the changes, push them, and enjoy!