ecotone/jms-converter
Ecotone JMS Converter integrates JMS Serializer with Ecotone’s media type conversion, letting you serialize/deserialize commands, events, and query responses using JMS annotations (groups, naming, handlers) for JSON/XML. Works with Symfony, Laravel, or PSR-11.
Install Dependencies Add the package and JMS Serializer to your Laravel project:
composer require ecotone/jms-converter jms/serializer
Set Up Ecotone Install Ecotone (if not already present) and configure it for Laravel:
composer require ecotone/ecotone
Follow the Laravel DDD/CQRS setup guide.
Configure JMS Converter
Bind the JMS converter to Ecotone’s media type converter system in your AppServiceProvider:
use Ecotone\MediaTypeConverter\MediaTypeConverter;
use Ecotone\MediaTypeConverter\Jms\JmsMediaTypeConverter;
public function register()
{
$this->app->singleton(MediaTypeConverter::class, function ($app) {
$converter = new MediaTypeConverter();
$converter->addConverter(new JmsMediaTypeConverter());
return $converter;
});
}
Annotate a DTO Use JMS annotations on a command, event, or query response:
use JMS\Serializer\Annotation as JMS;
#[JMS\Type("App\Dto\UserCreatedDto")]
class UserCreatedDto
{
#[JMS\SerializedName("user_id")]
#[JMS\Groups({"public"})]
public int $id;
#[JMS\Groups({"internal"})]
public string $email;
}
First Use Case Publish an event with JMS annotations:
use Ecotone\Attribute\Event;
#[Event]
class UserCreated
{
public function __construct(public UserCreatedDto $dto) {}
}
// In a command handler:
$this->bus->publish(new UserCreated(new UserCreatedDto()));
Command/Event Serialization Use JMS annotations to control how commands and events are serialized when published or consumed:
#[Command]
class CreateUserCommand
{
#[JMS\SerializedName("full_name")]
public string $name;
}
Query Response Serialization Annotate query responses to expose only specific fields to APIs:
#[Query]
class GetUserQuery {}
#[JMS\Type("App\Dto\UserDto")]
class UserDto
{
#[JMS\Groups({"api"})]
public int $id;
#[JMS\Exclude]
public string $apiToken;
}
// In a query handler:
return new UserDto($user->id, $user->apiToken);
Serialization Groups Use groups to conditionally include/exclude fields:
#[JMS\Groups({"public", "internal"})]
class UserDto { ... }
// Serialize only "public" fields:
$serializer->serialize($dto, 'json', ['groups' => ['public']]);
Custom Type Handlers Register custom handlers for complex types (e.g., DateTime, UUID):
$serializer->registerHandler(
new DateTimeHandler(),
DateTime::class
);
Laravel API Integration
// Controller
public function show(User $user)
{
return response()->json(
$this->bus->query(new GetUserQuery($user->id))
);
}
Event Sourcing
#[Event]
class UserEmailChanged
{
#[JMS\Type("DateTime<'Y-m-d\TH:i:sP'>")]
public Carbon $changedAt;
}
External Integrations
#[JMS\XmlRoot("user")]
class UserDto { ... }
Laravel Service Container
Bind the JmsMediaTypeConverter as a singleton and configure it once:
$this->app->singleton(JmsMediaTypeConverter::class, function ($app) {
$serializer = SerializerBuilder::create()
->addMetadataDir(__DIR__.'/../resources/config/jms', 'App\\')
->build();
return new JmsMediaTypeConverter($serializer);
});
Metadata Configuration
Store JMS metadata in resources/config/jms for better organization:
resources/
└── config/
└── jms/
├── UserCreatedDto.metadata.php
└── ...
Testing
Mock the MediaTypeConverter in tests:
$converter = $this->createMock(MediaTypeConverter::class);
$converter->method('convert')->willReturn($expectedDto);
$this->bus->setMediaTypeConverter($converter);
Performance Cache the JMS metadata builder for repeated use:
$metadataFactory = new MetadataFactory();
$metadataFactory->setCache(new FileCache(__DIR__.'/cache/jms'));
Circular References
JMS Serializer may fail on circular references (e.g., User ↔ Order). Use @MaxDepth or @Exclude:
#[JMS\MaxDepth(1)]
class User { ... }
Annotation Conflicts
Avoid mixing JMS annotations with Laravel’s native attributes (e.g., #[JsonSerializable]). Stick to one serialization approach per class.
Laravel Request/Response Interference
JMS annotations on request DTOs may conflict with Laravel’s Illuminate\Http\Request. Use separate DTOs for internal messaging vs. HTTP:
// HTTP Request DTO (no JMS)
class StoreUserRequest { ... }
// Internal Command DTO (with JMS)
#[Command]
class StoreUserCommand { ... }
Metadata Caching Clear the JMS metadata cache when annotations change:
php artisan cache:clear
Ecotone Default Converter Override Ensure the JMS converter is registered after Ecotone’s default converter to avoid precedence issues:
$converter->addConverter(new JmsMediaTypeConverter(), 100); // High priority
Serialization Errors Enable JMS Serializer debug mode:
$serializer->setDebug(true);
Check logs for missing metadata or type errors.
Missing Metadata If annotations are ignored, ensure:
composer dump-autoload).Performance Bottlenecks Profile serialization with Xdebug or Blackfire. Optimize with:
@JMS\Type hints for complex objects.Leverage Existing Annotations Reuse JMS annotations from existing DTOs to avoid duplication:
// Existing API DTO (with JMS)
class UserDto { ... }
// Reuse for Ecotone commands
#[Command]
class UpdateUserCommand
{
public UserDto $data;
}
Partial Serialization
Use @JMS\Groups to expose different views of the same data:
// API response (public fields only)
$serializer->serialize($dto, 'json', ['groups' => ['public']]);
// Internal event (all fields)
$serializer->serialize($dto, 'json', ['groups' => ['internal']]);
Custom Naming Strategies Override field names globally:
$serializer->setPropertyNamingStrategy(new SnakeCaseNamingStrategy());
Laravel Validation Combine JMS annotations with Laravel validation:
#[JMS\SerializedName("user_email")]
#[Rule(['required', 'email'])]
public string $email;
Ecotone Lite For non-Laravel projects, use Ecotone Lite with JMS:
$bus = new Bus();
$bus->setMediaTypeConverter(new MediaTypeConverter([
new JmsMediaTypeConverter()
]));
XML Support Enable XML serialization if needed:
$serializer->setFormat('xml');
Type Safety
Use @JMS\Type to enforce strict typing:
#[JMS\Type("string")]
public string $name;
**Exclude
How can I help you explore Laravel packages today?