Tutorial

Develop RESTful API in PHP using Apigility

by Enrico Zimuel - Zend, a Rogue Wave Company

29 January 2016 - Antwerp (Belgium)

About me

Enrico Zimuel (@ezimuel), developer since 1996. Senior Software Engineer in the R&D department of Zend a Rogue Wave Company. Research programmer at the Informatics Institute of Amsterdam University in 2006. Open source contributor, core team of Apigility and Zend Framework. TEDx and international speaker about programming. Co-founder of PHP User Group Torino (Italy).

Summary

  • Web API and RESTful architectures
  • Introduction to Apigility
  • Hands on Apigility:
    - Build a RESTful service
    - Fields and validators
    - Customize the hypermedia response

API

API stands for "Application Programming Interface" and as a term, specifies how software should interact.

In this worskhop we are interested in Web APIs, those delivered over HyperText Transfer Protocol (HTTP).

REST

REpresentational State Transfer (REST) is an architecture designed around the HTTP specification.

RESTful

REST leverages HTTP's strengths, and builds on:

  • URIs as unique identifiers for resources
  • Rich set of HTTP verbs for operations on resources
  • Specify the representation format of the output
  • Linking between resources (hypermedia)

Glory of REST

Talking about REST, the Richardson Maturity Model is often used to describe the concerns necessary when implementing a well-designed REST API

REST: Level 0

The usage of HTTP as communication layer

A format for data representation (e.g. JSON)

Basically a Remote Procedure Call (RPC)

REST: Level 1

URIs as unique identifiers for resources

For instance, the resource User can be identified by

http://domain/api/user[/:user_id]

where user_id is an optional parameter

REST: Level 2

Usage of HTTP verbs for operations on resources

CRUD HTTP verbs
CREATE POST
READ GET
UPDATE PUT, PATCH*
DELETE DELETE

* partial update

REST: Level 3

Linking between resources to indicate relationships (hypermedia)


GET /api/user/ezimuel
{
	"_links": {
		"self": {
			"href": "http://domain/api/user/ezimuel"
		},
		"contacts": [
			{ "href": "http://domain/api/user/mwop" },
			{ "href": "http://domain/api/user/zeevs" }
		]
	},
	"id": "ezimuel",
	"name": "Enrico Zimuel"
}

JSON-HAL format

Advantages of REST

Scalable architecture

Very easy to consume

Reduce client/server coupling

Discoverability

Apigility

Main features

  • RPC and REST
  • JSON (HAL) as default format
  • Error handling (API Problem)
  • Content negotiation
  • Versioning (via URI and Accept header)
  • Filtering and validation
  • Authentication (HTTP Basic/Digest, OAuth2)
  • Interactive documentation (HTML, Swagger)

JSON HAL

  • JSON Hypertext Application Language (internet draft)
  • Example:
    
    GET /api/user/ezimuel
    
    {
        "_links": {
            "self": {
                "href": "http://domain/api/user/ezimuel"
            }
        }
        "id": "ezimuel",
        "name": "Enrico Zimuel"
    }
    

_embedded


{
    "_links": {
        "self": {
            "href": "http://domain/api/user/ezimuel"
        }
    }
    "id": "ezimuel",
    "name": "Enrico Zimuel",
    "_embedded": {
        "contacts": [
            {
                "_links": {
                    "self": {
                        "href": "http://domain/api/user/mwop"
                    }
                },
                "id": "mwop",
                "name": "Matthew Weier O'Phinney"
            },
            {
                "_links": {
                    "self": {
                        "href": "http://domain/api/user/zeevs"
                    }
                },
                "id": "zeevs",
                "name": "Zeev Suraski"
            }
        ]
    }
}

Collections


{
    "_links": {
        "self": {
            "href": "http://domain/api/user?page=3"
        },
        "first": {
            "href": "http://domain/api/user"
        },
        "prev": {
            "href": "http://domain/api/user?page=2"
        },
        "next": {
            "href": "http://domain/api/user?page=4"
        },
        "last": {
            "href": "http://domain/api/user?page=133"
        }
    }
    "count": 3,
    "total": 498,
    "_embedded": {
        "users": [
            {
                "_links": {
                    "self": {
                        "href": "http://domain/api/user/mwop"
                    }
                },
                "id": "mwop",
                "name": "Matthew Weier O'Phinney"
            },
            {
                "_links": {
                    "self": {
                        "href": "http://domain/api/user/mac_nibblet"
                    }
                },
                "id": "mac_nibblet",
                "name": "Antoine Hedgecock"
            },
            {
                "_links": {
                    "self": {
                        "href": "http://domain/api/user/spiffyjr"
                    }
                },
                "id": "spiffyjr",
                "name": "Kyle Spraggs"
            }
        ]
    }
}

API Problem

  • API Problem is a RFC proposal (internet draft)
  • Example:
    
    Content-Type: application/problem+json
    
    {
        "detail": "The GET method has not been defined for individual",
        "status": 405,
        "title": "Method Not Allowed",
        "type": "http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html"
    }
    

Content negotiation

  • Content negotiation is a mechanism defined in the HTTP specification that makes it possible to serve different versions of a document at the same URI.
  • Example:
    
    Accept: application/hal+json, application/json
    

API Versioning

Agility uses two approaches:

  • In the URL, e.g. /api/v1/user
  • By Accept header, e.g. Accept:application/vnd.example.v1+json

Authentication

Apigility supports three different authentication systems: HTTP Basic, HTTP Digest, and OAuth2

Hands on!

Source code on github

  • https://github.com/ezimuel/php-benelux-apigility
  • Clone it and install using composer
    git clone https://github.com/ezimuel/php-benelux-apigility
    cd php-benelux-apigility
    composer install
  • To execute the Apigility UI run the following command:
    php -S 0.0.0.0:8888 -t public/ public/index.php

Exercise 1: REST service

  1. Create a REST service for PHP Benelux 2016 conference
  2. We want to expose 2 resources:
    /speaker[:/speaker_id]
    /talk[:/talk_id]
  3. Implement GET, POST, PATCH and DELETE for entities
  4. Implement GET for collections

The SQLite database


CREATE TABLE speakers (
  id INTEGER PRIMARY KEY,
  name VARCHAR(80) NOT NULL,
  title VARCHAR(80),
  company VARCHAR(80),
  url VARCHAR(255),
  twitter VARCHAR(80)
);

CREATE TABLE talks (
  id INTEGER PRIMARY KEY,
  title TEXT,
  abstract TEXT,
  day TEXT,
  start_time TEXT,
  end_time TEXT
);

CREATE TABLE talks_speakers (
  talk_id INTEGER NOT NULL,
  speaker_id INTEGER NOT NULL
);
The database is stored in /data/db/conference.db

Data Mapper

The Data Mapper is a class that interacts with the data model

It's consumed by REST resource to return Entities/Collection

Can be injected in the resource using ResourceFactory

Database Adapters

Apigility offers the management of DBs using adapters

Support for PDO, MySQL, Oci8, IbmDb2, PgSQL, SqlSrv

Use the Zend\Db\Adapter component of ZF2

Check the solution

To check the solution of exercise 1:

git checkout -b exercise/1 origin/exercise/1

Exercise 2: Fields and Validators

  1. Add fields name, title and company to the Speaker resource
  2. Add fields title, and day to the Talk resource
  3. Add a validator for day to accept only date in the format YYYY-MM-DD*
  4. Add a custom error message for the validator day
  5. Try to POST against /speaker and /talk without data

* use the Zend\Validator\Date with the options "format" to "Y-m-d"

Check the solution

To check the solution of exercise 2:

git checkout -b exercise/2 origin/exercise/2

Exercise 3: customize hypermedia

Using the _embedded field format, add the following:

  1. Add the "speakers" to GET /talk/:talk_id *
  2. Add the "talks" to GET /speaker/:speaker_id

* Add a speakers field in TalkEntity to return a SpeakerCollection

Check the solution

To check the solution of exercise 3:

git checkout -b exercise/3 origin/exercise/3

Thanks!

Rate this talk at joind.in/talk/84a7e


More information on apigility.org

Contact me: enrico [at] zend.com

Follow me: @ezimuel



Creative Commons License
This work is licensed under a
Creative Commons Attribution-ShareAlike 3.0 Unported License.
I used reveal.js to make this presentation.