Workshop:

Develop RESTful API in PHP using Apigility

by Enrico Zimuel - Zend Technologies Ltd.

28 August - Rovinj (Croatia)

About me

Enrico Zimuel (@ezimuel), developer since 1996. Senior Software Engineer in the R&D department of Zend Technologies since 2008. 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 computer programming. Co-founder of PHP User Group Torino (Italy).

Workshop Summary

  • Web API and RESTful architectures
  • Introduction to Apigility
  • Hands-on Apigility:
    - Build a RPC service
    - Build a RESTful service
    - Validate the input for REST services
    - Manage the HAL-JSON 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

REST in PHP


header('Content-Type: application/json');
echo json_encode([
	'id'   => 'ezimuel',
	'name' => 'Enrico Zimuel'
]);

Quite simple, right?

No! What about error handling, hypermedia, data validation, content negotiation, versioning, etc?

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)
  • Deploy, build package file ready for production

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!

Start the environment
(netgen/summercamp-2015):

  • vagrant up --no-provision
  • vagrant ssh
  • cd /var/www/summercamp
  • ./run.sh apigility

In your laptop:

  • Add this line in /etc/hosts:
    
    172.21.12.10	apigility.phpsc
    
  • Open the browser to apigility.phpsc

Exercises on github

All the exercise are on github:

https://github.com/ezimuel/php-summercamp-apigility

Four exercises with solutions in branches: exercise/1, exercise/2, exercise/3, and exercise/4

Exercise 1: RPC service

  1. Create a RPC service that returns the timezone of the server on GET /timezone
  2. Enable POST and check if the timezone parameter is a valid timezone

Check the solution

In your environment:

  • cd /var/www/summercamp/workshops/apigility
  • git fetch
  • git checkout exercise/1

Build a Conference API

  • We want to build a simple API for the SummerCamp 2015 conference
  • We want to publish the following URIs:
    
    /speaker[/:speaker_id]
    /talk[/:talk_id]
    

The SQLite database


CREATE TABLE speakers (
  id INTEGER PRIMARY KEY,
  name VARCHAR(80) NOT NULL,
  title VARCHAR(80) NOT NULL,
  company VARCHAR(80) NOT NULL,
  url_company 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
);

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

Exercise 2: REST service

  1. Create two REST services for speakers and talks
  2. Create the Mapper classes for speakers and talks
  3. Use the SQLite adapter inside the Mapper
  4. Consume the Mapper class in the REST Resources
  5. Implement GET, POST, PATCH and DELETE for Entities
  6. Implement GET for Collections

Check the solution

In your environment:

  • cd /var/www/summercamp/workshops/apigility
  • git fetch
  • git checkout exercise/2

Exercise 3: 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

In your environment:

  • cd /var/www/summercamp/workshops/apigility
  • git fetch
  • git checkout exercise/3

Exercise 4: Manage HAL-JSON result

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

In your environment:

  • cd /var/www/summercamp/workshops/apigility
  • git fetch
  • git checkout exercise/4

Thanks!


More information on apigility.org

Contact me: enrico [at] zend.com

Rate this talk https://joind.in/15133



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.