mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2025-06-07 18:44:08 +00:00
489 lines
16 KiB
ReStructuredText
489 lines
16 KiB
ReStructuredText
.. Copyright (C) 2014 Champs Libres Cooperative SCRLFS
|
|
Permission is granted to copy, distribute and/or modify this document
|
|
under the terms of the GNU Free Documentation License, Version 1.3
|
|
or any later version published by the Free Software Foundation;
|
|
with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts.
|
|
A copy of the license is included in the section entitled "GNU
|
|
Free Documentation License".
|
|
|
|
.. _api:
|
|
|
|
API
|
|
###
|
|
|
|
Chill provides a basic framework to build REST api.
|
|
|
|
Configure a route
|
|
=================
|
|
|
|
Follow those steps to build a REST api:
|
|
|
|
1. Create your model;
|
|
2. Configure the API;
|
|
|
|
You can also:
|
|
|
|
* hook into the controller to customize some steps;
|
|
* add more route and steps
|
|
|
|
.. note::
|
|
|
|
Useful links:
|
|
|
|
* `How to use annotation to configure serialization <https://symfony.com/doc/current/serializer.html>`_
|
|
* `How to create your custom normalizer <https://symfony.com/doc/current/serializer/custom_normalizer.html>`_
|
|
|
|
Auto-loading the routes
|
|
***********************
|
|
|
|
Ensure that those lines are present in your file `app/config/routing.yml`:
|
|
|
|
|
|
.. code-block:: yaml
|
|
|
|
chill_cruds:
|
|
resource: 'chill_main_crud_route_loader:load'
|
|
type: service
|
|
|
|
|
|
Create your model
|
|
*****************
|
|
|
|
Create your model on the usual way:
|
|
|
|
.. code-block:: php
|
|
|
|
namespace Chill\PersonBundle\Entity\AccompanyingPeriod;
|
|
|
|
use Chill\PersonBundle\Entity\AccompanyingPeriod\OriginRepository;
|
|
use Doctrine\ORM\Mapping as ORM;
|
|
|
|
/**
|
|
* @ORM\Entity(repositoryClass=OriginRepository::class)
|
|
* @ORM\Table(name="chill_person_accompanying_period_origin")
|
|
*/
|
|
class Origin
|
|
{
|
|
/**
|
|
* @ORM\Id
|
|
* @ORM\GeneratedValue
|
|
* @ORM\Column(type="integer")
|
|
*/
|
|
private $id;
|
|
|
|
/**
|
|
* @ORM\Column(type="json")
|
|
*/
|
|
private $label;
|
|
|
|
/**
|
|
* @ORM\Column(type="date_immutable", nullable=true)
|
|
*/
|
|
private $noActiveAfter;
|
|
|
|
// .. getters and setters
|
|
|
|
}
|
|
|
|
|
|
Configure api
|
|
*************
|
|
|
|
Configure the api using Yaml (see the full configuration: :ref:`api_full_configuration`):
|
|
|
|
.. code-block:: yaml
|
|
|
|
# config/packages/chill_main.yaml
|
|
chill_main:
|
|
apis:
|
|
accompanying_period_origin:
|
|
base_path: '/api/1.0/person/accompanying-period/origin'
|
|
class: 'Chill\PersonBundle\Entity\AccompanyingPeriod\Origin'
|
|
name: accompanying_period_origin
|
|
base_role: 'ROLE_USER'
|
|
actions:
|
|
_index:
|
|
methods:
|
|
GET: true
|
|
HEAD: true
|
|
_entity:
|
|
methods:
|
|
GET: true
|
|
HEAD: true
|
|
|
|
.. note::
|
|
|
|
If you are working on a shared bundle (aka "The chill bundles"), you should define your configuration inside the class :code:`ChillXXXXBundleExtension`, using the "prependConfig" feature:
|
|
|
|
.. code-block:: php
|
|
|
|
namespace Chill\PersonBundle\DependencyInjection;
|
|
|
|
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
|
use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface;
|
|
use Symfony\Component\HttpFoundation\Request;
|
|
|
|
/**
|
|
* Class ChillPersonExtension
|
|
* Loads and manages your bundle configuration
|
|
*
|
|
* To learn more see {@link http://symfony.com/doc/current/cookbook/bundles/extension.html}
|
|
* @package Chill\PersonBundle\DependencyInjection
|
|
*/
|
|
class ChillPersonExtension extends Extension implements PrependExtensionInterface
|
|
{
|
|
public function prepend(ContainerBuilder $container)
|
|
{
|
|
$this->prependCruds($container);
|
|
}
|
|
|
|
/**
|
|
* @param ContainerBuilder $container
|
|
*/
|
|
protected function prependCruds(ContainerBuilder $container)
|
|
{
|
|
$container->prependExtensionConfig('chill_main', [
|
|
'apis' => [
|
|
[
|
|
'class' => \Chill\PersonBundle\Entity\AccompanyingPeriod\Origin::class,
|
|
'name' => 'accompanying_period_origin',
|
|
'base_path' => '/api/1.0/person/accompanying-period/origin',
|
|
'controller' => \Chill\PersonBundle\Controller\OpeningApiController::class,
|
|
'base_role' => 'ROLE_USER',
|
|
'actions' => [
|
|
'_index' => [
|
|
'methods' => [
|
|
Request::METHOD_GET => true,
|
|
Request::METHOD_HEAD => true
|
|
],
|
|
],
|
|
'_entity' => [
|
|
'methods' => [
|
|
Request::METHOD_GET => true,
|
|
Request::METHOD_HEAD => true
|
|
]
|
|
],
|
|
]
|
|
]
|
|
]
|
|
]);
|
|
}
|
|
}
|
|
|
|
The :code:`_index` and :code:`_entity` action
|
|
=============================================
|
|
|
|
The :code:`_index` and :code:`_entity` action are default actions:
|
|
|
|
* they will call a specific method in the default controller;
|
|
* they will generate defined routes:
|
|
|
|
Index:
|
|
Name: :code:`chill_api_single_accompanying_period_origin__index`
|
|
|
|
Path: :code:`/api/1.0/person/accompanying-period/origin.{_format}`
|
|
|
|
Entity:
|
|
Name: :code:`chill_api_single_accompanying_period_origin__entity`
|
|
|
|
Path: :code:`/api/1.0/person/accompanying-period/origin/{id}.{_format}`
|
|
|
|
Role
|
|
====
|
|
|
|
By default, the key `base_role` is used to check ACL. Take care of creating the :code:`Voter` required to take that into account.
|
|
|
|
For index action, the role will be called with :code:`NULL` as :code:`$subject`. The retrieved entity will be the subject for single queries.
|
|
|
|
You can also define a role for each method. In this case, this role is used for the given method, and, if any, the base role is taken into account.
|
|
|
|
.. code-block:: yaml
|
|
|
|
# config/packages/chill_main.yaml
|
|
chill_main:
|
|
apis:
|
|
accompanying_period_origin:
|
|
base_path: '/api/1.0/person/bla/bla'
|
|
class: 'Chill\PersonBundle\Entity\Blah'
|
|
name: bla
|
|
actions:
|
|
_entity:
|
|
methods:
|
|
GET: true
|
|
HEAD: true
|
|
roles:
|
|
GET: MY_ROLE_SEE
|
|
HEAD: MY ROLE_SEE
|
|
|
|
Customize the controller
|
|
========================
|
|
|
|
You can customize the controller by hooking into the default actions. Take care of extending :code:`Chill\MainBundle\CRUD\Controller\ApiController`.
|
|
|
|
|
|
.. code-block:: php
|
|
|
|
|
|
namespace Chill\PersonBundle\Controller;
|
|
|
|
use Chill\MainBundle\CRUD\Controller\ApiController;
|
|
use Symfony\Component\HttpFoundation\Request;
|
|
use Symfony\Component\HttpFoundation\Response;
|
|
|
|
class OpeningApiController extends ApiController
|
|
{
|
|
protected function customizeQuery(string $action, Request $request, $qb): void
|
|
{
|
|
$qb->where($qb->expr()->gt('e.noActiveAfter', ':now'))
|
|
->orWhere($qb->expr()->isNull('e.noActiveAfter'));
|
|
$qb->setParameter('now', new \DateTime('now'));
|
|
}
|
|
}
|
|
|
|
And set your controller in configuration:
|
|
|
|
.. code-block:: yaml
|
|
|
|
chill_main:
|
|
apis:
|
|
accompanying_period_origin:
|
|
base_path: '/api/1.0/person/accompanying-period/origin'
|
|
class: 'Chill\PersonBundle\Entity\AccompanyingPeriod\Origin'
|
|
name: accompanying_period_origin
|
|
# add a controller
|
|
controller: 'Chill\PersonBundle\Controller\OpeningApiController'
|
|
base_role: 'ROLE_USER'
|
|
actions:
|
|
_index:
|
|
methods:
|
|
GET: true
|
|
HEAD: true
|
|
_entity:
|
|
methods:
|
|
GET: true
|
|
HEAD: true
|
|
|
|
Create your own actions
|
|
=======================
|
|
|
|
You can add your own actions:
|
|
|
|
.. code-block:: yaml
|
|
|
|
chill_main:
|
|
apis:
|
|
-
|
|
class: Chill\PersonBundle\Entity\AccompanyingPeriod
|
|
name: accompanying_course
|
|
base_path: /api/1.0/person/accompanying-course
|
|
controller: Chill\PersonBundle\Controller\AccompanyingCourseApiController
|
|
actions:
|
|
# add a custom participation:
|
|
participation:
|
|
methods:
|
|
POST: true
|
|
DELETE: true
|
|
GET: false
|
|
HEAD: false
|
|
PUT: false
|
|
roles:
|
|
POST: CHILL_PERSON_ACCOMPANYING_PERIOD_SEE
|
|
DELETE: CHILL_PERSON_ACCOMPANYING_PERIOD_SEE
|
|
GET: null
|
|
HEAD: null
|
|
PUT: null
|
|
single-collection: single
|
|
|
|
The key :code:`single-collection` with value :code:`single` will add a :code:`/{id}/ + "action name"` (in this example, :code:`/{id}/participation`) into the path, after the base path. If the value is :code:`collection`, no id will be set, but the action name will be append to the path.
|
|
|
|
Then, create the corresponding action into your controller:
|
|
|
|
.. code-block:: php
|
|
|
|
namespace Chill\PersonBundle\Controller;
|
|
|
|
use Chill\MainBundle\CRUD\Controller\ApiController;
|
|
use Symfony\Component\HttpFoundation\Request;
|
|
use Symfony\Component\HttpFoundation\Response;
|
|
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
|
use Symfony\Component\HttpFoundation\Exception\BadRequestException;
|
|
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
|
use Symfony\Component\Validator\Validator\ValidatorInterface;
|
|
use Chill\PersonBundle\Privacy\AccompanyingPeriodPrivacyEvent;
|
|
use Chill\PersonBundle\Entity\Person;
|
|
|
|
class AccompanyingCourseApiController extends ApiController
|
|
{
|
|
protected EventDispatcherInterface $eventDispatcher;
|
|
|
|
protected ValidatorInterface $validator;
|
|
|
|
public function __construct(EventDispatcherInterface $eventDispatcher, $validator)
|
|
{
|
|
$this->eventDispatcher = $eventDispatcher;
|
|
$this->validator = $validator;
|
|
}
|
|
|
|
public function participationApi($id, Request $request, $_format)
|
|
{
|
|
/** @var AccompanyingPeriod $accompanyingPeriod */
|
|
$accompanyingPeriod = $this->getEntity('participation', $id, $request);
|
|
$person = $this->getSerializer()
|
|
->deserialize($request->getContent(), Person::class, $_format, []);
|
|
|
|
if (NULL === $person) {
|
|
throw new BadRequestException('person id not found');
|
|
}
|
|
|
|
$this->onPostCheckACL('participation', $request, $accompanyingPeriod, $_format);
|
|
|
|
switch ($request->getMethod()) {
|
|
case Request::METHOD_POST:
|
|
$participation = $accompanyingPeriod->addPerson($person);
|
|
break;
|
|
case Request::METHOD_DELETE:
|
|
$participation = $accompanyingPeriod->removePerson($person);
|
|
break;
|
|
default:
|
|
throw new BadRequestException("This method is not supported");
|
|
}
|
|
|
|
$errors = $this->validator->validate($accompanyingPeriod);
|
|
|
|
if ($errors->count() > 0) {
|
|
// only format accepted
|
|
return $this->json($errors);
|
|
}
|
|
|
|
$this->getDoctrine()->getManager()->flush();
|
|
|
|
return $this->json($participation);
|
|
}
|
|
}
|
|
|
|
Serialization for collection
|
|
============================
|
|
|
|
A specific model has been defined for returning collection:
|
|
|
|
.. code-block:: json
|
|
|
|
{
|
|
"count": 49,
|
|
"results": [
|
|
],
|
|
"pagination": {
|
|
"more": true,
|
|
"next": "/api/1.0/search.json&q=xxxx......&page=2",
|
|
"previous": null,
|
|
"first": 0,
|
|
"items_per_page": 1
|
|
}
|
|
}
|
|
|
|
|
|
This can be achieved quickly by assembling results into a :code:`Chill\MainBundle\Serializer\Model\Collection`. The pagination information is given by using :code:`Paginator` (see :ref:`Pagination <pagination-ref>`).
|
|
|
|
.. code-block:: php
|
|
|
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
|
use Chill\MainBundle\Pagination\PaginatorInterface;
|
|
|
|
class MyController extends AbstractController
|
|
{
|
|
|
|
protected function serializeCollection(PaginatorInterface $paginator, $entities): Response
|
|
{
|
|
$model = new Collection($entities, $paginator);
|
|
|
|
return $this->json($model, Response::HTTP_OK, [], $context);
|
|
}
|
|
}
|
|
|
|
.. _api_full_configuration:
|
|
|
|
Full configuration example
|
|
==========================
|
|
|
|
.. code-block:: yaml
|
|
|
|
apis:
|
|
-
|
|
class: Chill\PersonBundle\Entity\AccompanyingPeriod
|
|
name: accompanying_course
|
|
base_path: /api/1.0/person/accompanying-course
|
|
controller: Chill\PersonBundle\Controller\AccompanyingCourseApiController
|
|
actions:
|
|
_entity:
|
|
roles:
|
|
GET: CHILL_PERSON_ACCOMPANYING_PERIOD_SEE
|
|
HEAD: null
|
|
POST: null
|
|
DELETE: null
|
|
PUT: null
|
|
controller_action: null
|
|
path: null
|
|
single-collection: single
|
|
methods:
|
|
GET: true
|
|
HEAD: true
|
|
POST: false
|
|
DELETE: false
|
|
PUT: false
|
|
participation:
|
|
methods:
|
|
POST: true
|
|
DELETE: true
|
|
GET: false
|
|
HEAD: false
|
|
PUT: false
|
|
roles:
|
|
POST: CHILL_PERSON_ACCOMPANYING_PERIOD_SEE
|
|
DELETE: CHILL_PERSON_ACCOMPANYING_PERIOD_SEE
|
|
GET: null
|
|
HEAD: null
|
|
PUT: null
|
|
controller_action: null
|
|
# the requirements for the route. Will be set to `[ 'id' => '\d+' ]` if left empty.
|
|
requirements: []
|
|
path: null
|
|
single-collection: single
|
|
base_role: null
|
|
|
|
Maintaining an OpenApi
|
|
======================
|
|
|
|
.. note::
|
|
|
|
This is experimental and may change. Keep reading this part.
|
|
|
|
Accessing swagger
|
|
-----------------
|
|
|
|
The swagger UI is accessible through `<http://localhost:8001/_dev/swagger>`_
|
|
|
|
This is possible only in development mode.
|
|
|
|
You must be authenticated with a valid user to access it.
|
|
|
|
Maintaining specs
|
|
-----------------
|
|
|
|
Each bundle should have an `open api 3.0 spec file <https://swagger.io/docs/specification/about/>`_ at the bundle's root, and name :code:`chill.api.specs.yaml`.
|
|
|
|
.. warning::
|
|
|
|
Update the command :code:`specs-build` into `package.json` when adding a new api file.
|
|
|
|
The specs may be compiled from the different bundles filed, and validated using docker node:
|
|
|
|
.. code-block:: bash
|
|
|
|
./docker-node.sh
|
|
# build the openapi
|
|
yarn specs-build
|
|
# validate
|
|
yarn specs-validate
|
|
|
|
|