-
-
Notifications
You must be signed in to change notification settings - Fork 960
Description
Description
After upgrading from API Platform 4.2 to 4.3, we noticed that all validation constraints are executed twice for every write operation on resources that do not use Symfony's ObjectMapper (#[Map] attributes).
This causes duplicate external API calls (e.g. email validation services, payment providers, CRM lookups) and duplicate database queries from custom constraint validators on every single POST/PATCH/PUT request.
Root cause
PR #7731 introduced ValidateProcessor (priority 80) in the processor chain to validate entities after ObjectMapperInputProcessor maps a DTO to an entity. This makes sense for the ObjectMapper flow.
However, ValidateProvider (priority 200, provider chain) already validates the deserialized input. For resources where ObjectMapper is not used (no #[Map] attributes → canMap() returns null), ObjectMapperInputProcessor correctly skips mapping, but ValidateProcessor still validates unconditionally — on the exact same object that ValidateProvider already validated.
The processor chain in 4.3:
ObjectMapperOutputProcessor (priority 150)
→ ValidateProcessor (priority 80) ← always validates
→ ObjectMapperInputProcessor (priority 50) ← correctly skips when canMap() is null
→ WriteProcessor → custom processor
ValidateProcessor only checks canValidate(), not canMap():
// ValidateProcessor.php
public function process(mixed $data, Operation $operation, ...): mixed
{
// ...
if (false === ($operation->canValidate() ?? true)) {
return $this->decorated?->process(...);
}
// Always validates — does not check canMap()
$this->validator->validate($data, $operation->getValidationContext() ?? []);
return $this->decorated?->process(...);
}Setting validate: false on the operation is not a solution because it disables both ValidateProvider and ValidateProcessor — we still need the provider-level validation.
Impact
For any app that:
- Uses API Platform 4.3 with
symfony/object-mapperinstalled - Does not use
#[Map]attributes on its resources (entity-as-resource pattern)
Every write operation triggers validation twice. This is particularly problematic for validators that make external API calls or expensive database queries:
- Email validation services (ZeroBounce, etc.)
- CRM lookups (HubSpot, Salesforce, etc.)
- Payment provider calls (Stripe, etc.)
- Uniqueness checks via database queries
Expected behavior
ValidateProcessor should only validate when ObjectMapperInputProcessor actually performed a mapping. When canMap() is null or false, the processor-level validation is redundant since ValidateProvider already handled it.
Current workaround
We replaced ValidateProcessor with a custom implementation that checks canMap():
final class ObjectMapperValidateProcessor implements ProcessorInterface
{
public function __construct(
private readonly ProcessorInterface $decorated,
private readonly ValidatorInterface $validator,
) {
}
public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []): mixed
{
if (
!$data instanceof Response
&& $data
&& false !== ($operation->canWrite() ?? true)
&& false !== ($operation->canValidate() ?? true)
&& $operation->canMap() // ← only validate when ObjectMapper is used
) {
$this->validator->validate($data, $operation->getValidationContext() ?? []);
}
return $this->decorated->process($data, $operation, $uriVariables, $context);
}
}Registered in services.yaml:
api_platform.state_processor.validate:
class: App\Api\State\ObjectMapperValidateProcessor
decorates: 'api_platform.state_processor.main'
decoration_priority: 80
arguments:
- '@api_platform.state_processor.validate.inner'
- '@api_platform.validator'Question
Is this the intended behavior, or should ValidateProcessor natively check canMap() before validating? Would a PR adding this check be welcome?
Versions
- API Platform: 4.3.0
- Symfony: 8.0
- PHP: 8.4