Julien Guittard
Matthew Weier O'Phinney
19 October 2015 — Las Vegas
Julien is a Zend/PHP Architect and Trainer.
Matthew is a Prinicipal Engineer at Zend Technologies, and Project Lead for Zend Framework and Apigility.
Application Programming Interface
How software should interact.
APIs delivered over HyperText Transfer Protocol (HTTP)
REpresentational State Transfer
An architecture designed around the HTTP specification.
REST leverages HTTP's strengths, and builds on:
http://domain/api/user[/:user_id]
Linking between resources to indicate relationships (hypermedia controls)
GET /api/user/jguittard
{
"_links": {
"self": {
"href": "http://domain/api/user/jguittard"
},
"contacts": [
{ "href": "http://domain/api/user/mwop" },
{ "href": "http://domain/api/user/zeevs" }
]
},
"id": "jguittard",
"name": "Julien Guittard"
}
JSON-HAL format
header('Content-Type: application/json');
echo json_encode([
'id' => 'jguittard',
'name' => 'Julien Guittard'
]);
Quite simple, right?
What about:
GET /api/user/jguittard
{
"_links": {
"self": {
"href": "http://domain/api/user/jguittard"
}
}
"id": "jguittard",
"name": "Julien Guittard"
}
{
"_embedded": {
"contacts": [
{
"_links": {
"self": {
"href": "http://domain/api/user/mwop"
}
},
"id": "mwop",
"name": "Matthew Weier O'Phinney"
}
]
}
}
{
"_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": [ /* ... */ ]
}
}
AKA API Problem
HTTP/1.1 405 Method Not Allowed
Content-Type: application/problem+json
{
"detail": "The GET method has not been defined for entities",
"status": 405,
"title": "Method Not Allowed",
"type": "http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html"
}
Request and provide different mediatypes for the same resource.
Accept: application/hal+json, application/json
Content-Type: application/vnd.conference+json
Content-Type: application/hal+json
(hopefully one the client accepts!)
Agility uses three approaches:
/v1/api/user
Accept
header:
Accept: application/vnd.example.v1+json
namespace Conference\V1\Rest\Speaker
Apigility officially supports three authentication systems:
authorization
event for custom
assertions.https://github.com/weierophinney/apigility-zendcon-tutorial
Start and initializer the environment:
$ vagrant up
$ vagrant ssh
$ cd /vagrant
$ phing init
Hopefully you did this before arriving today!
Open the browser to localhost:8888
All the exercise are on github:
https://github.com/weierophinney/apigility-zendcon-tutorial
Four exercises with solutions in branches: exercise/1, exercise/2, exercise/3, and exercise/4.
Switch between them within your vagrant box:
$ vagrant ssh
$ cd /vagrant && git checkout -b exercise/1 origin/exercise/1
GET /timezone
POST
and check if the timezone
parameter is a valid timezone.In your environment:
$ cd /vagrant
$ git checkout -b exercise/1 origin/excercise/1
/speaker[/:speaker_id]
/talk[/:talk_id]
CREATE TABLE speakers (
id INTEGER PRIMARY KEY,
name VARCHAR(80) NOT NULL,
url VARCHAR(255),
twitter VARCHAR(80)
);
CREATE TABLE talks (
id INTEGER PRIMARY KEY,
title TEXT,
abstract TEXT,
day TEXT,
start_time TEXT
);
CREATE TABLE talks_speakers (
talk_id INTEGER NOT NULL,
speaker_id INTEGER NOT NULL
);
class TableGateway extends AbstractTableGateway
{
public function __construct($table,
AdapterInterface $adapter,
$features = null,
ResultSetInterface $resultSetPrototype = null,
Sql $sql = null)
{
}
}
class HydratingResultSet extends AbstractResultSet
{
public function __construct(HydratorInterface $hydrator = null,
$objectPrototype = null)
{
$this->setHydrator(($hydrator) ?: new ArraySerializable);
$this->setObjectPrototype(($objectPrototype) ?: new ArrayObject);
}
}
//...
$resultSet = new HydratingResultSet();
$resultSet->setObjectPrototype(new TalkEntity());
/speaker[/:speaker_id]
/talk[/:talk_id]
In your environment:
cd /vagrant
git checkout -b exercise/2 origin/exercise/2
/speaker
and /talk
without data.* use Zend\Validator\Date with the options "format" to "Y-m-d"
In your environment:
cd /vagrant
git checkout -b exercise/3 origin/exercise/3
$sql = $table->getSql();
$select = $sql->select();
$select->join('talks_speakers', 'talks_speakers.speaker_id = speakers.id')
->where(['talks_speakers.talk_id' => $talkId]);
You'll want the DbSelect paginator:
use Zend\Paginator\Adapter\DbSelect;
$paginator = new DbSelect(
$select,
$table->adapter,
$table->getResultSetPrototype() // <-- THIS MAKES IT WORK!
);
Using the _embedded field format, add the following:
GET /talk/:talk_id
*GET /speaker/:speaker_id
*** Add a speakers field in TalkEntity to return a SpeakerCollection
** Add a talks field in SpeakerEntity to return a TalkCollection
SQL joins: http://framework.zend.com/manual/current/en/modules/zend.db.sql.html
DbSelect Paginator: http://framework.zend.com/manual/current/en/modules/zend.paginator.usage.html
Hint: you'll want factories for your mappers...
In your environment:
cd /vagrant
git checkout -b exercise/4 origin/exercise/4
This work is licensed under a
Creative Commons Attribution-ShareAlike 3.0 Unported License.
we used reveal.js to make this presentation.