phpsymfonyapi-platform.com

Universal controller for soft delete in ApiPlatform


I use ApiPlatform and want to change the delete request behavior for my entities. In every entity, I have a status property - when a delete request is called, it should set status to 0 instead of removing the record from the database.

I have a solution for this (a custom controller), but I have to create a new controller for each entity. Is it possible to create one controller and reuse it?

My plan was:

interface SoftDeletableInterface
{
    public function setStatus(int $status): static;
}
#[ApiResource(
    operations: [
        new Delete(
            controller: ChannelSoftDeleteController::class
        ),
    ]
)]
public function __invoke(string $entityClassName, $id): Response
{
    $entityClass = 'App\\Entity\\' . ucfirst($entityClassName);

    if (!class_exists($entityClass) || !is_subclass_of($entityClass, SoftDeletableInterface::class))    
    {
        return new Response(null, Response::HTTP_METHOD_NOT_ALLOWED);
    }

    $repository = $this->entityManager->getRepository($entityClass);
    $entity = $repository->find($id);

    if (!$entity) 
    {
        return new Response(null, Response::HTTP_NOT_FOUND);
    }

    $entity->setStatus(0);

    $this->entityManager->persist($entity);
    $this->entityManager->flush();

    return new Response(null, Response::HTTP_NO_CONTENT);
}

But it generates error:

Could not resolve argument $entityClassName of "App\Controller\SoftDeleteController::__invoke()", maybe you forgot to register the controller as a service or missed tagging it with the "controller.service_arguments"?

When the __invoke argument is specific class (Channel in my case) everything works. I also tried passing the argument without type declaration to __invoke:

public function __invoke($entity)

but it's generate same error


Solution

  • At least in ApiPlatform v3.3, deleting the entity happens in a service named api_platform.doctrine.orm.state.remove_processor, implemented in ApiPlatform\Doctrine\Common\State\RemoveProcessor. You could try to override this service like this:

    class SoftDeleteProcessor implements ProcessorInterface
    {
        use ClassInfoTrait;
    
        public function __construct(private readonly ManagerRegistry $managerRegistry)
        {
        }
    
        public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []): void
        {
            if (!$manager = $this->getManager($data)) {
                return;
            }
    
            $data->setStatus(0);
            $manager->persist($data);
            $manager->flush();
        }
    
        /**
         * Gets the Doctrine object manager associated with given data.
         */
        private function getManager($data): ?DoctrineObjectManager
        {
            return \is_object($data) ? $this->managerRegistry->getManagerForClass($this->getObjectClass($data)) : null;
        }
    }