Design, create and test your API in Symfony
API Blueprint
A powerful high-level API description language for web APIs.
API Blueprint
Blueprint - format
FORMAT: 1A
HOST: https://erp.local/api
# ERP API Documentation
REST API documentation for ERP application
## API Authorization
User authorization is based on JWT: [https://jwt.io/](https://jwt.io/)
Header example:
```http
Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXUyJ9
```
## General responses
Api response that can occur on each request
**401 Unauthorized**
User not authorized with correct token
Example response:
```http
{
"error": {
"code": 401,
"message": "Unauthorized"
}
}
```
API - data structures
# Data Structures
## EmployeeData
+ id: 1 (required, number) - Unique identifier
+ firstName: Jan (required, string)
+ lastName: Kowalski (required, string)
+ legalBasis: contract (required, string)
+ position: accountant (required, string)
+ status: hired (required, string)
## EmployeeList
+ page: 1 (number)
+ limit: 10 (number)
+ pages: 1 (number)
+ total: 2 (number)
+ _links
+ self
+ href: "/api/employee/?page=1&limit=10"
+ first
+ href: "/api/employee/?page=1&limit=10"
+ last
+ href: "/api/employee/?page=1&limit=10"
+ _embedded
+ items (array)
+ (EmployeeData)
+ (EmployeeData)
API - groups
# Group Employee
## List [/employee]
### Get employees [GET]
Get a list of employees.
+ Response 200 (application/json)
+ Attributes (EmployeeList)
### Create new employee [POST]
Create a new employee from request data
+ Request (application/json)
+ Body
{
"firstName": "John",
"lastName": "Doe",
"legalBasis": "contract",
"position": "accountant",
"status": "hired"
}
+ Response 201
+ Attributes (EmployeeData)
+ Response 400 (application/json)
+ Body
{
"error": "Invalid title"
}
API - schema
### Suggest Employee [GET]
Get employee suggestions based on text input
+ Response 200 (application/json)
+ Attributes (array[EmployeeSuggestData])
+ Schema
{
"$schema": "http://json-schema.org/draft-04/schema#",
"type": "array",
"items": {
"type": "object",
"properties": {
"first_name": {
"type": "string"
},
"last_name": {
"type": "string"
}
}
}
}
Aglio
An API Blueprint renderer that supports multiple themes and outputs static HTML that can be served by any web host.
Aglio
Aglio
aglio --theme-variables slate
-i app/Resources/blueprints/api.apib
-o web/apidoc.html
tools/apidoc.sh
Aglio
Dredd
Dredd is a language-agnostic command-line tool for validating API documentation written in API Blueprint format against its backend implementation.
Frontend testuje backend !!!
Dredd - config
dry-run: null
hookfiles:
- tests/dredd/hooks.php
language: vendor/ddelnano/dredd-hooks-php/bin/dredd-hooks-php
sandbox: false
server: null
server-wait: 3
init: false
custom: {}
names: false
only: []
reporter: []
output: []
header: []
sorted: false
user: null
inline-errors: false
details: false
method: []
color: true
level: info
timestamp: false
silent: false
path: []
blueprint: app/Resources/blueprints/api.apib
endpoint: 'http://localhost/api'
Dredd - hooks
<?php
use Dredd\Hooks;
$loader = require __DIR__.'/../../app/autoload.php';
$kernel = new AppKernel('prod', false);
$kernel->loadClassCache();
$kernel->boot();
$container = $kernel->getContainer();
$user = $container->get('dvs.user_bundle.repository.doctrine.user_repository')->findByUsername('admin');
$tokenManager = $container->get('lexik_jwt_authentication.jwt_manager');
$token = $tokenManager->create($user);
Hooks::beforeEach(function(&$transaction) use ($token) {
$transaction->request->
headers->Authorization = 'Bearer '.$token;
});
Dredd - in action
dredd
tools/test_api.sh
SyliusResourceBundle
Rapid RESTful API Development
SyliusResourceBundle
SyliusResourceBundle
sylius_resource:
resources:
dvs.employee:
classes:
model: Dvs\Domain\Employee\Entity\Employee
<?php
declare (strict_types = 1);
namespace Dvs\Domain\Employee\Entity;
use Sylius\Component\Resource\Model\ResourceInterface;
class Employee implements ResourceInterface
{
/**
* @var int
*/
private $id;
/**
* @var string
*/
private $firstName;
/**
* @var string
*/
private $lastName;
/**
* @var string
*/
private $legalBasis;
/**
* @var string
*/
private $position;
/**
* @var string
*/
private $status;
/**
* @return int
*/
public function getId()
{
return $this->id;
}
/**
* @return string
*/
public function getFirstName()
{
return $this->firstName;
}
/**
* @param string $firstName
*/
public function setFirstName(string $firstName)
{
$this->firstName = $firstName;
}
/**
* @return string
*/
public function getLastName()
{
return $this->lastName;
}
/**
* @param string $lastName
*/
public function setLastName(string $lastName)
{
$this->lastName = $lastName;
}
/**
* @return string
*/
public function getLegalBasis()
{
return $this->legalBasis;
}
/**
* @param string $legalBasis
*/
public function setLegalBasis(string $legalBasis)
{
$this->legalBasis = $legalBasis;
}
/**
* @return string
*/
public function getPosition()
{
return $this->position;
}
/**
* @param string $position
*/
public function setPosition(string $position)
{
$this->position = $position;
}
/**
* @return string
*/
public function getStatus()
{
return $this->status;
}
/**
* @param string $status
*/
public function setStatus(string $status)
{
$this->status = $status;
}
/**
* @return array
*/
public function getSuggest()
{
$name = sprintf('%s %s', $this->firstName, $this->lastName);
$reversedName = sprintf('%s %s', $this->lastName, $this->firstName);
$input = [$this->firstName, $this->lastName, $name, $reversedName];
$payload = ['first_name' => $this->firstName, 'last_name' => $this->lastName];
return [
'input' => $input,
'output' => $name,
'payload' => $payload,
];
}
}
SRB - routing
dvs_employee:
resource: |
alias: dvs.employee
path: employee
except: ['delete']
type: sylius.resource_api
defaults:
_sylius:
sortable: true
filterable: true
app/console debug:router
...
dvs_employee_index GET ANY ANY /api/employee
dvs_employee_create POST ANY ANY /api/employee
dvs_employee_update PUT|PATCH ANY ANY /api/employee/{id}
dvs_employee_show GET ANY ANY /api/employee/{id}
SRB - validation
<?php
class UpdateNotificationSettingsType extends AbstractType
{
/**
* @param FormBuilderInterface $builder
* @param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('daysBeforeExpiration', 'number', [
'constraints' => [
new NotBlank(),
new Range([
'min' => 0,
'max' => 180,
]),
],
])
->add('method', 'choice', [
'constraints' => [
new NotBlank(),
],
'choices' => NotificationType::getChoices(),
]);
}
/**
* @return string
*/
public function getName()
{
return 'updateNotificationSettingsType';
}
}
<?xml version="1.0" encoding="UTF-8" ?>
<constraint-mapping xmlns="http://symfony.com/schema/dic/constraint-mapping"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/constraint-mapping http://symfony.com/schema/dic/constraint-mapping/constraint-mapping-1.0.xsd">
<class name="Dvs\Domain\Employee\Entity\Employee">
<property name="firstName">
<constraint name="NotBlank"/>
</property>
<property name="lastName">
<constraint name="NotBlank"/>
</property>
<property name="legalBasis">
<constraint name="NotBlank"/>
<constraint name="Choice">
<option name="callback">
<value>Dvs\Domain\Employee\Type\LegalBasisType</value>
<value>getValues</value>
</option>
</constraint>
</property>
<property name="position">
<constraint name="NotBlank"/>
<constraint name="Choice">
<option name="callback">
<value>Dvs\Domain\Employee\Type\PositionType</value>
<value>getValues</value>
</option>
</constraint>
</property>
<property name="status">
<constraint name="NotBlank"/>
<constraint name="Choice">
<option name="callback">
<value>Dvs\Domain\Employee\Type\StatusType</value>
<value>getValues</value>
</option>
</constraint>
</property>
</class>
</constraint-mapping>
SRB - list
{
"page": 1,
"limit": 10,
"pages": 6,
"total": 52,
"_links": {
"self": {
"href": "/api/employee?page=1&limit=10"
},
"first": {
"href": "/api/employee?page=1&limit=10"
},
"last": {
"href": "/api/employee?page=6&limit=10"
},
"next": {
"href": "/api/employee?page=2&limit=10"
}
},
"_embedded": {
"items": [
{
"id": 3,
"firstName": "Dariusz",
"lastName": "Szymczak",
"legalBasis": "contract",
"position": "fitter",
"status": "candidate"
},
{
"id": 19,
"firstName": "Anita",
"lastName": "Król",
"legalBasis": "order",
"position": "accountant",
"status": "hired"
},
{
"id": 27,
"firstName": "Arkadiusz",
"lastName": "Tomaszewski",
"legalBasis": "none",
"position": "accountant",
"status": "dismissed"
},
{
"id": 32,
"firstName": "Dominik",
"lastName": "Grabowska",
"legalBasis": "order",
"position": "staff",
"status": "hired"
},
{
"id": 35,
"firstName": "Dariusz",
"lastName": "Dudek",
"legalBasis": "contract",
"position": "fitter",
"status": "dismissed"
},
{
"id": 36,
"firstName": "Alicja",
"lastName": "Brzezińska",
"legalBasis": "contract",
"position": "management-assistant",
"status": "dismissed"
},
{
"id": 37,
"firstName": "Dawid",
"lastName": "Krawczyk",
"legalBasis": "order",
"position": "foreman",
"status": "hired"
},
{
"id": 40,
"firstName": "Apolonia",
"lastName": "Zakrzewska",
"legalBasis": "recruitment",
"position": "foreman",
"status": "dismissed"
},
{
"id": 42,
"firstName": "Anita",
"lastName": "Borowski",
"legalBasis": "contract",
"position": "fitter",
"status": "hired"
},
{
"id": 50,
"firstName": "Bianka",
"lastName": "Michalak",
"legalBasis": "recruitment",
"position": "fitter",
"status": "dismissed"
}
]
}
}
SRB - sort & filter
GET /api/employee?sorting[firstName]=asc
GET /api/employee?criteria[position]=fitter
&criteria[status]=dismissed
SRB - want more ?
http://docs.sylius.org/en/latest/
bundles/SyliusResourceBundle/
Thx !
Your are the best team I've worked with
Design, document, create and test your API in Symfony
By Arkadiusz Kondas
Design, document, create and test your API in Symfony
Symfony + SyliusResourceBundle + API Blueprint + Aglio + Dredd = Perfect API
- 1,331