9.4 API: Migrate DataPersister to StateProcessor
This step migrates API Platform 2.x DataPersisters to API Platform 4.x StateProcessors.
When to skip this step:
Your plugin doesn't have custom DataPersister classes
When to do this step:
You have custom DataPersister classes in
src/DataPersister/
Overview of Changes
API Platform 4.x replaced DataPersisters with StateProcessors as part of the unified State system:
DataPersisterInterface
ProcessorInterface
ContextAwareDataPersisterInterface
ProcessorInterface
persist() method
process() method
remove() method
process() method (check operation type)
Key changes:
New interfaces and namespaces
Different method signatures
Explicit linking to operations (no more
supports()method)Organized directory structure
Decorator pattern for wrapping core processors
1. Identify Files to Migrate
Check if you have DataPersisters:
2. Migrate DataPersister to StateProcessor
Directory Structure
Follow Sylius conventions for organization:
Old structure:
New structure:
Pattern: StateProcessor/{Section}/{Resource}/{Type}Processor.php
Section:
ShoporAdminResource: Entity name (singular)
Type:
PersistProcessorfor create/update,RemoveProcessorfor delete, or custom name
Example Migration: Simple Persist
Before (API Platform 2.x):
src/DataPersister/CreateProductDataPersister.php:
After (API Platform 4.x):
src/StateProcessor/Admin/Product/PersistProcessor.php:
Key Changes:
Namespace:
DataPersister→StateProcessor\Admin\ProductClass name:
CreateProductDataPersister→PersistProcessorInterface:
DataPersisterInterface→ProcessorInterfaceMethod:
persist()→process()Removed:
supports()method - no longer neededRemoved:
remove()method - create separateRemoveProcessorif neededAdded:
Operation $operationparameterChanged: Constructor now injects core processor instead of EntityManager
Class modifiers: Added
final readonlyPHPDoc: Added
@implements ProcessorInterface<ProductInterface>
Example Migration: Processor with Validation
Before (API Platform 2.x):
src/DataPersister/CreateOrderDataPersister.php:
After (API Platform 4.x):
src/StateProcessor/Shop/Order/PersistProcessor.php:
Key Pattern:
Decorator pattern: StateProcessor wraps the core processor (
api_platform.doctrine.orm.state.persist_processor)Custom logic first: Validation/business logic runs before delegating to core processor
Return delegated result: Always return the result from the core processor
Example Migration: Remove Processor
Before (API Platform 2.x):
src/DataPersister/DeleteProductDataPersister.php:
After (API Platform 4.x):
src/StateProcessor/Admin/Product/RemoveProcessor.php:
3. Update Service Registration
Before:
config/services/dataPersister/dataPersister.xml:
After:
config/services/stateProcessor/stateProcessor.xml:
Changes:
Service ID: Follow pattern
{vendor}.{plugin}.state_processor.{section}.{resource}.{type}Class: Updated to new namespace
First argument: Core processor instead of EntityManager
api_platform.doctrine.orm.state.persist_processorfor create/updateapi_platform.doctrine.orm.state.remove_processorfor delete
Tag:
api_platform.data_persister→api_platform.state_processorPriority: Added
priority="10"
Service Registration for Remove Processor
config/services/stateProcessor/stateProcessor.xml:
4. Link StateProcessor to Operations
In API Platform 4.x, you must explicitly link processors to operations.
config/api_resources/resources/admin/Product.xml:
Before (implicit):
After (explicit):
Add the processor="{service_id}" attribute to link your StateProcessor to the operation.
Operation types that need processors:
ApiPlatform\Metadata\Post- Create operations → UsePersistProcessorApiPlatform\Metadata\Put- Full update operations → UsePersistProcessorApiPlatform\Metadata\Patch- Partial update operations → UsePersistProcessorApiPlatform\Metadata\Delete- Delete operations → UseRemoveProcessor
5. Remove Old Files
After migration is complete and tested:
6. Validate Changes
Clear the cache:
Verify routes are registered:
Verify StateProcessor is registered:
Test API endpoints:
Common Patterns
Pattern: Checking Operation Type
If you need different logic for create vs update:
Pattern: Accessing URI Variables
Access path parameters via $uriVariables:
Pattern: Accessing Context
Access request context via $context:
Important Notes
No supports() method: Linking is now explicit via
processorattributeDecorator pattern: Most StateProcessors wrap core processors
Core processors:
api_platform.doctrine.orm.state.persist_processor- for persistenceapi_platform.doctrine.orm.state.remove_processor- for deletion
readonly classes: Follow Sylius convention with
final readonly classReturn types: Always
mixed- return the processed dataService ID format:
{vendor}.{plugin}.state_processor.{section}.{resource}.{type}Priority: Use
priority="10"in service tagsSeparate processors: Create separate
PersistProcessorandRemoveProcessorclasses
Reference
For more examples, check Sylius core StateProcessors:
Example files:
Admin/Country/PersistProcessor.php- Processor with validationAdmin/ProductVariant/RemoveProcessor.php- Remove processor with checksShop/Order/PersistProcessor.php- Complex business logic processor
Last updated
Was this helpful?
