9.6 API: Migrate DataTransformer to SerializerContextBuilder
This step migrates API Platform 2.x DataTransformers to API Platform 4.x SerializerContextBuilders.
When to skip this step:
Your plugin doesn't have custom DataTransformer classes
When to do this step:
You have custom DataTransformer classes in
src/DataTransformer/Your DataTransformers inject contextual data (channel code, locale code, user ID, etc.) into input DTOs
Overview of Changes
API Platform 4.x dropped DataTransformers. For DataTransformers that inject contextual data into input DTOs, the recommended approach is to use SerializerContextBuilders:
DataTransformerInterface
SerializerContextBuilderInterface
InputDataTransformerInterface
SerializerContextBuilderInterface
transform() method
createFromRequest() method
Property modification after deserialization
Constructor argument injection during deserialization
Key changes:
DataTransformers that modify input DTOs → SerializerContextBuilders
DataTransformers that format output → May need custom Normalizers
New decorator pattern for SerializerContextBuilders
Use PHP 8 attributes to mark DTOs
Inject values into DTO constructors, not properties
Important: Not all DataTransformers should become SerializerContextBuilders. Only those that inject contextual data (like channel code, locale code, current user ID) from the request/session into input DTOs.
1. Identify Files to Migrate
Check if you have DataTransformers:
2. Determine Migration Strategy
Review each DataTransformer and determine the appropriate migration path:
Migrate to SerializerContextBuilder if:
✅ Injects channel code into input DTO
✅ Injects locale code into input DTO
✅ Injects current user ID into input DTO
✅ Injects any contextual data from request/session into DTO constructor
Alternative approaches if:
❌ Transforms output format → Use custom Normalizer
❌ Performs complex data transformation → Use StateProvider/StateProcessor
❌ Validates data → Use Symfony Validator constraints
3. Migrate DataTransformer to SerializerContextBuilder
Directory Structure
Follow Sylius conventions for organization:
Old structure:
New structure:
Example Migration: Channel Code Injection
Before (API Platform 2.x):
src/DataTransformer/ChannelCodeAwareInputCommandDataTransformer.php:
After (API Platform 4.x):
src/Serializer/ContextBuilder/ChannelCodeAwareContextBuilder.php:
Key Changes:
Namespace:
DataTransformer→Serializer\ContextBuilderClass name: Remove "DataTransformer" suffix, add "ContextBuilder" suffix
Extends: Extend
AbstractInputContextBuilderfrom SyliusInterface: Removed
DataTransformerInterface(parent handles it)Method:
transform()→supports()+resolveValue()Removed:
supportsTransformation()methodConstructor: Added decorator, attribute class, and default parameter name
Class modifiers: Added
finaland optionallyreadonlyLogic: Changed from property modification to value resolution
Create Attribute Class
src/Attribute/ChannelCodeAware.php:
This PHP 8 attribute marks which DTOs should have channel code injected.
Update Input DTO/Command
src/Command/CreateProductCommand.php:
Changes:
Add attribute to class:
#[ChannelCodeAware]Ensure
channelCodeis a constructor parameter (not just a property)Property visibility changed to
protected
4. Update Service Registration
Before:
config/services/dataTransformer/dataTransformer.xml:
After:
config/services/serializer/contextBuilder.xml:
Changes:
Decorator pattern: Use
decorates="api_platform.serializer.context_builder"First argument:
.inner(the decorated core context builder)Second argument: Attribute class parameter
Third argument: Default constructor parameter name
Remaining arguments: Your dependencies (e.g.,
sylius.context.channel)Tag removed: No need for
api_platform.data_transformertagParameter: Define attribute class as a parameter for reusability
5. Example Migration: Locale Code Injection
Before (API Platform 2.x):
src/DataTransformer/LocaleCodeAwareInputCommandDataTransformer.php:
After (API Platform 4.x):
src/Serializer/ContextBuilder/LocaleCodeAwareContextBuilder.php:
src/Attribute/LocaleCodeAware.php:
6. Example Migration: Conditional Logic
If your DataTransformer has conditional logic:
Before:
After:
7. Alternative: Manual SerializerContextBuilder
If you don't want to use AbstractInputContextBuilder, you can implement SerializerContextBuilderInterface directly:
This approach gives you more control but requires more boilerplate code.
8. Remove Old Files
After migration is complete and tested:
9. Validate Changes
Clear the cache:
Verify SerializerContextBuilder is registered:
Test API endpoints:
Important Notes
Decorator pattern: Always use
decorates="api_platform.serializer.context_builder"Constructor injection: Values are injected into DTO constructor, not set as properties afterward
Attributes required: DTOs must be marked with custom PHP 8 attributes
AbstractInputContextBuilder: Sylius provides this base class for common patterns
Not all transformers migrate: Only those injecting contextual data become ContextBuilders
Final classes: Follow Sylius convention with
finalkeywordReadonly: Consider using
readonlyfor immutable dependencies
Common Patterns
Pattern: Multiple Values Injection
If you need to inject multiple values (channel code AND locale code):
Option 1: Use both attributes on the DTO
Both ContextBuilders will run and inject their values.
Option 2: Single ContextBuilder injecting multiple values
Pattern: Custom Attribute Parameter
If you want to customize the constructor parameter name per DTO:
The attribute's constructorArgumentName will be used by AbstractInputContextBuilder.
Reference
For more examples, check Sylius core SerializerContextBuilders:
Example files:
ChannelCodeAwareContextBuilder.php- Channel code injectionLocaleCodeAwareContextBuilder.php- Locale code injectionLoggedInShopUserIdAwareContextBuilder.php- User ID injectionAbstractInputContextBuilder.php- Base class for common pattern
Last updated
Was this helpful?
