Dans ce chapitre nous allons voir comment on peut utiliser Symfony dans le cadre de la création d'une API. On a vu lors de la découverte des Controllers que l'on disposait d'une méthode json()
sur le AbstractController
qui permet de renvoyer du JSON.
class RecipesController extends AbstractController
{
#[Route("/api/recipes", methods: ["GET"])]
public function index(
RecipeRepository $repository,
)
{
$recipes = $repository->findAll();
return $this->json($recipes);
}
}
Par défaut, cette méthode va nous renvoyer un tableau contenant l'ensemble des champs de nos recettes. Cela est rendu possible par le système de sérialisation du framework qui repose sur 2 composants
- Le normalizer qui va convertir un objet en tableau PHP classique
- L'encodeur qui va convertir un tableau dans le format choisi (JSON, CSV, XML...)
Dans notre cas, c'est l'ObjectNormalizer
qui va être capable de scanner notre objet pour en extirper les informations. Si on souhaite contrôler les champs à exposer on peut utiliser le contexte de normalisation et notamment les groupes.
return $this->json($recipes, 200, [], [
'groups' => ['recipes.index']
]);
Ensuite, dans notre entité on pourra annoter les propriétés que l'on souhaite assigner au groupe.
use Symfony\Component\Serializer\Attribute\Groups;
class Recipe
{
#[Groups(['recipes.index'])]
private ?int $id = null;
#[Groups(['recipes.index', 'recipes.create'])]
private string $title = '';
#[Groups(['recipes.show', 'recipes.create'])]
private string $content = '';
// ...
}
Ces groupes marchent aussi pour les entités imbriquées.
Normalizer personnalisé
Pour des cas plus complexes, il est aussi possible de créer un normalizer personnalisé.
php bin/console make:serializer:normalizer
Ce normalizer sera automatiquement enregistré grâce au système d'autoconfiguration du gestionnaire de services de Symfony.
<?php
namespace App\Normalizer;
use App\Entity\Recipe;
use Knp\Component\Pager\Pagination\PaginationInterface;
use Symfony\Component\DependencyInjection\Attribute\Autowire;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
class PaginationNormalizer implements NormalizerInterface
{
public function __construct(
#[Autowire(service: 'serializer.normalizer.object')]
private readonly NormalizerInterface $normalizer
){
}
public function normalize(mixed $object, ?string $format = null, array $context = []): array|string|int|float|bool|\ArrayObject|null
{
if (!($object instanceof PaginationInterface)) {
throw new \RuntimeException();
}
return [
'items' => array_map(fn (Recipe $recipe) => $this->normalizer->normalize($recipe, $format, $context), $object->getItems()),
'total' => $object->getTotalItemCount(),
'page' => $object->getCurrentPageNumber(),
'lastPage' => ceil($object->getTotalItemCount() / $object->getItemNumberPerPage())
];
}
public function supportsNormalization(mixed $data, ?string $format = null, array $context = []): bool
{
return $data instanceof PaginationInterface;
}
public function getSupportedTypes(?string $format): array
{
return [
PaginationInterface::class => true
];
}
}