(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-api
namespaces:
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)
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'];
});
};
});