question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

[QUESTION] How to create multiple resources

See original GitHub issue

Hi, I am quite new to api-platform so excuse me if it is a newbie question.

I need to expose an api endpoint to create (or even update) multiple instances of a single resource type.

Something like a POST to /teams_multiple which accepts a json like:

[
{
  "name": "Team 1",
  "teamLogo": "logo1.png"
},
{
  "name": "Team 2",
  "teamLogo": "logo2.png"
},
...
]

Which is the best practice in this case?

Issue Analytics

  • State:closed
  • Created 6 years ago
  • Comments:10 (1 by maintainers)

github_iconTop GitHub Comments

12reactions
Renrhafcommented, Sep 20, 2021

Here is an example of my use case. I needed to create many invitations, each one with an email address, at once on the same endpoint.

Firstly, you define your custom operation :

 * @ApiResource(
 *        ...
 *        collectionOperations={
 *          ...
 *          "multiple"={
 *             "method"="POST",
 *             "path"="/invitations/multiple",
 *             "input"=InvitationMultipleRequest::class,
 *             "output"=InvitationMultipleResponse::class,
 *             "controller"=CreateMultipleAction::class,
 *             "normalization_context"={"skip_null_values"=false, "groups"={"invitation_read", "invitation_read_tenant", "invitation_read_user", "invitation_read_role", "tenant_minimal", "user_minimal", "role_minimal"}},
 *             "denormalization_context"={"groups"={"invitation_multiple_create"}},
 *             "validation_groups"={"default", "invitation_multiple_create"},
 *             "openapi_context"={
 *                 "summary"="Creates multiple invitations resources.",
 *                 "description"="Creates at once multiple invitation entities, in a batch."
 *             }
 *         }
 *         ...

Secondly, you create your DTOs and DataTransformers according to the data needed, in my case InvitationMultipleRequest for the input, InvitationMultipleResponse for the output.

DTO InvitationMultipleRequest :

<?php

namespace App\Dto\Invitation;

use Symfony\Component\Serializer\Annotation\Groups;
use Symfony\Component\Validator\Constraints as Assert;

/**
 * Class InvitationMultipleRequest.
 *
 * Custom input DTO to wrap multiple invitations request.
 * NB: This class is simplified for the example, adapt it to your needs.
 */
final class InvitationMultipleRequest
{

    /**
     * Invited email addresses.
     * 
     * @Groups({"invitation_multiple_create"})
     * @Assert\NotBlank(groups={"invitation_multiple_create"})
     * @Assert\Unique(groups={"invitation_multiple_create"})
     */
    protected ?array $emails = null;

    public function __construct(?array $emails)
    {
        $this->emails = $emails;
    }

    public function getEmails(): ?array
    {
        return $this->emails;
    }

}

And it’s data transformer :

<?php

namespace App\DataTransformer\Invitation;

use ApiPlatform\Core\DataTransformer\DataTransformerInterface;
use ApiPlatform\Core\Validator\ValidatorInterface;
use App\Dto\Invitation\InvitationMultipleRequest;
use App\Entity\Tenant\Invitation;

/**
 * Class InvitationMultipleRequestDataTransformer.
 *
 * Data transformer for the InvitationMultipleRequest DTO.
 * NB: This class is simplified for the example, adapt it to your needs.
 */
final class InvitationMultipleRequestDataTransformer implements DataTransformerInterface
{

    public function __construct(
        protected ValidatorInterface $validator
    ) {}

    /**
     * {@inheritdoc}
     */
    public function transform($data, string $to, array $context = [])
    {
        /** @var InvitationMultipleRequest $data */
        $this->validator->validate($data, $context);

        return $data;
    }

    /**
     * {@inheritdoc}
     */
    public function supportsTransformation($data, string $to, array $context = []): bool
    {
        // in the case of an input, the value given here is an array (the JSON decoded).
        // if it's a media object we transformed the data already
        if ($data instanceof Invitation) {
            return false;
        }

        return Invitation::class === $to && InvitationMultipleRequest::class === ($context['input']['class'] ?? null);
    }
}

DTO InvitationMultipleResponse :

<?php

namespace App\Dto\Invitation;

use App\Entity\Tenant\Invitation;

use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Symfony\Component\Serializer\Annotation\Groups;

/**
 * Class InvitationMultipleResponse.
 *
 * Custom input DTO to wrap multiple invitations response.
 * NB: This class is simplified for the example, adapt it to your needs.
 */
final class InvitationMultipleResponse
{

    /**
     * Invitation entities.
     *
     * @var ?Collection<Invitation>
     *
     * @Groups({"invitation_read"})
     */
    protected ?Collection $invitations = null;

    public function __construct(?Collection $invitations = null)
    {
        $this->invitations = $invitations ?? new ArrayCollection();
    }

    public function getInvitations(): ?Collection
    {
        return $this->invitations;
    }

    public function setInvitations(?Collection $invitations): void
    {
        $this->invitations = $invitations;
    }

    public function addInvitation(Invitation $invitation): void
    {
        $this->invitations->add($invitation);
    }

}

Ands it’s data transformer :

<?php

namespace App\DataTransformer\Invitation;

use ApiPlatform\Core\DataTransformer\DataTransformerInterface;
use ApiPlatform\Core\Validator\ValidatorInterface;
use App\Dto\Invitation\InvitationMultipleResponse;
use App\Entity\Tenant\Invitation;

/**
 * Class InvitationMultipleResponseDataTransformer.
 *
 * Data transformer for the InvitationMultipleResponse DTO.
 */
final class InvitationMultipleResponseDataTransformer implements DataTransformerInterface
{

    public function __construct(
        protected ValidatorInterface $validator,
    ) {}

    /**
     * {@inheritdoc}
     */
    public function transform($data, string $to, array $context = [])
    {
        /** @var InvitationMultipleResponse $data */
        $this->validator->validate($data);

        return $data;
    }

    /**
     * {@inheritdoc}
     */
    public function supportsTransformation($data, string $to, array $context = []): bool
    {
        // in the case of an input, the value given here is an array (the JSON decoded).
        // if it's a media object we transformed the data already
        if ($data instanceof Invitation) {
            return false;
        }

        return Invitation::class === $to && InvitationMultipleResponse::class === ($context['output']['class'] ?? null);
    }
}

Note : I’m doing a single data transformer for each DTO, maybe it’s not the right way to go. If someone has some insight on this I would be more than happy to improve this part of my app and reduce code duplication.

Thirdly, you create your custom controller for implementing the action code, in my case CreateMultipleAction Controller. This code contains some other service classes that I won’t share in this post for more clarity. Edit the code and put whatever you need in your controller.

<?php

namespace App\Controller\Invitation;

use ApiPlatform\Core\Validator\ValidatorInterface;
use App\Dto\Invitation\InvitationMultipleRequest;
use App\Dto\Invitation\InvitationMultipleResponse;
use App\Entity\Tenant\Invitation;
use App\Model\Security\Permission;
use App\Service\Invitation\InvitationManager;
use App\Service\Traits\EntityManagerAwareTrait;
use App\Service\User\UserManager;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;

/**
 * Create multiple invitation entities action.
 */
final class CreateMultipleAction
{
    use EntityManagerAwareTrait;

    public function __construct(
        protected ValidatorInterface $validator,
        protected InvitationManager $invitationManager,
        protected UserManager $userManager,
        protected AuthorizationCheckerInterface $authorizationChecker,
    ) {}

    /**
     * React to a resend action on an invitation entity.
     *
     * @param InvitationMultipleRequest $data
     *   Invitation multiple request entity.
     *
     * @return InvitationMultipleResponse
     *   Invitation multiple response entity.
     */
    public function __invoke(InvitationMultipleRequest $data): InvitationMultipleResponse
    {
        $response = new InvitationMultipleResponse();
        $currentUser = $this->userManager->getCurrentUser();
        $currentDate = new \DateTime('now');

        // Check all emails as individual invitation.
        $emails = $data->getEmails();
        foreach ($emails as $email) {
            $invitation = new Invitation();
            $invitation->setEmail($email);
            $invitation->setTenant($data->getTenant());
            $invitation->setRole($data->getRole());
            $invitation->setMessage($data->getMessage());
            $invitation->setUser($currentUser);

            // Validate permissions.
            if (!$this->authorizationChecker->isGranted(Permission::CREATE, $invitation)) {
                throw new AccessDeniedHttpException('You are not allowed to create such an invitation');
            }

            // Validate data.
            $this->validator->validate($invitation, [
                'groups' => [
                    'invitation_create',
                ],
            ]);

            // Save the invitation.
            $this->entityManager->persist($invitation);

            // Send the email.
            $this->invitationManager->send($invitation);
            $invitation->setSent($currentDate);

            $response->addInvitation($invitation);
        }

        // Save to database.
        $this->entityManager->flush();

        return $response;
    }
}

image

And it works like a charm. Thank you API Platform team !

6reactions
sunviwocommented, Jun 4, 2019

@Simperfit would you have any idea have to do this ? A snippet would be highly appreciated.

Read more comments on GitHub >

github_iconTop Results From Across the Web

How to create multiple resources when required by terraform
To solve a similar problem, I created a workspace for each environemnt (dev1, dev2, dev3, dev4, and prod), and then a Makefile that...
Read more >
Create Multiple Resources - Beginner's Guide to Terraform
This lesson covers different aspects of creating multiple instances of the same resource in Terraform. In it, we'll go over the following concepts:....
Read more >
Add resources to your project - Microsoft Support
You can add several types of resources to your project. ... To create a budget resource, select the resource, right-click the resource name,...
Read more >
Multiple Resource View - Verbum Support
Open the Multiple Resource menu. Click the drop-down menu to the right of the Multiple Resource icon in the resource panel menu. Select...
Read more >
Encouraged students to use multiple resources (e.g., Internet ...
Helping students find answers to questions on their own is also an effective strategy for learning. Find or create a problem for students...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found