Exposer une API avec API Platform
Nous avons terminé la réalisation du site web du livre d'or. Maintenant, pour faciliter l'accès aux données, que diriez-vous d'exposer une API ? Une API pourrait être utilisée par une application mobile pour afficher toutes les conférences, leurs commentaires, et peut-être permettre la soumission de commentaires.
Dans cette étape, nous allons implémenter une API en lecture seule.
Installer API Platform
Exposer une API en écrivant du code est possible, mais si nous voulons utiliser des standards, nous ferions mieux d'utiliser une solution qui prend déjà en charge le gros du travail. Une solution comme API Platform :
1
$ symfony composer req api
Exposer une API pour les conférences
Quelques attributs sur la classe Conference suffisent pour configurer l'API :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
--- a/src/Entity/Conference.php
+++ b/src/Entity/Conference.php
@@ -2,29 +2,45 @@
namespace App\Entity;
+use ApiPlatform\Metadata\ApiResource;
+use ApiPlatform\Metadata\Get;
+use ApiPlatform\Metadata\GetCollection;
use App\Repository\ConferenceRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
+use Symfony\Component\Serializer\Attribute\Groups;
use Symfony\Component\String\Slugger\SluggerInterface;
#[ORM\Entity(repositoryClass: ConferenceRepository::class)]
#[UniqueEntity('slug')]
+#[ApiResource(
+ operations: [
+ new Get(normalizationContext: ['groups' => 'conference:item']),
+ new GetCollection(normalizationContext: ['groups' => 'conference:list'])
+ ],
+ order: ['year' => 'DESC', 'city' => 'ASC'],
+ paginationEnabled: false,
+)]
class Conference
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
+ #[Groups(['conference:list', 'conference:item'])]
private ?int $id = null;
#[ORM\Column(length: 255)]
+ #[Groups(['conference:list', 'conference:item'])]
private ?string $city = null;
#[ORM\Column(length: 4)]
+ #[Groups(['conference:list', 'conference:item'])]
private ?string $year = null;
#[ORM\Column]
+ #[Groups(['conference:list', 'conference:item'])]
private ?bool $isInternational = null;
/**
@@ -34,6 +50,7 @@ class Conference
private Collection $comments;
#[ORM\Column(length: 255, unique: true)]
+ #[Groups(['conference:list', 'conference:item'])]
private ?string $slug = null;
public function __construct()
L'attribut principal ApiResource
configure l'API pour les conférences. Il restreint les opérations possibles à get
et configure différentes choses, comme par exemple, quels champs afficher et comment trier les conférences.
Par défaut, le point d'entrée principal de l'API est /api
. Cette configuration a été ajoutée dans config/routes/api_platform.yaml
par la recette du paquet.
Une interface web vous permet d'interagir avec l'API :
Utilisez-la pour tester les différentes possibilités :
Imaginez le temps qu'il faudrait pour développer tout cela à partir de zéro !
Exposer une API pour les commentaires
Faites de même pour les commentaires :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66
--- a/src/Entity/Comment.php
+++ b/src/Entity/Comment.php
@@ -2,41 +2,63 @@
namespace App\Entity;
+use ApiPlatform\Doctrine\Orm\Filter\SearchFilter;
+use ApiPlatform\Metadata\ApiFilter;
+use ApiPlatform\Metadata\ApiResource;
+use ApiPlatform\Metadata\Get;
+use ApiPlatform\Metadata\GetCollection;
use App\Repository\CommentRepository;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM;
+use Symfony\Component\Serializer\Attribute\Groups;
use Symfony\Component\Validator\Constraints as Assert;
#[ORM\Entity(repositoryClass: CommentRepository::class)]
#[ORM\HasLifecycleCallbacks]
+#[ApiResource(
+ operations: [
+ new Get(normalizationContext: ['groups' => 'comment:item']),
+ new GetCollection(normalizationContext: ['groups' => 'comment:list'])
+ ],
+ order: ['createdAt' => 'DESC'],
+ paginationEnabled: false,
+)]
+#[ApiFilter(SearchFilter::class, properties: ['conference' => 'exact'])]
class Comment
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
+ #[Groups(['comment:list', 'comment:item'])]
private ?int $id = null;
#[ORM\Column(length: 255)]
#[Assert\NotBlank]
+ #[Groups(['comment:list', 'comment:item'])]
private ?string $author = null;
#[ORM\Column(type: Types::TEXT)]
#[Assert\NotBlank]
+ #[Groups(['comment:list', 'comment:item'])]
private ?string $text = null;
#[ORM\Column(length: 255)]
#[Assert\NotBlank]
#[Assert\Email]
+ #[Groups(['comment:list', 'comment:item'])]
private ?string $email = null;
#[ORM\Column]
+ #[Groups(['comment:list', 'comment:item'])]
private ?\DateTimeImmutable $createdAt = null;
#[ORM\ManyToOne(inversedBy: 'comments')]
#[ORM\JoinColumn(nullable: false)]
+ #[Groups(['comment:list', 'comment:item'])]
private ?Conference $conference = null;
#[ORM\Column(length: 255, nullable: true)]
+ #[Groups(['comment:list', 'comment:item'])]
private ?string $photoFilename = null;
#[ORM\Column(length: 255, options: ['default' => 'submitted'])]
Le même type d'attributs est utilisé pour configurer la classe.
Filtrer les commentaires exposés par l'API
Par défaut, API Platform expose toutes les entrées de la base de données. Mais pour les commentaires, seuls ceux qui ont été publiés devraient apparaître dans l'API.
Lorsque vous avez besoin de filtrer les éléments retournés par l'API, créez un service qui implémente QueryCollectionExtensionInterface
pour gérer la requête Doctrine utilisée pour les collections, et/ou QueryItemExtensionInterface
pour gérer les éléments :
La classe d'extension de requête n'applique sa logique que pour la ressource Comment
et modifie le query builder Doctrine pour ne considérer que les commentaires dans l'état published
.
Configurer le CORS
Par défaut, la politique de sécurité de même origine des clients HTTP modernes interdit d'appeler l'API depuis un autre domaine. Le paquet CORS, installé par défaut avec composer req api
, envoie des en-têtes de Cross-Origin Resource Sharing en fonction de la variable d'environnement CORS_ALLOW_ORIGIN
.
Par défaut, sa valeur, définie par le fichier .env
, autorise les requêtes HTTP depuis localhost
et 127.0.0.1
sur n'importe quel port. C'est exactement ce dont nous avons besoin pour la prochaine étape, car nous allons créer une SPA qui aura son propre serveur web et qui appellera l'API.