Benjamin BALET
Benjamin BALET

Reputation: 969

Patch operation not working in custom StateProcessor

I have an API Resource with 4 operations (Get, List, Post and Patch). Everything is working fine except for the Patch operation. It returns a 404 error with this Error log entry:

[2024-02-23T13:28:28.603322+00:00] app.ERROR: An exception occured, transforming to an Error resource. {"exception":"[object] (Symfony\Component\HttpKernel\Exception\NotFoundHttpException(code: 0): Not Found at /home/bbalet/studi/web-ecf/vendor/api-platform/core/src/State/Provider/ReadProvider.php:87)","operation":{"ApiPlatform\Metadata\Patch":[]}} []

My ApiIssue.php looks like this:

<?php

namespace App\ApiResource;

use App\State\IssueStateProvider;
use App\State\IssueStateProcessor;
use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\GetCollection;
use ApiPlatform\Metadata\Get;
use ApiPlatform\Metadata\Post;
use ApiPlatform\Metadata\Patch;

#[ApiResource(
    shortName: 'Issue',
    description: 'Issue Management. Restricted to employees.',
    paginationEnabled: false,
    operations: [
        new GetCollection(
            description: 'Get the list of issues for a room.',
            uriTemplate: '/rooms/{id}/issues',
            security: "is_granted('ROLE_EMPLOYEE')",
            provider: IssueStateProvider::class
        ),
        new Get(
            description: 'Get an issue by its id.',
            uriTemplate: '/issues/{id}',
            security: "is_granted('ROLE_EMPLOYEE')",
            provider: IssueStateProvider::class
        ),
        new Post(
            description: 'Create a new issue.',
            security: "is_granted('ROLE_EMPLOYEE')",
            uriTemplate: '/rooms/{id}/issues',
            processor: IssueStateProcessor::class
        ),
        new Patch(
            description: 'Update an existing issue.',
            security: "is_granted('ROLE_EMPLOYEE')",
            uriTemplate: '/issues/{id}',
            processor: IssueStateProcessor::class
        )
    ]
)]
class ApiIssue
{
    public ?int $id = null;

    public ?string $title = null;

    public ?string $status = null;

    public ?string $description = null;
}

And my IssueStateProcessor.php like this:

<?php

namespace App\State;

use App\Repository\IssueRepository;
use App\Repository\RoomRepository;
use Doctrine\ORM\EntityManagerInterface;
use App\ApiResource\ApiIssue;
use App\Entity\Issue;

use ApiPlatform\Metadata\PostOperationInterface;
use ApiPlatform\Metadata\PatchOperationInterface;
use Psr\Log\LoggerInterface;
use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ProcessorInterface;
use Symfony\Bundle\SecurityBundle\Security;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\Security\Core\Exception\AuthenticationException;

class IssueStateProcessor implements ProcessorInterface
{
    public function __construct(
        private IssueRepository $issueRepository,
        private RoomRepository $roomRepository,
        private Security $security,
        private EntityManagerInterface $entityManager,
        private LoggerInterface $logger
    ) {}

    /**
     * {@inheritDoc}
     */
    public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []): object|array|null
    {
        $user = $this->security->getUser();
        if ($user === null) {
            throw new AuthenticationException('Not authenticated or invalid token.');
        }
        
        if ($operation instanceof PostOperationInterface) {
            $roomId = $uriVariables['id'];
            $this->logger->info('Creating a new issue for room ' . $roomId);
            $room = $this->roomRepository->findOneBy(['id' => $roomId]);
            if (!$room) {
                throw new NotFoundHttpException('Room not found.');
            } else { 
                $issue = new Issue();
                $issue->setUser($user);
                $issue->setRoom($room);
                $issue->setDate(new \DateTime());
                $issue->setTitle($data->title);
                $issue->setDescription($data->description);
                $issue->setStatus(Issue::STATUS_NEW);
                $this->entityManager->persist($issue);
                $this->entityManager->flush();
                //Return the updated issue from DB
                $issueApi = new ApiIssue();
                $issueApi->id = $issue->getId();
                $issueApi->title = $issue->getTitle();
                $issueApi->status = $issue->getStatusAsString();
                $issueApi->description = $issue->getDescription();
                return $issueApi;
            }
        } else if ($operation instanceof PatchOperationInterface) {
            $issueId = $uriVariables['id'];
            $this->logger->info('Updating issue ' . $issueId);
            $issue = $this->issueRepository->findOneBy(['id' => $issueId]);
            if (!$issue) {
                throw new NotFoundHttpException('Issue not found.');
            } else {            
                $issue->setTitle($data->title);
                $issue->setDescription($data->description);
                $issue->setStatus(intval($data->status));
                $this->entityManager->persist($issue);
                $this->entityManager->flush();
                //Return the updated issue from DB
                $issueApi = new ApiIssue();
                $issueApi->id = $issue->getId();
                $issueApi->title = $issue->getTitle();
                $issueApi->status = $issue->getStatusAsString();
                $issueApi->description = $issue->getDescription();
                return $issueApi;
            }
        }
    }
}

I'm not clear if the log entry says that it tries to read something from the StateProvider before giving it to the StateProcessor. And I don't know if I need a dedicated StateProcessor for each operation.

Upvotes: 1

Views: 300

Answers (2)

Bruno Guignard
Bruno Guignard

Reputation: 303

Its because now Patch and Put operation also need a Provider, not only a Processor.

Upvotes: 0

Benjamin BALET
Benjamin BALET

Reputation: 969

The documentation of API Platform is misleading. The correct code is as below:

use ApiPlatform\Metadata\Post;
use ApiPlatform\Metadata\Patch;

...

if ($operation instanceof Post) {
...
} else if ($operation instanceof Patch) {
...

Upvotes: 0

Related Questions