Julien GuittardMatthew 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.
How software should interact.
APIs delivered over HyperText Transfer Protocol (HTTP)
An architecture designed around the HTTP specification.
REST leverages HTTP's strengths, and builds on:
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" }
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: application/vnd.example.v1+json
namespace Conference\V1\Rest\Speaker
Apigility officially supports three authentication systems:
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
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());
In your environment:
cd /vagrant git checkout -b exercise/2 origin/exercise/2
* 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:
Add "speakers" to entities returned via GET /talk/:talk_id * Add "talks" to entities returned via 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 aCreative Commons Attribution-ShareAlike 3.0 Unported License.we used reveal.js to make this presentation.