jms/serializer
Powerful PHP serializer/deserializer for complex object graphs, with JSON/XML support, circular reference handling, rich exclusion strategies, versioning, and built-in type handlers. Configurable via annotations, YAML, or XML; integrates with Doctrine ORM.
## Getting Started
### Minimal Setup in Laravel
1. **Installation**:
```bash
composer require jms/serializer-bundle
For Laravel, manually register the bundle in config/app.php under extra.bundles:
JMS\SerializerBundle\JMSSerializerBundle::class,
Basic Serialization:
use JMS\Serializer\SerializerBuilder;
use JMS\Serializer\SerializerInterface;
$builder = SerializerBuilder::create();
$serializer = $builder->build();
$json = $serializer->serialize($data, 'json');
First Use Case: Convert a Laravel Eloquent model to JSON with custom formatting:
$user = User::find(1);
$serializer = app('jms_serializer');
$json = $serializer->serialize($user, 'json', [
'groups' => ['user:read']
]);
config/packages/jms_serializer.yaml:
jms_serializer:
metadata:
directories:
- { path: '%kernel.project_dir%/src/Metadata', namespace_prefix: 'App\Metadata' }
config/packages/jms_serializer.yaml:
jms_serializer:
handlers:
custom_date_handler:
type: App\Serializer\Handler\CustomDateHandler
$serializer->serialize($resource, 'json', [
'groups' => ['api', 'public']
]);
$context = new SerializationContext();
$context->setGroups(['api']);
$context->setAttribute('version', 'v1');
$serializer->serialize($data, 'json', $context);
$data = $serializer->deserialize($json, 'App\Entity\User', 'json');
// In metadata (e.g., via annotation)
/** @JMS\Type("App\Entity\Address") */
private $address;
$serializer->serialize($user->roles, 'json', [
'groups' => ['role:read']
]);
use JMS\Serializer\Handler\SubscribingHandlerInterface;
class CustomHandler implements SubscribingHandlerInterface {
public static function getSubscribingMethods() {
return [
[
'direction' => SerializationDirection::DESERIALIZATION,
'format' => 'json',
'type' => 'App\Entity\CustomType',
'method' => 'deserializeCustomType'
]
];
}
public function deserializeCustomType($format, $data, array $config) {
return new CustomType($data['value']);
}
}
# config/packages/jms_serializer.yaml
jms_serializer:
handlers:
custom_handler:
type: App\Serializer\Handler\CustomHandler
$context = new SerializationContext();
$context->setAttribute('version', 'v2');
$serializer->serialize($data, 'json', $context);
# src/Metadata/Version2/User.yml
App\Entity\User:
exclusion_policy: ALL
properties:
id:
expose: true
name:
expose: true
groups: [v2]
$context = new SerializationContext();
$context->setMaxDepth(5); // Limit recursion depth
$xml = $serializer->serialize($data, 'xml', [
'xml_root_node_name' => 'root',
'xml_format_output' => true
]);
Service Provider Binding:
Bind the serializer to the container in AppServiceProvider:
public function register() {
$this->app->singleton('jms_serializer', function () {
return SerializerBuilder::create()
->configureHandlers(function (HandlerRegistry $registry) {
$registry->registerSubscribingHandler(new CustomHandler());
})
->build();
});
}
Middleware for API Responses:
namespace App\Http\Middleware;
use Closure;
use JMS\Serializer\SerializerInterface;
class SerializeResponseMiddleware {
protected $serializer;
public function __construct(SerializerInterface $serializer) {
$this->serializer = $serializer;
}
public function handle($request, Closure $next) {
$response = $next($request);
if ($response->isJson()) {
$data = json_decode($response->getContent(), true);
$response->setContent($this->serializer->serialize($data, 'json'));
}
return $response;
}
}
Form Request Validation: Use the serializer to deserialize and validate incoming requests:
use JMS\Serializer\Exception\RuntimeException;
public function validate() {
try {
$data = $this->serializer->deserialize(
$this->request->getContent(),
'App\Entity\User',
'json'
);
$this->merge($data);
} catch (RuntimeException $e) {
$this->fail('Invalid data format.');
}
}
Metadata Caching:
Enable caching in config/packages/jms_serializer.yaml:
jms_serializer:
metadata:
cache: file
file_cache:
dir: '%kernel.cache_dir%/serializer'
Group Preloading: Preload groups in the constructor of your service:
public function __construct(SerializerInterface $serializer) {
$this->serializer = $serializer;
$this->serializer->getMetadataFactory()->getMetadataFor('App\Entity\User');
}
Circular Reference Limits:
SerializationContext:
$context = new SerializationContext();
$context->setMaxDepth(10);
Annotation vs. YAML/XML Metadata:
# config/packages/jms_serializer.yaml
jms_serializer:
metadata:
directories:
- { path: '%kernel.project_dir%/config/serializer', namespace_prefix: 'App\Metadata' }
cache: file
Doctrine Proxy Objects:
DoctrineObjectConstructor and exclude lazy properties:
$context = new SerializationContext();
$context->setAttribute('exclude_null', true);
$context->setAttribute('exclude_empty', true);
Custom DateTime Handling:
DateTimeInterface implementations may not serialize/deserialize correctly.$builder->configureHandlers(function (HandlerRegistry $registry) {
$registry->registerSubscribingHandler(new CustomDateTimeHandler());
});
Group Inheritance:
# src/Metadata/Child.yml
App\Entity\Child:
inheritance:
type: JOINED
groups: [parent_groups, child_groups]
PHP 8 Attributes:
php8_attributes is enabled in config/packages/jms_serializer.yaml:
jms_serializer:
metadata:
enabled_attributes: true
Type Mismatches:
DateTime vs. Carbon).How can I help you explore Laravel packages today?