
(at least)
Play well with microservices too.
Or any other client (mobile app...)
example.com, api.example.com
Not mandatory for small apps.
SEO and SMO... but solutions exist!
Hypermedia as the Engine of Application State
JSON for Linked Data
{
"@context": "/contexts/Person",
"@id": "/people",
"@type": "hydra:PagedCollection",
"hydra:totalItems": 1,
"hydra:itemsPerPage": 30,
"hydra:firstPage": "/people",
"hydra:lastPage": "/people",
"hydra:member": [
{
"@id": "/people/1",
"@type": "http://schema.org/Person",
"gender": "male",
"name": "Dunglas",
"url": "https://dunglas.fr"
}
]
}
composer create-project api-platform/api-platform my-apinamespaces:
entity: AppBundle\Entity
types:
Person:
parent: false
properties:
name: ~
birthDate: ~
gender: ~
bin/schema generate-types src/ app/config/schema.yml
<?php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Dunglas\ApiBundle\Annotation\Iri;
use Symfony\Component\Validator\Constraints as Assert;
/**
* A person (alive, dead, undead, or fictional).
*
* @see http://schema.org/Person Documentation on Schema.org
*
* @ORM\Entity
* @Iri("http://schema.org/Person")
*/
class Person
{
/**
* @var integer
* @ORM\Column(type="integer")
* @ORM\Id
* @ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* @var string The name of the item.
*
* @ORM\Column(nullable=true)
* @Assert\Type(type="string")
* @Iri("https://schema.org/name")
*/
private $name;
/**
* @var \DateTime Date of birth.
* @Assert\Date
* @ORM\Column(type="date", nullable=true)
* @Iri("http://schema.org/birthDate")
*/
private $birthDate;
/**
* @var string Gender of the person.
* @Assert\Type(type="string")
* @ORM\Column(nullable=true)
* @Iri("http://schema.org/gender")
*/
private $gender;
/**
* Sets id.
*
* @param integer $id
* @return $this
*/
public function setId($id)
{
$this->id = $id;
return $this;
}
/**
* Sets name.
*
* @param string $name
*
* @return $this
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Gets name.
*
* @return string
*/
public function getName()
{
return $this->name;
}
/**
* Gets id.
*
* @return integer
*/
public function getId()
{
return $this->id;
}
/**
* Sets birthDate.
*
* @param \DateTime $birthDate
* @return $this
*/
public function setBirthDate(\DateTime $birthDate)
{
$this->birthDate = $birthDate;
return $this;
}
/**
* Gets birthDate.
*
* @return \DateTime
*/
public function getBirthDate()
{
return $this->birthDate;
}
/**
* Sets gender.
*
* @param string $gender
* @return $this
*/
public function setGender($gender)
{
$this->gender = $gender;
return $this;
}
/**
* Gets gender.
*
* @return string
*/
public function getGender()
{
return $this->gender;
}
}
ResolveTargetEntity Doctrine mappings
# app/config/services.yml
services:
person_resource:
parent: "api.resource"
arguments: [ "AppBundle\Entity\Person" ]
tags: [ { name: "api.resource" } ]
GET /people
{
"@context": "/contexts/Person",
"@id": "/people",
"@type": "hydra:PagedCollection",
"hydra:totalItems": 1,
"hydra:itemsPerPage": 30,
"hydra:firstPage": "/people",
"hydra:lastPage": "/people",
"hydra:member": [
{
"@id": "/people/1",
"@type": "http://schema.org/Person",
"name": "Dunglas",
"gender": "male",
"birthDate": "1988-21-01T00:00:00+01:00"
}
]
}
A collection by API Platform (JSON-LD + Hydra output)
An autodiscoverable Linked Data API for free!
NelmioApiDoc automatically detects and documents exposed resources.
POST)GET products and its related offers)# src/app/config.yml
nelmio_cors:
defaults:
allow_origin: ["https://example.com"]
allow_methods: ["POST", "PUT", "GET", "DELETE", "OPTIONS"]
allow_headers: ["content-type", "authorization"]
max_age: 3600
paths:
'^/': ~
# app/config/security.yml
security:
# ...
firewalls:
login:
pattern: ^/login$
stateless: true
anonymous: true
form_login:
check_path: /login
success_handler: lexik_jwt_authentication.handler.authentication_success
failure_handler: lexik_jwt_authentication.handler.authentication_failure
require_previous_session: false
api:
pattern: ^/
stateless: true
lexik_jwt: ~
access_control:
- { path: ^/, roles: ROLE_ANONYMOUS, methods: [GET] }
- { path: ^/special, roles: ROLE_USER }
- { path: ^/, roles: ROLE_ADMIN }
fos_http_cache:
cache_control:
rules:
-
match:
path: ^/content$
headers:
cache_control:
public: true
max_age: 64000
etag: true
Behat and its Behatch extension make testing and API easy.
# features/put.feature
Scenario: Update a resource
When I send a "PUT" request to "/people/1" with body:
"""
{
"name": "Kevin"
}
"""
Then the response status code should be 200
And the response should be in JSON
And the header "Content-Type" should be equal to "application/ld+json"
And the JSON should be equal to:
"""
{
"@context": "/contexts/Person",
"@id": "/people/1",
"@type": "Person",
"name": "Kevin",
"address": null
}
"""
'use strict';
angular.module('myApp')
.controller('MainCtrl', function ($scope, Restangular) {
var peopleApi = Restangular.all('people');
function loadPeople() {
peopleApi.getList().then(function (people) {
$scope.people = people;
});
}
loadPeople();
$scope.newPerson = {};
$scope.success = false;
$scope.errorTitle = false;
$scope.errorDescription = false;
$scope.createPerson = function (form) {
peopleApi.post($scope.newPerson).then(function () {
loadPeople();
$scope.success = true;
$scope.errorTitle = false;
$scope.errorDescription = false;
$scope.newPerson = {};
form.$setPristine();
}, function (response) {
$scope.success = false;
$scope.errorTitle = response.data['hydra:title'];
$scope.errorDescription = response.data['hydra:description'];
});
};
});
