coosos/jms-serializer-bidirectional-relation
Adds JMS Serializer subscribers that embed a _mapping_bidirectional_relation in serialized data so bidirectional associations can be restored on deserialize. Supports Symfony or standalone setup via event subscribers, with annotations to enable mapping on root models and exclude fields.
Installation:
composer require coosos/jms-serializer-bidirectional-relation
For Symfony, add the subscribers to services.yaml as shown in the README.
First Use Case:
@SerializerBidirectionalRelation to your root entity class (e.g., User).User with posts and author)._mapping_bidirectional_relation key into the serialized output.use Coosos\BidirectionalRelation\Annotations\SerializerBidirectionalRelation;
/**
User->posts and Post->author) are restored correctly during deserialization.Annotate Root Entities:
Apply @SerializerBidirectionalRelation to entities that act as root objects in serialization (e.g., DTOs or API responses).
Example:
/**
* @SerializerBidirectionalRelation
*/
class UserResponse {
/** @var Post[] */
public $posts;
public $author;
}
Exclude Fields (Optional):
Use @ExcludeFromMapping on fields that shouldn’t participate in bidirectional mapping (e.g., non-relational fields).
use Coosos\BidirectionalRelation\Annotations\ExcludeFromMapping;
class Post {
/** @ExcludeFromMapping */
public $publishedAt;
}
Serialization: Serialize objects as usual. The package injects metadata for bidirectional relations:
$serializer->serialize($userResponse, 'json');
// Output includes `_mapping_bidirectional_relation` for relation tracking.
Deserialization: The package automatically restores relations during deserialization:
$deserialized = $serializer->deserialize($json, UserResponse::class, 'json');
// Relations like $deserialized->posts[0]->author are now correctly set.
Symfony Forms:
Use this package with Symfony’s AbstractType to ensure form submissions restore bidirectional relations.
Example:
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder->add('posts', CollectionType::class, [
'entry_type' => PostType::class,
'allow_add' => true,
'by_reference' => false, // Critical for bidirectional mapping
]);
}
API Platform:
Combine with @ApiResource to handle nested relations in GraphQL or REST APIs. Ensure the root entity has @SerializerBidirectionalRelation.
Custom Metadata:
Extend the _mapping_bidirectional_relation structure by overriding the subscribers (see Gotchas).
Root Annotation Requirement:
@SerializerBidirectionalRelation on the root entity causes the package to silently skip bidirectional mapping.Circular References:
User->posts->comments->author->posts) may cause infinite loops during deserialization.@ExcludeFromMapping on problematic fields or limit relation depth in serialization groups.Symfony Dependency Injection:
services.yaml. Missing tags = no bidirectional mapping.jms_serializer.event_subscriber tag is present for both subscribers.Overwriting Metadata:
_mapping_bidirectional_relation key, breaking the package.postSerialize/postDeserialize events to extend functionality instead.Check Serialized Output:
Inspect the JSON/XML output for the _mapping_bidirectional_relation key. If missing, the root entity lacks the annotation or subscribers aren’t loaded.
php bin/console debug:container | grep BidirectionalRelation
Event Dispatcher Logs: Enable JMS Serializer’s event dispatcher logs to trace subscriber execution:
# config/packages/jms_serializer.yaml
jms_serializer:
handlers:
event_dispatcher:
logging: true
Deserialization Validation:
If relations aren’t restored, compare the serialized _mapping_bidirectional_relation with the deserialized object’s state. Mismatches indicate subscriber failures.
Custom Mapping Logic:
Extend MapSerializerSubscriber or MapDeserializerSubscriber to add custom relation logic. Override methods like:
public function onPostSerialize(PostSerializeEvent $event) { ... }
Dynamic Relation Handling:
Use the onPostDeserialize event to dynamically resolve relations based on external data (e.g., caching layers):
$event->getObject()->setAuthor($this->resolveAuthorFromCache($event->getData()));
Performance Optimization:
For large datasets, batch relation restoration by overriding MapDeserializerSubscriber::onPostDeserialize to process relations in chunks.
$this->getMockBuilder(MapDeserializerSubscriber::class)
->disableOriginalConstructor()
->onlyMethods(['onPostDeserialize'])
->getMock();
How can I help you explore Laravel packages today?