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:

API Platform 2.x
API Platform 4.x

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: Shop or Admin

  • Resource: Entity name (singular)

  • Type: PersistProcessor for create/update, RemoveProcessor for 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:

  1. Namespace: DataPersisterStateProcessor\Admin\Product

  2. Class name: CreateProductDataPersisterPersistProcessor

  3. Interface: DataPersisterInterfaceProcessorInterface

  4. Method: persist()process()

  5. Removed: supports() method - no longer needed

  6. Removed: remove() method - create separate RemoveProcessor if needed

  7. Added: Operation $operation parameter

  8. Changed: Constructor now injects core processor instead of EntityManager

  9. Class modifiers: Added final readonly

  10. PHPDoc: 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:

  1. Service ID: Follow pattern {vendor}.{plugin}.state_processor.{section}.{resource}.{type}

  2. Class: Updated to new namespace

  3. First argument: Core processor instead of EntityManager

    • api_platform.doctrine.orm.state.persist_processor for create/update

    • api_platform.doctrine.orm.state.remove_processor for delete

  4. Tag: api_platform.data_persisterapi_platform.state_processor

  5. Priority: Added priority="10"

Service Registration for Remove Processor

config/services/stateProcessor/stateProcessor.xml:

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 → Use PersistProcessor

  • ApiPlatform\Metadata\Put - Full update operations → Use PersistProcessor

  • ApiPlatform\Metadata\Patch - Partial update operations → Use PersistProcessor

  • ApiPlatform\Metadata\Delete - Delete operations → Use RemoveProcessor

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

  1. No supports() method: Linking is now explicit via processor attribute

  2. Decorator pattern: Most StateProcessors wrap core processors

  3. Core processors:

    • api_platform.doctrine.orm.state.persist_processor - for persistence

    • api_platform.doctrine.orm.state.remove_processor - for deletion

  4. readonly classes: Follow Sylius convention with final readonly class

  5. Return types: Always mixed - return the processed data

  6. Service ID format: {vendor}.{plugin}.state_processor.{section}.{resource}.{type}

  7. Priority: Use priority="10" in service tags

  8. Separate processors: Create separate PersistProcessor and RemoveProcessor classes

Reference

For more examples, check Sylius core StateProcessors:

Example files:

  • Admin/Country/PersistProcessor.php - Processor with validation

  • Admin/ProductVariant/RemoveProcessor.php - Remove processor with checks

  • Shop/Order/PersistProcessor.php - Complex business logic processor

Last updated

Was this helpful?