agentsclimarketplace

Custom validator

Skill api-platform/skillset/skills/custom-validator

API Platform agent skills

Install
npx -y skills add api-platform/skillset --skill custom-validator

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

2 things to look at

  • no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
  • 24 stars24 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

Creates custom validation constraints for API Platform resources. Use whenever a rule goes beyond built-in Symfony constraints — rate or plan limits, uniqueness checks, domain-specific formats, cross-field or conditional validation, or any 'reject the request when X' business rule on write operations, even if the user doesn't say 'validator'.

SKILL.md

4.8 KB, as published. Nobody here has run it

Custom Validation Constraints

Create custom validators when built-in Symfony constraints are insufficient.

Constraint Class

<?php
namespace App\Validator;

use Symfony\Component\Validator\Constraint;

#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
class IsValidAccountLimit extends Constraint
{
    public string $message = 'Account limit reached for your current plan.';
    public int $limit = 50;
}

Validator Class

Convention: {ConstraintName}Validator in the same namespace.

<?php
namespace App\Validator;

use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;

class IsValidAccountLimitValidator extends ConstraintValidator
{
    public function __construct(
        private readonly UserHelper $userHelper,
        private readonly AccountRepository $accountRepository,
    ) {}

    public function validate(mixed $value, Constraint $constraint): void
    {
        if (!$constraint instanceof IsValidAccountLimit) {
            throw new \InvalidArgumentException('Unexpected constraint type');
        }

        if (null === $value || '' === $value) {
            return;
        }

        $user = $this->userHelper->getUser();
        $count = $this->accountRepository->countByUser($user->getId());

        if ($count >= $constraint->limit) {
            $this->context->buildViolation($constraint->message)
                ->addViolation();
        }
    }
}

Usage on Properties

use App\Validator\IsValidAccountLimit;
use Symfony\Component\Validator\Constraints as Assert;

class Account
{
    #[Assert\NotBlank(groups: ['account:write'])]
    #[Assert\Email(groups: ['account:write'])]
    #[IsValidAccountLimit(groups: ['account:write'])]
    public ?string $address;
}

Class-Level Constraint (UniqueEntity)

For constraints that validate across multiple fields:

#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::IS_REPEATABLE)]
class MongoDBUnique extends Constraint
{
    public array $fields = [];
    public string $message = 'This value is already used.';

    public function getTargets(): string
    {
        return self::CLASS_CONSTRAINT;
    }
}

Usage:

#[MongoDBUnique(fields: ['address'], groups: ['account:write', 'account:patch'])]
class Account {}

Validation Groups with Operations

Target a constraint to specific operations by passing groups: (as shown above) and wiring those groups into each operation's validationContext — see operations for the full per-operation setup.

Callback Validation

For quick one-off validation on DTOs:

use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Component\Validator\Context\ExecutionContextInterface;

class SendMessage
{
    public ?string $text = null;
    public ?string $html = null;

    #[Assert\Callback]
    public function validateContent(ExecutionContextInterface $context): void
    {
        if (null === $this->text && null === $this->html) {
            $context->buildViolation('Either text or html content is required.')
                ->atPath('text')
                ->addViolation();
        }
    }
}

Laravel

Laravel does not use Symfony Constraints/ConstraintValidator. Validation is declared with Laravel validation rules via the rules option on #[ApiResource] or per-operation (rules can be an array, a closure, or a FormRequest class-string). The ValidateProvider runs them before the processor and emits the same 422 ConstraintViolationList shape.

use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\Post;

#[ApiResource(rules: ['title' => 'required|min:2'])]
#[Post(rules: ['isbn' => ['required', 'string']])] // per-operation override
class Book extends Model {}

For business rules beyond built-in rules, write a Laravel custom rule (a class implementing Illuminate\Contracts\Validation\ValidationRule, scaffold with php artisan make:rule) or a closure rule, and reference it in rules. A FormRequest class-string is also accepted — its authorize()/rules() run, and an AuthorizationException maps to 403, ValidationException to 422. Validation groups and Symfony validationContext do not apply on Laravel; scope rules per operation instead. Partial PATCH can relax required to sometimes via partial_patch_validation in config/api-platform.php.

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.