Créer son propre paramConverter

En voilà une idée qui est bonne! Très honnêtement, cela m’est déjà arrivé, mais plutôt pour récupérer une liste et « vider » le contrôleur.

Mais il arrive aussi que cela soit très utile si l’on veut que le comportement par défaut du paramConverter soit modifié. Le cas s’est posé lors de l’implémentation du verbe PUT dans une API. Comme vous le savez, lorsque l’on fait un PUT, si la ressource existe, elle est mise à jour (et si des champs manquent, tant pis, ils sont mis à null en base), et si elle n’existe pas, elle est créée. Sauf que le paramConverter fait une chose très simple: s’il ne trouve pas l’entité, il renvoie une 404. Et ce n’est pas du tout ce que nous voulons. Alors bien sûr, nous pourrions faire un traitement dans le handler du formulaire et ne pas utiliser le paramConverter, mais ce serait dommage. Et c’est parti pour créer notre propre paramConverter!

Appel du paramConverter personnalisé dans le controller:

    /**
     * @param Request   $request
     * @param MyEntity  $myEntity
     * @param bool      $isNew
     *
     * @ParamConverter("myEntity", converter="app_get_or_create_entity_converter", options={
     *  "repository_method" = "getMyEntityById"
     * })
     */
    public function putMyEntityAction(Request $request, MyEntity $$myEntity, $isNew)
    {
    [...]
    }

 

Créons le paramConverter

namespace AppBundle\ParamConverter;

use AppBundle\Exception\NotFoundResourceException;
use Doctrine\ORM\EntityManagerInterface;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
use Symfony\Component\HttpFoundation\Request;
use Doctrine\Common\Persistence\ManagerRegistry;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Sensio\Bundle\FrameworkExtraBundle\Request\ParamConverter\ParamConverterInterface;

/**
 * Class EntityConverter.
 */
class GetOrCreateEntityConverter implements ParamConverterInterface
{
    /**
     * @var EntityManagerInterface
     */
    protected $entityManager;

    /**
     * @var ManagerRegistry Manager registry
     */
    private $registry;

    /**
     * @param ManagerRegistry        $registry
     * @param EntityManagerInterface $entityManager
     */
    public function __construct(ManagerRegistry $registry, EntityManagerInterface $entityManager)
    {
        $this->registry = $registry;
        $this->entityManager = $entityManager;
    }

    /**
     * {@inheritdoc}
     */
    public function supports(ParamConverter $configuration)
    {
        if ('app_get_or_create_entity_converter' !== $configuration->getConverter()) {
            return false;
        }

        return true;
    }

    /**
     * {@inheritdoc}
     *
     * Applies converting
     *
     * @throws \InvalidArgumentException When route attributes are missing
     * @throws NotFoundHttpException     When object not found
     */
    public function apply(Request $request, ParamConverter $configuration)
    {
        $name = $configuration->getName();
        $options = $configuration->getOptions();
        $class = $configuration->getClass();
        $repository = $this->entityManager->getRepository($class);
        $isNew = false;

        $id = $request->attributes->get($name);
        $repositoryMethod = $options['repository_method'];

        // Catch the NotFoundResourceException because we need to return a new instance in this case
        try {
            $entity = $repository->$repositoryMethod($id);
        } catch (NotFoundResourceException $e) {
            $entity = new $class();
            $entity->setId($id);
            $isNew = true;
        }

        $request->attributes->set($name, $entity);
        $request->attributes->set('isNew', $isNew);
    }
}

Nous avons ici une dépendance obligatoire, le ManagerRegistry, et une autre que nous avons choisi d’injecter, l’entityManager.

La fonction supports va permettre de vérifier que notre paramConverter peut bien s’exécuter ici.

Et enfin, la fonction apply va récupérer le nom sur lequel le paramConverter agit (ici myEntity, puisque c’est ce que nous avons mis comme premier paramètre dans l’annotation).

@ParamConverter("myEntity", converter="app_get_or_create_entity_converter", options={
     *  "repository_method" = "getMyEntityById"
     * })

Les options de l’annotation et la classe de l’entité sont récupérées.
Grâce à l’entityManager, nous récupérons le repository de l’entité et nous pouvons faire appel à la fonction renseignée par l’option « repository_method » (avec l’identifiant transmis lors de la requête).

Si l’entité n’est pas trouvée est qu’une NotFoundResourceException est lancée, nous l’attrapons et créons l’entité en lui settant un id.

Puis nous plaçons cette entité dans le parameterBag avec sa clef, et changeons le flag $isNew à true.

Déclarons le paramConverter

    app.converter.get_or_create_entity:
        class: AppBundle\ParamConverter\GetOrCreateEntityConverter
        arguments:
            - '@doctrine'
            - '@doctrine.orm.entity_manager'
        tags:
            - { name: request.param_converter, converter: app_get_or_create_entity_converter }

Et c’est déjà terminé, il est opérationnel et utilisable où l’on désirera.

Au cas où vous vous demanderiez pourquoi on utilise ce flag $isNew, c’est pour renvoyer le bon code HTTP: une 201 en cas de création, une 200 en cas de mise à jour:

    /**
     * @param Request   $request
     * @param MyEntity  $myEntity
     * @param bool      $isNew
     *
     * @ParamConverter("myEntity", converter="app_get_or_create_entity_converter", options={
     *  "repository_method" = "getMyEntityById"
     * })
     */
    public function putMyEntityAction(Request $request, MyEntity $myEntity, $isNew)
    {
        $statusCode = Response::HTTP_OK;

        if ($isNew) {
            $statusCode = Response::HTTP_CREATED;
        }
        [...]
    }

 

Rédigé par

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.