Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[HELP] Add a custom operation without overriding an existing item/collection operation #1482

Closed
mikaelcom opened this issue Nov 7, 2017 · 5 comments

Comments

@mikaelcom
Copy link

First of all, it rocks!

I'm trying to add a bulk operation for a collection of entities of the same type so I did this way:

namespace AppBundle\Action;

use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
use Symfony\Component\Routing\Annotation\Route;
use AppBundle\Entity\Entity;
use Doctrine\ORM\EntityManagerInterface;

class EntityBulk
{

    /**
     *
     * @var EntityManagerInterface $manager
     */
    protected $entityManager;

    public function __construct(EntityManagerInterface $manager)
    {
        $this->entityManager = $manager;
    }

    /**
     * @Route(name="api_entity_post_bulk", path="/runner_subscriptions/bulk", defaults={"_api_resource_class"=Entity::class, "_api_collection_operation_name"="bulk"})
     *
     * @method ("POST")
     */
    public function __invoke($data)
    {
        array_map(function ($item) {
            $this->entityManager->persist($item);
        }, $data);
        $this->entityManager->flush();
        return $data;
    }
}

As PUT is defined for an item collection, I had to use the POST verb. In addition, I had to allow the update as my goal is to allow an update on many entities. So I added a custom normalizer:

<?php
namespace AppBundle\Serializer;

use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
use AppBundle\Entity;
use \InvalidArgumentException;

class CustomNormalizer implements NormalizerInterface, DenormalizerInterface
{
    /**
     *
     * @var NormalizerInterface $normalizer
     */
    private $normalizer;

    /**
     *
     * @param NormalizerInterface $normalizer
     * @throws InvalidArgumentException
     */
    public function __construct(NormalizerInterface $normalizer)
    {
        if (!$normalizer instanceof DenormalizerInterface) {
            throw new InvalidArgumentException('The normalizer must implement the DenormalizerInterface');
        }

        $this->normalizer = $normalizer;
    }

    /**
     *
     * {@inheritDoc}
     * @see \Symfony\Component\Serializer\Normalizer\DenormalizerInterface::denormalize()
     */
    public function denormalize($data, $class, $format = null, array $context = [])
    {
        if (is_array($data) && Entity::class === $class) {
            // bulk operation must update on POST as PUT is only allowed for item and not collection
            // https://api-platform.com/docs/core/operations
            $context['api_allow_update'] = true;
            return array_map(function($item) use ($class, $format, $context) {
                return $this->normalizer->denormalize($item, $class, $format, $context);
            }, $data);
        }
        return $this->normalizer->denormalize($data, $class, $format, $context);
    }

    /**
     *
     * {@inheritDoc}
     * @see \Symfony\Component\Serializer\Normalizer\DenormalizerInterface::supportsDenormalization()
     */
    public function supportsDenormalization($data, $type, $format = null)
    {
        return $this->normalizer->supportsDenormalization($data, $type, $format);
    }

    /**
     *
     * {@inheritDoc}
     * @see \Symfony\Component\Serializer\Normalizer\NormalizerInterface::normalize()
     */
    public function normalize($object, $format = null, array $context = [])
    {
        return $this->normalizer->normalize($object, $format, $context);
    }

    /**
     *
     * {@inheritDoc}
     * @see \Symfony\Component\Serializer\Normalizer\NormalizerInterface::supportsNormalization()
     */
    public function supportsNormalization($data, $format = null)
    {
        return $this->normalizer->supportsNormalization($data, $format);
    }
}

Unfortunately, the POST verb is now only available with the /api/entities/bulk path.

My questions are:

  • Is it possible to add an additional route to the API on a verb already in use?
  • Can I use PUT for a collection of entities?
  • Do you see a better way to achieve my goal?

Thanks in advance 😄

@mikaelcom
Copy link
Author

Okay so I simply used the below collectionOperations definition and it allowed me to have both api_entity_post_collection and api_entity_post_bulk routes :

 *  collectionOperations={
 *      "bulk"={
 *          "method"="POST",
 *          "route_name"="api_entity_post_bulk"
 *      },
 *      "post"={"method"="POST"},
 *      "get"={"method"="GET"}
 *  }

I also reviewed the custom denormalize method:

public function denormalize($data, $class, $format = null, array $context = [])
    {
        if (is_array($data) && 
            OperationType::COLLECTION === $context['operation_type'] && 
            'bulk' === $context['collection_operation_name'] && 
            Entity::class === $class) {
            // bulk operation must update on POST as PUT is only allowed for item and not collection
            // https://api-platform.com/docs/core/operations
            $context['api_allow_update'] = true;
            return array_map(function($item) use ($class, $format, $context) {
                return $this->normalizer->denormalize($item, $class, $format, $context);
            }, $data);
        }
        return $this->normalizer->denormalize($data, $class, $format, $context);
    }

@coudenysj
Copy link
Contributor

@mickaelprevot How did you change the swagger model to reflect you now expect an array if entities?

@ghost
Copy link

ghost commented Aug 7, 2018

hi @mikaelcom I'm trying to use your approach to get bulk post working but I'm getting a ServiceCircularReferenceException exception regarding the Normalizer/Denormalizer.
Do you know about it?

Thanks

@mikaelcom
Copy link
Author

@maaxxicarvalho in my services.yml file, I have these settings :

    AppBundle\Serializer\CustomItemNormalizer:
        arguments:
            - '@api_platform.serializer.normalizer.item'
        tags: [ 'serializer.normalizer' ]

That's all

@ghost
Copy link

ghost commented Aug 8, 2018

@mikaelcom it worked! thanks for your fast reply!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants