Api Platform conference
Register now
API Platform Conference
September 19-20, 2024 | Lille & online

The international conference on the API Platform Framework

API Platform Conference 2024: meet the best PHP, JavaScript and API experts

Learn more about the event, register for the conference, and get ready for two days of inspiration, ideas, and knowledge-sharing with our incredible lineup of renowned specialists and advocates.

Register now

Delete operation with validation

validation expert
Let’s add a custom Constraint.
// src/App/Validator.php
namespace App\Validator;
use Symfony\Component\Validator\Constraint;
class AssertCanDelete extends Constraint
    public string $message = 'For whatever reason we denied removeal of this data.';
    public string $mode = 'strict';
    public function getTargets(): string
        return self::CLASS_CONSTRAINT;
And a custom validator following Symfony’s naming conventions.
// src/App/Validator.php
namespace App\Validator;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
class AssertCanDeleteValidator extends ConstraintValidator
    public function validate(mixed $value, Constraint $constraint): void

// src/App/Entity.php
namespace App\Entity;
use ApiPlatform\Metadata\Delete;
use ApiPlatform\Validator\Exception\ValidationException;
use App\Validator\AssertCanDelete;
use Doctrine\ORM\Mapping as ORM;
By default, validation is not triggered on a DELETE operation, let’s activate it.
    validate: true,
Just as with serialization we can add validation groups.
    validationContext: ['groups' => ['deleteValidation']],
    exceptionToStatus: [ValidationException::class => 403]
Here we use the previously created constraint on the class directly.
#[AssertCanDelete(groups: ['deleteValidation'])]
class Book
    #[ORM\Id, ORM\Column, ORM\GeneratedValue]
    private ?int $id = null;
    public string $title = '';
    public function getId()
        return $this->id;

// src/App/Playground.php
namespace App\Playground;
use Symfony\Component\HttpFoundation\Request;
function request(): Request
    return Request::create(uri: '/books/1', method: 'DELETE', server: ['CONTENT_TYPE' => 'application/ld+json']);

// src/App/Fixtures.php
namespace App\Fixtures;
use App\Entity\Book;
use Doctrine\Bundle\FixturesBundle\Fixture;
use Doctrine\Persistence\ObjectManager;
use function Zenstruck\Foundry\anonymous;
use function Zenstruck\Foundry\faker;
use function Zenstruck\Foundry\repository;
final class BookFixtures extends Fixture
    public function load(ObjectManager $manager): void
        $bookFactory = anonymous(Book::class);
        if (repository(Book::class)->count()) {
            fn () => [
                'title' => faker()->name(),

// src/DoctrineMigrations.php
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
final class Migration extends AbstractMigration
    public function up(Schema $schema): void

You can also help us improve this guide.

Made with love by

Les-Tilleuls.coop can help you design and develop your APIs and web projects, and train your teams in API Platform, Symfony, Next.js, Kubernetes and a wide range of other technologies.

Learn more

Copyright © 2023 Kévin Dunglas

Sponsored by Les-Tilleuls.coop