REST API Documentation in Zend Framework 2 with Swagger
People talk about the self-documenting ideal of REST APIs, citing the Richardson Maturity Model, and occasionally this is interpreted as “a good REST API should be it's own documentation”. I personally think a REST API should be documented out the wazoo, especially if your work is spread across multiple developers, so any utility that can help with making documentation simple to write and easy to maintain is a winner, in my book.
A recent project called implementing a REST API; with multiple developers involved, I needed a way to keep the API documented, but I didn't much fancy forcing my developers to both comment the functions and maintain a separate API document, as when push-comes-to-shove on a deadline, that's the last thing anyone will be keeping up to date.
Step to the plate Swagger, an open-source “standard” for REST API documentation, using JSON.
Swagger-UI aims to give you a standalone interface for your API which interperets the documentation into something readable, while also giving you “Try me!” tools, allowing you to execute the API calls directly from the documentation.
Swagger-PHP can also be used in PHP 5.3+ projects to auto-generate the JSON documentation based on structured comments (annotations) in your code. Modules such as SwaggerModule can also help to integrate Swagger-PHP directly into a Zend FW2 project.
This guide aims to give you an understanding of Swagger, how to leverage the comment system of Swagger-PHP, and show you how to integrate Swagger with a Zend Framework 2 or CakePHP 2 project.
What's so good about Swagger?
The Swagger-UI Petstore Demo shows a little bit of what you can achieve with Swagger; the URL at the top of the page is the location of the demo Swagger JSON document to be loaded, which is processed by Swagger-UI and turned into the in-page documentation. As the document is JSON, it can technicall be read by anything - it doesn't have to be Swagger-UI.
Swagger-UI also provides “Try it out” buttons for each request type (except delete operations), so if you use Swagger from the start, you can use it for testing your API too!
Swagger Documents
Swagger-UI actually accepts two types of JSON Swagger documents:
- Resource Listing documents, which direct Swagger-UI to further documentation files.
- API Declaration documents, which detail the actual resources (API calls).
In the Swagger-UI Petstore Demo the initial document file is a Resource Listing, which supplies a series of API Declaration documents to be loaded.
Here's a snippet of the Resource Listing document (http://petstore.swagger.wordnik.com/api/api-docs):
{ "apiVersion":"1.0.0", "swaggerVersion":"1.2", "apis":[ {"path":"/user", "description":"Operations about user"}, {"path":"/pet","description":"Operations about pets"} ] }
Further documentation for the Resource Listing format can be found on the Swagger Core Wiki - Resource Listing page.
The listed APIs are relative links from the initial document (/api/api-docs) to further JSON documents, despite the preceding slash which normally indicates an absolute path from the domain root. For instance, the ”/pet” document referenced above is located at ”/api/api-docs/pet”. Swagger-UI will see each listed API, and load the document files for each.
Here's an example snippet from the Pet document which is subsequently loaded (http://petstore.swagger.wordnik.com/api/api-docs/pet):
{ "apiVersion":"1.0.0","swaggerVersion":"1.2"," basePath":"http://petstore.swagger.wordnik.com/api", "resourcePath":"/pet", "produces":["application/json","application/xml","text/plain","text/html"], "apis":[ { "path":"/pet/{petId}", "operations":[ { "method":"GET", "summary":"Find pet by ID", "notes":"Returns a pet based on ID", "type":"Pet", "nickname":"getPetById", "produces":["application/json","application/xml"] } } ] }
Further documentation on the API Declaration format can be found on the Swagger Core Wiki - API Declaration page.
Without going into too much depth, the API array lists a series of paths, which each can have a series of operations (Create, Read, Update, Delete, etc). These operations have a description, return type, etc, etc..
I don't want to write lots of JSON!
That's good, because instead you can use a Swagger documentation generator to parse code annotations (structured comments) and generate the documents on-the-fly!
Under PHP there's Zircote's own Swagger-PHP (also on github). Swagger-PHP uses a hierarchy of classes placed within the comments of your application, called annotations. These annotations are parsed by Swagger-PHP into the JSON document format.
An example of a function using the annotation format:
use Swagger\Annotations as SWG; /** * @SWG\Operation( * partial="admin.clients.specific.update", * summary="Update a specific client", * notes="User must be an Administrator", * @SWG\Parameters( * @SWG\Parameter( * name="client_id", * paramType="path", * dataType="int", * required="true", * description="Client ID" * ) * ) * ) */ public function adminUpdate( $clientId ) { ... code ... }
This annotation formatting appears a little unusual, however the structure is easy to follow and simple to use.
More information on the format and use of Swagger-PHP can be found on the Swagger-PHP documentation pages.
Integrating Swagger with Zend Framework 2
Let's look into integrating this magic directly into a Zend Framework 2 application. I'm assuming you're using the Zend Framework Skeleton Application for this exercise, using Composer.
Composer.json
{ "require": { "php": ">=5.3.3", "zendframework/zendframework": "2.2.*", "outeredge/swagger-module": "dev-master" } }
Execute php composer.phar install
to get things underway. Fetching Swagger Module will also install an appropriate version of Swagger-PHP.
Zend Application Config
config/application.config.php
array( 'Application', 'SwaggerModule', ),
This will enable Swagger Module in your application.
Swagger Module Configuration
Copy /vendor/outeredge/swagger-module/config/swagger.global.php.dist
to /config/autoload/swagger.global.php
swagger.global.php
return array( 'swagger' => array( 'paths' => array( __DIR__ . '/../../module/User/config', __DIR__ . '/../../module/User/src/User/Controller', ), ) );
The swagger config should contain paths to directories you want Swagger-PHP to process for annotations. In the case above, Swagger Module is being instructed to look at both the User module's controllers and the user module config files. More on this later.
As of 2014-01-16 you will need to add the following additional lines to /vendor/outeredge/swagger-module/config/module.config.php
to enable the ViewJsonStrategy and fix errors that may appear on the default document generation route.
'view_manager' => array( 'strategies' => array( 'ViewJsonStrategy' ), ),
Routes and Controllers
Swagger Module comes with default routes for generating the json Swagger documents (defined in /vendor/outeredge/swagger-module/config/module.config.php
).
You can generate the (empty) Swagger document by browsing to /api/docs
Integrating CakePHP 2
Composer.json
Setting up CakePHP using Composer is an entire article in itself, however installing Swagger-PHP on CakePHP only requires the one extra entry to your composer.json file:
{ "require": { "zircote/swagger-php": "dev-master" }, }
CakePHP Plugin
I've put together a quick CakePHP 2 plugin for Swagger-PHP which should act similarly to Swagger Module as per the above. Get CakePHP-Swagger on GitHub
Simply clone this repo into your CakePHP 2 app/Plugin
directory, then add the following like to your Config/bootstrap.php
: CakePlugin::load( array( 'Swagger' ⇒ array( 'routes' ⇒ true, 'bootstrap'⇒true ) ) );
Browse to /api/docs
to see the documentation.
Adding Swagger Annotations
Regardless of the framework you're using, now we have those (empty) documents we need to add some Swagger Annotations to our code comments.
Annotations don't need to be lined up exactly with a function, so for testing purposes, just throw this code into the ApiController class.
/** * @SWG\Resource( * resourcePath="/test", * @SWG\Api( * path="/test-path", * @SWG\Operation( * nickname="test", * method="GET", * summary="This is a test" * ) * ) * ) */
If you were to now call /api/docs/test
you should see the JSON representation of the above, with “This is a test” lurking somewhere in the code.
Visiting /api/docs
should also produce a Resource Listing document with a reference to /test.
Setting up Swagger UI
Swagger UI is a stand-alone HTML/JavaScript application for viewing Swagger documents - you can drop the Swagger-UI collection of files anywhere and get started reading Swagger documents, it's really as simple as that.
For myself, I placed an instance of Swagger-UI as the homepage of my API (/api
), using standard routing in the framework of your choice.
Swagger UI Quirks
- Swagger-PHP annotations will not be loaded from files without
use Swagger\Annotations as SWG;
in the header. This is intentional, and can actually he helpful for debugging. - Swagger-PHP is sensitive to trailing commas, unlike PHP. You'll get an error if you have a trailing comma in your swagger annotation.
- If there is an error in your annotation formatting, Swagger-PHP will produce a PHP error. This will stop Swagger-UI from loading the URLs, and may get stuck on an “200: some-unhelpful-url” notice. Simply load the
/api/docs
file and check for errors if this starts happening.
Separating Route Annotations From Controller Annotations
Under our framework of choice we may find our routes are defined in a separate file to our controller code. We have to specify the paths and methods (GET, POST, etc) for each of our Swagger annotations, but we don't want to be putting path information in our Controller files. Similarly, we don't want to put descriptions and params in our routes files. How do we separate the two definitions but keep everything working?
Simply, we use the new @SWG\Partial()
element. This allows for multiple detached elements to link back to the one Partial parent. You'll then and up with one item that has all of the attributes.
That's not a very good explanation, so let's see it in practice.
// routing.php // This file would contain our routes. use Swagger\Annotations as SWG; /** * @SWG\Resource( * resourcePath="/pets", * @SWG\Api( * path="/pets/getPet", * @SWG\Operation( * httpMethod="GET", * nickname="getPet", * @SWG\Partial("pets.getPet") * ) * ) * ) */ // .. code ..
// controller.php // this file contains our controller methods /** * @SWG\Operation( * partial="pets.getPet", * summary="Fetches a list of all the pets" * ) */ // .. code ..
When Swagger-PHP parses these two separate documents, it'll see the partial=“pets.getPet”
reference and look for an @SWG\Partial
element with a matching name. It'll then condense the two together, forming the one @Operation for our routes.php /pets/getPet
with the summary from the controller.php.
This allows us to separate our route definitions from our method definitions.
If you forget to define an @Partial
or partial=””
in either direction and end up with an orphan, an error will be raised.
All the bits and pieces
- Swagger-UI on GitHub
- Swagger-PHP on GitHub
- CakePHP-Swagger on GitHub
- ZF2 SwaggerModule on GitHub
- Swagger Core Wiki - Resource Listing
- Swagger Core Wiki - API Declaration
- Swagger-PHP Documentation