On the 7th October 2013, Zend introduced Apigility to the world. Once you get beyond the name, you see a very interesting project that allows you to easily create a web service without having to worry about the nitty-gritty details. Which details? Well, Apigility will handle content negotiation, error handling and versioning for you, allowing you to concentrate on your application. In the recently tagged 0.7 release, Apigility also supports both HTTP and OAuth2 authentication.

In this tutorial we will create a simple REST API that allows us to view a list of music albums, showing how to start using Apigility and how to publish an API using this tool.

Getting Started

Apigility is an open source project hosted on GitHub. The easiest way to get started with Apigility is to use Composer, you can get installation instructions from their site if you don’t have it already.

We can use Composer to create an Apigility project. Change directory to wherever you want to put your new project, and then run this command:

This will install the Apigility skeleton project which we will use as the starting point for our application. During the install, you should answer “Y” to question “Do you want to remove the existing VCS (.git, .svn..) history? [Y,n]?” as we do not want the skeleton’s .git files.

Development Mode

As part of the project, Apigility provides an administration system to allow you to create and manage the APIs that your application will offer. Obviously, you would not want the administration pages accessible on your live website and so Apigility controls this with the concept of development mode. This mode is controlled using the command line. To enable development mode, change into your project directory and type:

To disable the development mode, you can use this command:

Behind the scenes, running this command will create a new config file, config/development.config.php which enables the Apigility admin module. This file should not be stored in version control.

Apigility Admin

With development mode enabled, we can access the administration tool. The easiest way to run this is to use the built in webserver in PHP versions >= 5.4:

We can now navigate to http://localhost:8080/admin and we are greeted by this web page:

apigility dashboard

The most important part of the dashboard is the row immediately below the header that is labelled APIs. This is the list of the APIs that are provided by the application.

To create a new API, we simply click on the Create New API button in the top right hand corner:

create new API

Enter an API name of “Music” and press the green Create API button. The Apigility admin will then create our API for us. Behind the scenes Apigility has create a new ZF2 module, called Music, within the module directory and has registered it with the ZF2 ModuleManager for us. There is no code created yet however as we haven’t defined any endpoints.

We use this same admin interface to manage our new Music API:

apigility dashboard

API Versioning With Apigility

As you can see from the Music API’s management page, the current version of this API is 1. Apigility has built in support for versioning of APIs and supports both a custom media-type (there’s a good description of media types and API versioning on Peter Williams’ blog) in the Accept header and a URL segment containing the version. This is, of course, completely configurable.

The tool allows you to create a new version of your API by pressing the Create New Version button. This will create an entirely separate set of PHP files within the Music module so that the previous version will continue to work. As a general rule, you should create a new version whenever you introduce a change that will break all your existing clients.

Create A REST Endpoint

To create a REST endpoint within our API, we click on the REST Services menu item on the left of the Music admin page. This will show a new Create New REST Service button, which when pressed, opens a form providing for two different types of REST services to be created: Code-Connected or DB-Connected.

A DB-Connected REST service uses ZF2′s Table Gateway component to expose a CRUD interface to a single database table with minimal work. If you use this service, you get a lot of functionality for free, but less control; you just need to write whatever field validation requirements you need.

If your requirements are more complex, then the Code-Connected REST service allows you full control, so this is the one we will use. Enter “Album” as the REST Service Name and press the green “Create REST Service” button. This will create a new REST endpoint called AlbumEntity. You can click on this to manage it:

album rest endpoint

Apigility creates sensible defaults for a REST endpoint, so we don’t need to change anything. The most common setting that needs to be checked is Route matches which defines the URL to reach this service. For our new service, it is /album[/:album_id] which is a standard Zend\Route definition. This will call methods from this service whenever the URL starts with /album with an optional second segment containing an id. It is important that this does not clash with any other route in the application.

Plurals and Naming

I like to use a singular name for the REST service, but the URL in the route will operate on a collection, which to my mind is plural. Hence, the URL in the route should be plural. The Apligity admin defaults to the same as the service name and so it is album, so go click on the Edit tab and change Route to match to /albums[/:album_id]. This makes things a little neater.

Test the API with Curl

We can test our new API with this curl command:

The only header we need to set is Accept, in order to specify that we want version 1 of the API. As we haven’t actually written any code yet, the status code returned is HTTP/1.1 405 Method Not Allowed and the body content is JSON containing some error information in api-problem error-reporting format.

The api-problem format is a standard way to deliver error messages for HTTP APIs to help a client determine what has gone wrong. In this case, we can see that we have not defined a GET method for the availabilty endpoint, so let’s do that now.

Create the Albums Collection Endpoint

When we created the Music REST service, Apigility created a ZF2 module for us, which is located at module/Music. This is a standard ZF2 module with a set of directories within the module to provide structure. The configuration of the module is done using the web-based administration tool and is stored in config/module.config.php. This is helpful as you can store this within your version control system and see the changes made by the admin system.

The PHP code for our service is stored within the src directory. All the PHP classes are namespaced to the module name (in line with PSR-0 compliance) and therefore there is a subdirectory called Music immediately under src.

As we’ve already noticed, our API is versioned and this is reflected in the source code as a separate sub-namespace, V1. We have created a REST service also called Album, so our PHP class files for that service are stored in src/Music/V1/Rest/Album. The apigility admin tool has created three PHP classes for us in that directory:

  • AlbumCollection
  • AlbumEntity
  • AlbumResource

These are all skeleton classes that we need to complete in order to create our API. The AlbumResource class is the entry point to the service and has a set of class method that map to the various HTTP methods that we will support:

Class method HTTP method Notes
create POST Create an item within the collection
delete DELETE Delete an item
deleteList DELETE Delete all items in the collection
fetch GET Retrieve an item
fetchAll GET Retrieve items in the collection
patch PATCH Update some fields of an item
replaceList PUT Replace all items in the collection
update PUT Replace an item

 

The same class is used to operate on a collection of albums at the /albums URI and on a single album at /albums/[album_id]. As a result, for most of the HTTP methods, we have two class methods available: one for the collection and one for the item.

The AlbumEntity class is used to represent a single album. This object holds information about the album such as its name and the artist. Generally, I like my entites to be reasonably smart and so I expect them to have properties along with methods that operate on and interrogate those properties. The entity object should have no knowledge of how it is persisted however. This is the responsibility of a mapper class which we will write ourselves.

Finally, Apigility provides an AlbumCollection class for holding lists of AlbumEntity objects. This class extends Zend\Paginator, providing a helpful hint that we may need to paginate our collection data in order to be a useful web service.

Create the Data Model

In order for our web service to be able to read and write albums, we will need to store them somewhere. The easiest way to do that is using an SQLite database and a mapper class.

Create a Database

Our application has a data directory which is ideal for storing our database and schema in. To create our SQLite database, we first create a album.sql file and then run it:

data/album.sql

We use the sqlite3 command line tool to create the database with this table and data:

We now have a database; let’s write the classes that interact with it.

Represent Data with an Entity

Each AlbumEntity object represents one album, so we add three properties to the class:

module/Music/src/Music/V1/Rest/Album/AlbumEntity.php:

We also need to configure a ZF2 hydrator so that Apigility can convert an AlbumEntity into JSON – in our case we need an ObjectProperty hydrator. To add this, take a look in module/Music/config/module.config.php and search for metadata_map and change the definition of 'Music\\V1\\Rest\\Album\\AlbumEntity' to this:

module/Music/config/module.config.php:

Load and Save Data

To load and save to the database, we use a mapper class. Apigility didn’t provide any skeleton classes to help us as it does not know which persistent storage system we will use. For this tutorial, we will create a class called AlbumMapper that uses a Zend\Db database adapter:

module/Music/src/Music/V1/Rest/Album/AlbumMapper.php:

The fetchAll() method uses Zend\Db‘s Select object to get data from the database. This enables the paginator class to alter the generate SQL so that Apigility can automatically paginate our collection. The fetchOne method simply fetches a single row and returns an AlbumEntity.

The mapper class depends on a database adapter that is passed in via the constructor. Apigility already uses ZF2′s dependency injection container, Zend\ServiceManager, so we can add our configuration to it. This is done in the Module class by adding a new method getServiceConfig() to the Album’s Module.php file:

module/Music/src/Music/V1/Rest/Album/Module.php:

The ServiceManager will now instantiate a database adapter when we ask it for an AlbumMapper. We also need to configure the database adapter to point to our SQLite database; this is done by adding the following to config/autoload/user.global.php:

config/autoload/user.global.php:

Here, we have told the application the name of the SQLite database we want to use and that we’d like the AdapterServiceFactory to automagically create the correct database adapter for us.

Implement the API

With our model layer in place, we can now implement our API in the AlbumResource class by fleshing out the various methods declared there.

Fetch the Collection

To return a dataset to the user when they GET against /albums, we need to edit the AlbumResource class and fill in the fetchAll() method. This is quite easy as we will want to return an AlbumCollection using our AlbumMapper:

module/Music/src/Music/V1/Rest/Album/AlbumResource.php:

So how does the AlbumResource know about AlbumMapper? We inject it into AlbumResource using the ServiceManager again. We add a constructor to AlbumResource to accept the injected mapper. Mine looks like this:

module/Music/src/Music/V1/Rest/Album/AlbumResource.php:

Finally, we will also need remove the current ServiceManager definition for Music\V1\Rest\Album\AlbumResource and replace it with a factory in Module.php. The current definition is in module/Music/config/module.config.php. Open this file and remove the line:

Now open the Module.php file again and add a new factory to the getServiceConfig() method so that it now looks like:

module/Music/src/Music/V1/Rest/Album/Module.php:

Fetch All Albums

We can now re-run our curl command to test our API:

We can now see our collection of albums:

An Apigility API’s output is in the Hypertext Application Language (HAL) format. This format formalises how to express links and embedded resources within a response and makes it easier for your clients to consume your web service.

Fetch One Album

Fetching a single resource is just a case of filling in the fetch() method within the AlbumResource object, so we can add something like this

module/Music/src/Music/V1/Rest/Album/AlbumResource.php:

Apigility expects a single entity object to be returned from fetch() and it will then convert it to JSON for us. Again we can test with curl:

which produces this response:

Again, this is a HAL-formatted resource with the item’s data at the top level and the _links object holding all the relevant URIs related to the this resource.

Building APIs with Apigility

Implementing the remaining HTTP methods required for a complete CRUD API simply requires the fleshing out of the remaining methods in the AlbumResource class to link to the mapper as appropriate. Apigility is currently in beta and this is particularly obvious in the amount of manual configuration we had to do, but I’m confident that by the time 1.0 is released, these minor quirks will have been ironed out.

As shown in this article, it is remarkably easy to put together a robust API with Apigility that includes robust error handling along with content negotiation for versioning and HAL-formatted output. With all this provided for you, the majority of the code you have to write is concentrated on the specific implementation of your application. This is how it should be. When you’re building your own projects with Apigility, I hope that you will share your experiences with a comment, and perhaps even get involved with the project itself.