Symfony 2: Как использовать ParamConverter с методом PUT для получения или создания объекта сущности

Мне нужно реализовать API с методом PUT, и я хотел бы использовать ParamConverter в своем контроллере для поиска существующего объекта сущности или, если объект сущности не существует, для создания нового.

Однако стандартный Symfony ParamConverter возвращает исключение, если не находит объект сущности в репозитории.

Есть ли у вас идеи сделать это красиво и чисто? Спасибо.

Вот пример того, что я хотел бы сделать (я использую FOS REST Bundle для обработки запроса PUT):

/**
 * @param Request $request
 * @return View
 *
 * @ParamConverter("video")
 *
 */
public function putVideosAction(Request $request, Video $video)
{
    try {
        return $this->getHandlerVideos()->put($video, $request->request->all());
    } catch (InvalidFormException $e) {
        return $e->getForm();
    }
}

person Youri_G    schedule 28.07.2016    source источник


Ответы (2)


Вот решение. Пожалуйста, поделитесь своими мыслями по этому поводу.

В вашем контроллере я бы сделал это:

/**
 * @param Request $request
 * @return View
 *
 * @Rest\Put()
 * @Rest\View()
 *
 * @ParamConverter("video", converter="app_get_or_create_entity_converter", options={"repository_method" = "findOneById"})
 */
public function putVideosAction(Request $request, Video $video)
{
    try {
        $video = $this->getHandlerVideos()->put($video, $request->request->all());
        return $video;
    } catch (InvalidFormException $e) {
        return $e->getForm();
    }
}

Я бы написал таким образом преобразователь динамических параметров:

    class GetOrCreateEntityConverter implements \Sensio\Bundle\FrameworkExtraBundle\Request\ParamConverter\ParamConverterInterface
{
    /**
     * @var EntityManagerInterface
     */
    protected $entityManager;

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

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

    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);

        $repositoryMethod = $options['repository_method'];

        if (!is_callable([$repository, $repositoryMethod])) {
            throw new \BadMethodCallException($repositoryMethod . ' function does not exist.', 405);
        }

        $entity = $repository->$repositoryMethod($id);

        if (null === $entity) {
            $entity = new $class;
        }

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

Если вы спросите, почему я возвращаю форму в улове, перейдите по ссылке https://github.com/liuggio/symfony2-rest-api-the-best-2013-way/blob/master/src/Acme/BlogBundle/Controller/PageController.php

person john saulnier    schedule 28.07.2016
comment
Круто, спасибо. Мне нравится этот универсальный ParamConverter. Таким образом, я могу использовать его с другими моими сущностями ;-) - person Youri_G; 29.07.2016

Вам нужно будет создать свой собственный paramConverter.

Во-первых, вот что вы хотите написать в своем контроллере:

/**
 * @ParamConverter("video", class = "MyBundle:Video", converter = "my_param_converter")
 * @param Request $request
 * @param Video $video
 * @return \Symfony\Component\HttpFoundation\Response
 */
public function putVideosAction(Request $request, Video $video)
{
    // your code..
}

Теперь напишем my_param_converter!

use Doctrine\Common\Persistence\ManagerRegistry;
use Sensio\Bundle\FrameworkExtraBundle\Request\ParamConverter\ParamConverterInterface;
// ...

class MyParamConverter implements ParamConverterInterface
{
    private $registry;

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

    /**
     * Check if object supported by our paramConverter
     *
     * @param ParamConverter $configuration
     */
    public function supports(ParamConverter $configuration)
    {
        // In this case we can do nothing and just return
        if (null === $this->registry || !count($this->registry->getManagers())) {
            return false;
        }

        // Check if the class is set in configuration
        if(null === $configuration->getClass()) {
            return false;
        }

        // Get actual entity manager for class
        $em = $this->registry->getManagerForClass($configuration->getClass());

        // Check what you need to check...

        return true;
    }

    public function apply(Request $request, ParamConverter $configuration)
    {
        $videoId = $request->attributes->get('video');

        if(null === videoId) {
            throw new \InvalidArgumentException('Route attribute is missing');
        }

        // Get actual entity manager for class
        $em = $this->registry->getManagerForClass($configuration->getClass());

        $repository = $em->getRepository($configuration->getClass());

        // Try to find the video
        $video = $$repository->findOneById($videoId);

        if($video === null || !($video instanceof Video)) {
            // Here you can create your new video object
        }

        // Map video to the route's parameter
        $request->attributes->set($configuration->getName(), $video);
    }
}

После того, как ваш новый paramConverter напишет, объявите его как службу:

services:
    app.param_converter.my_param_converter:
        class: YourBundle\Path\To\MyParamConverter
        tags:
            - { name: request.param_converter, converter: my_param_converter }
        arguments:
            - @?doctrine 

Вот и все!

Мой ответ во многом вдохновлен этой статьей и надеюсь полезно.

person lolmx    schedule 28.07.2016
comment
Спасибо за помощь :-) - person Youri_G; 29.07.2016