diff --git a/README.md b/README.md index 75f4cf07..7632a172 100644 --- a/README.md +++ b/README.md @@ -53,18 +53,18 @@ You can enable hot reloading. This setting re-generates swagger.json on each rel ## Annotations and Doc Block -SwaggerBake will parse some of your doc blocks for information. The first line of Doc Blocks above Controller Actions -are used for the Path Summary. +SwaggerBake will parse some of your doc blocks for information. The first line reads as the Path Summary and the +second as the Path Description, `@see` and `@deprecated` are also supported. ```php /** - * This will appear in the path summary + * Path Summary * - * This line will not appear in the path summary + * This is the path description + * @see https://book.cakephp.org/4/en/index.html The link and this description appear in Swagger + * @deprecated */ -public function index() { - -} +public function index() {} ``` SwaggerBake provides some optional Annotations for enhanced functionality. @@ -248,9 +248,10 @@ bin/cake swagger models This is built for CakePHP 4.x only. -| Version | Supported | Unit Tests | Notes | -| ------------- | ------------- | ------------- | ------------- | -| 4.0 | Yes | Yes | | +| Version | Cake Version | Supported | Unit Tests | Notes | +| ------------- | ------------- | ------------- | ------------- | ------------- | +| 2.* | 4.0 | Yes | Yes | Currently supported | +| 1.* | 3.8 | No | No | 1.* is being left available for possible Cake 3 support in the future | ## Common Issues @@ -308,6 +309,7 @@ local source to make developing easier: - Add a paths repository to your `composer.json` ``` +"minimum-stability": "dev", "repositories": [ { "type": "path", diff --git a/assets/swagger_bake.php b/assets/swagger_bake.php index 4a4cacf4..f19564b6 100644 --- a/assets/swagger_bake.php +++ b/assets/swagger_bake.php @@ -12,6 +12,8 @@ * built-in Swagger UI. * * @var string $docType: Options are swagger and redoc, defaults: swagger + * + * @var array $namespaces: Can be used if your controllers or entities exist in non-standard namespace such as a plugin */ return [ 'SwaggerBake' => [ @@ -20,7 +22,11 @@ 'json' => '/webroot/swagger.json', 'webPath' => '/swagger.json', 'hotReload' => false, - 'docType' => 'swagger' + 'docType' => 'swagger', + 'namespaces' => [ + 'controllers' => ['\App\\'], + 'entities' => ['\App\\'], + ] ] ]; diff --git a/src/Command/BakeCommand.php b/src/Command/BakeCommand.php index f79bd362..281ff6f9 100644 --- a/src/Command/BakeCommand.php +++ b/src/Command/BakeCommand.php @@ -9,13 +9,15 @@ use SwaggerBake\Lib\Configuration; use SwaggerBake\Lib\Factory\SwaggerFactory; -/** - * @class SwaggerBakeCommand - * @package SwaggerBake - * Generates a swagger json file - */ class BakeCommand extends Command { + /** + * Writes a swagger.json file + * + * @param Arguments $args + * @param ConsoleIo $io + * @return int|void|null + */ public function execute(Arguments $args, ConsoleIo $io) { $io->out("Running..."); diff --git a/src/Command/ModelCommand.php b/src/Command/ModelCommand.php index 7f92a4a0..305d4404 100644 --- a/src/Command/ModelCommand.php +++ b/src/Command/ModelCommand.php @@ -14,12 +14,15 @@ use SwaggerBake\Lib\Utility\DataTypeConversion; use SwaggerBake\Lib\Utility\ValidateConfiguration; -/** - * @class ModelCommand - * @package SwaggerBake - */ class ModelCommand extends Command { + /** + * List Cake Entities that can be added to Swagger. Prints to console. + * + * @param Arguments $args + * @param ConsoleIo $io + * @return int|void|null + */ public function execute(Arguments $args, ConsoleIo $io) { $io->out("Running..."); diff --git a/src/Command/RouteCommand.php b/src/Command/RouteCommand.php index 93cd7b7c..e8d620bf 100644 --- a/src/Command/RouteCommand.php +++ b/src/Command/RouteCommand.php @@ -14,13 +14,15 @@ use SwaggerBake\Lib\Configuration; use SwaggerBake\Lib\Utility\ValidateConfiguration; -/** - * @class CakeRouteCommand - * @package SwaggerBake - * Generates a list of routes matching a prefix - */ class RouteCommand extends Command { + /** + * List Cake Routes that can be added to Swagger. Prints to console. + * + * @param Arguments $args + * @param ConsoleIo $io + * @return int|void|null + */ public function execute(Arguments $args, ConsoleIo $io) { $io->out("Running..."); diff --git a/src/Controller/SwaggerController.php b/src/Controller/SwaggerController.php index a3c616c7..3a576ab3 100644 --- a/src/Controller/SwaggerController.php +++ b/src/Controller/SwaggerController.php @@ -7,14 +7,12 @@ use SwaggerBake\Lib\Configuration; use SwaggerBake\Lib\Factory\SwaggerFactory; -/** - * Swagger Controller - * - * - * @method \SwaggerBake\Model\Entity\Swagger[]|\Cake\Datasource\ResultSetInterface paginate($object = null, array $settings = []) - */ class SwaggerController extends AppController { + /** + * @link https://book.cakephp.org/4/en/controllers.html#controller-callback-methods + * @param EventInterface $event + */ public function beforeFilter(EventInterface $event) { parent::beforeFilter($event); @@ -28,7 +26,7 @@ public function beforeFilter(EventInterface $event) } /** - * Index method + * Controller action for displaying built-in Swagger UI * * @return \Cake\Http\Response|null|void Renders view */ diff --git a/src/Lib/Factory/PathFactory.php b/src/Lib/Factory/PathFactory.php index 9e9cacf3..7b08fda2 100644 --- a/src/Lib/Factory/PathFactory.php +++ b/src/Lib/Factory/PathFactory.php @@ -5,14 +5,13 @@ use Cake\Routing\Route\Route; use Cake\Utility\Inflector; -use SwaggerBake\Lib\Annotation\SwagEntity; use SwaggerBake\Lib\Annotation\SwagPath; use SwaggerBake\Lib\Exception\SwaggerBakeRunTimeException; use phpDocumentor\Reflection\DocBlock; use phpDocumentor\Reflection\DocBlockFactory; use ReflectionMethod; use SwaggerBake\Lib\Configuration; -use SwaggerBake\Lib\Model\ExpressiveModel; +use SwaggerBake\Lib\OpenApi\OperationExternalDoc; use SwaggerBake\Lib\OpenApi\Path; use SwaggerBake\Lib\OpenApi\Parameter; use SwaggerBake\Lib\OpenApi\Schema; @@ -48,23 +47,31 @@ public function create() : ?Path return null; } + foreach ((array) $defaults['_method'] as $method) { $path ->setType(strtolower($method)) - ->setPath($this->createPath()) + ->setPath($this->getPathName()) ->setOperationId($this->route->getName()) ->setSummary($this->dockBlock ? $this->dockBlock->getSummary() : '') + ->setDescription($this->dockBlock ? $this->dockBlock->getDescription() : '') ->setTags([ Inflector::humanize(Inflector::underscore($defaults['controller'])) ]) ->setParameters($this->getPathParameters()) + ->setDeprecated($this->isDeprecated()) ; + + $externalDoc = $this->getExternalDoc(); + if ($externalDoc) { + $path->setExternalDocs($externalDoc); + } } return $path; } - private function createPath() : string + private function getPathName() : string { $pieces = array_map( function ($piece) { @@ -196,4 +203,44 @@ private function isSwaggable(string $className) : bool return true; } + + private function isDeprecated() : bool + { + if (!$this->dockBlock || !$this->dockBlock instanceof DocBlock) { + return false; + } + + return $this->dockBlock->hasTag('deprecated'); + } + + private function getExternalDoc() : ?OperationExternalDoc + { + if (!$this->dockBlock || !$this->dockBlock instanceof DocBlock) { + return null; + } + + if (!$this->dockBlock->hasTag('see')) { + return null; + } + + $tags = $this->dockBlock->getTagsByName('see'); + $seeTag = reset($tags); + $str = $seeTag->__toString(); + $pieces = explode(' ', $str); + + if (!filter_var($pieces[0], FILTER_VALIDATE_URL)) { + return null; + } + + $externalDoc = new OperationExternalDoc(); + $externalDoc->setUrl($pieces[0]); + + array_shift($pieces); + + if (!empty($pieces)) { + $externalDoc->setDescription(implode(' ', $pieces)); + } + + return $externalDoc; + } } diff --git a/src/Lib/Factory/SwaggerFactory.php b/src/Lib/Factory/SwaggerFactory.php index 8a994bbc..f3aa7204 100644 --- a/src/Lib/Factory/SwaggerFactory.php +++ b/src/Lib/Factory/SwaggerFactory.php @@ -14,6 +14,11 @@ class SwaggerFactory { + /** + * Factory for Swagger objects + * + * @return Swagger + */ public function create() : Swagger { ValidateConfiguration::validate(); diff --git a/src/Lib/OpenApi/OperationExternalDoc.php b/src/Lib/OpenApi/OperationExternalDoc.php new file mode 100644 index 00000000..f7998579 --- /dev/null +++ b/src/Lib/OpenApi/OperationExternalDoc.php @@ -0,0 +1,58 @@ +toArray(); + } + + /** + * @return string + */ + public function getDescription(): string + { + return $this->description; + } + + /** + * @param string $description + * @return OperationExternalDoc + */ + public function setDescription(string $description): OperationExternalDoc + { + $this->description = $description; + return $this; + } + + /** + * @return string + */ + public function getUrl(): string + { + return $this->url; + } + + /** + * @param string $url + * @return OperationExternalDoc + */ + public function setUrl(string $url): OperationExternalDoc + { + $this->url = $url; + return $this; + } + +} \ No newline at end of file diff --git a/src/Lib/OpenApi/Path.php b/src/Lib/OpenApi/Path.php index 879da7ba..35bbaadf 100644 --- a/src/Lib/OpenApi/Path.php +++ b/src/Lib/OpenApi/Path.php @@ -13,6 +13,8 @@ class Path { private $summary = ''; + private $description = ''; + private $externalDocs; private $type = ''; private $path = ''; private $tags = []; @@ -35,6 +37,9 @@ public function toArray() : array if (empty($vars['security'])) { unset($vars['security']); } + if (empty($vars['externalDocs'])) { + unset($vars['externalDocs']); + } return $vars; } @@ -57,6 +62,24 @@ public function setSummary(string $summary): Path return $this; } + /** + * @return string + */ + public function getDescription(): string + { + return $this->description; + } + + /** + * @param string $description + * @return Path + */ + public function setDescription(string $description): Path + { + $this->description = $description; + return $this; + } + /** * @return string */ @@ -244,4 +267,22 @@ public function setPath(string $path): Path $this->path = $path; return $this; } + + /** + * @return OperationExternalDoc + */ + public function getExternalDocs() : OperationExternalDoc + { + return $this->externalDocs; + } + + /** + * @param OperationExternalDoc $externalDoc + * @return Path + */ + public function setExternalDocs(OperationExternalDoc $externalDoc) : Path + { + $this->externalDocs = $externalDoc; + return $this; + } } diff --git a/src/Lib/Swagger.php b/src/Lib/Swagger.php index a0a92c6d..9b7a231b 100644 --- a/src/Lib/Swagger.php +++ b/src/Lib/Swagger.php @@ -35,6 +35,11 @@ public function __construct(CakeModel $cakeModel) $this->array = $array; } + /** + * Returns OpenAPI 3.0 specification as an array + * + * @return array + */ public function getArray(): array { $this->buildSchemas(); @@ -67,16 +72,21 @@ public function getArray(): array return $this->array; } + /** + * Returns OpenAPI 3.0 spec as a JSON string + * + * @return false|string + */ public function toString() { return json_encode($this->getArray(), JSON_PRETTY_PRINT); } - public function __toString(): string - { - return $this->toString(); - } - + /** + * Writes OpenAPI 3.0 spec to a file using the $output argument as a file path + * + * @param string $output + */ public function writeFile(string $output) : void { if (!is_writable($output)) { @@ -86,6 +96,12 @@ public function writeFile(string $output) : void file_put_contents($output, $this->toString()); } + /** + * Adds a Schema element to OpenAPI 3.0 spec + * + * @param Schema $schema + * @return Swagger + */ public function pushSchema(Schema $schema): Swagger { $name = $schema->getName(); @@ -95,6 +111,12 @@ public function pushSchema(Schema $schema): Swagger return $this; } + /** + * Returns a schema object by $name argument + * + * @param string $name + * @return Schema|null + */ public function getSchemaByName(string $name): ?Schema { if (isset($this->array['components']['schemas'][$name])) { @@ -104,6 +126,12 @@ public function getSchemaByName(string $name): ?Schema return null; } + /** + * Adds a path to OpenAPI 3.0 spec + * + * @param Path $path + * @return $this + */ public function pushPath(Path $path): Swagger { $route = $path->getPath(); @@ -114,6 +142,15 @@ public function pushPath(Path $path): Swagger return $this; } + /** + * Returns a path by $route and $methodType argument + * + * @example $swagger->getPathByRouteAndMethodType('/my/route', 'post') + * + * @param string $route + * @param string $methodType + * @return Path|null + */ public function getPathByRouteAndMethodType(string $route, string $methodType): ?Path { if (isset($this->array['paths'][$route][$methodType])) { @@ -123,6 +160,11 @@ public function getPathByRouteAndMethodType(string $route, string $methodType): return null; } + /** + * Return the configuration + * + * @return Configuration + */ public function getConfig() : Configuration { return $this->config; @@ -209,4 +251,9 @@ private function withRequestBody(Path $path, Route $route) : Path } return $path; } + + public function __toString(): string + { + return $this->toString(); + } } \ No newline at end of file