bernard/normalt
Extra normalizers for Symfony’s Serializer plus an AggregateNormalizer delegator that selects the first supporting normalizer/denormalizer. Focuses on object-to-array normalization and array-to-object denormalization, with options like Doctrine and reflection-based normalizers.
Install the Package:
composer require bernard/normalt
Ensure symfony/serializer (v4.x or v5.x) is installed (Laravel 8+ includes this by default).
Basic Usage with Eloquent:
use Normalt\Normalizer\DoctrineNormalizer;
use Doctrine\ORM\EntityManagerInterface; // Laravel's Eloquent uses Doctrine under the hood
// In a service or controller:
$entityManager = app(EntityManagerInterface::class);
$normalizer = new DoctrineNormalizer($entityManager);
// Normalize an Eloquent model to array
$user = User::find(1);
$array = $normalizer->normalize($user);
// Returns: ['App\Models\User', 1] (class name + primary key)
// Denormalize back to model
$restoredUser = $normalizer->denormalize($array, User::class);
First Use Case:
Replace a manual toArray() method in an Eloquent model:
// Before:
public function toArray()
{
return ['id' => $this->id, 'name' => $this->name];
}
// After:
public function toArray()
{
$normalizer = app(DoctrineNormalizer::class);
return $normalizer->normalize($this);
}
Note: Register DoctrineNormalizer in Laravel’s service container (see Implementation Patterns).
Register the normalizers as Laravel services in config/app.php or a service provider:
// In AppServiceProvider@boot()
$this->app->singleton(DoctrineNormalizer::class, function ($app) {
return new DoctrineNormalizer($app->make(EntityManagerInterface::class));
});
$this->app->singleton(RecursiveReflectionNormalizer::class, function ($app) {
$doctrineNormalizer = $app->make(DoctrineNormalizer::class);
return new RecursiveReflectionNormalizer([$doctrineNormalizer]);
});
use Normalt\Normalizer\AggregateNormalizer;
use Symfony\Component\Serializer\Normalizer\GetSetMethodNormalizer;
$aggregateNormalizer = new AggregateNormalizer([
new GetSetMethodNormalizer(),
app(DoctrineNormalizer::class),
]);
$users = User::all();
$arrays = $aggregateNormalizer->normalize($users->toArray());
Use RecursiveReflectionNormalizer for objects with relationships:
$profileNormalizer = new RecursiveReflectionNormalizer([
app(DoctrineNormalizer::class),
]);
$profile = User::find(1)->profile;
$profileArray = $profileNormalizer->normalize($profile);
// Returns: ['user' => ['App\Models\User', 1], 'bio' => '...']
// Example: Reconstructing a User from API input
$data = ['name' => 'John', 'email' => 'john@example.com'];
$user = app(DoctrineNormalizer::class)->denormalize($data, User::class);
$user->save(); // Persist the new model
Extend Normalt\Normalizer\NormalizerInterface for domain-specific objects:
use Normalt\Normalizer\NormalizerInterface;
class CustomObjectNormalizer implements NormalizerInterface
{
public function normalize($object, string $format = null, array $context = [])
{
if ($object instanceof CustomObject) {
return ['id' => $object->id, 'value' => $object->value];
}
return null; // Not supported
}
public function denormalize($data, string $type, string $format = null, array $context = [])
{
if ($type === CustomObject::class) {
return new CustomObject($data['id'], $data['value']);
}
return null;
}
public function supportsNormalization($data, string $format = null)
{
return $data instanceof CustomObject;
}
public function supportsDenormalization($data, string $type, string $format = null)
{
return $type === CustomObject::class;
}
}
// Register in AggregateNormalizer:
$aggregateNormalizer = new AggregateNormalizer([
new CustomObjectNormalizer(),
app(DoctrineNormalizer::class),
]);
Replace JsonResource’s toArray() with Normalt:
use Illuminate\Http\Resources\Json\JsonResource;
use Normalt\Normalizer\DoctrineNormalizer;
class UserResource extends JsonResource
{
public function toArray($request)
{
$normalizer = app(DoctrineNormalizer::class);
return $normalizer->normalize($this->resource);
}
}
Normalize Eloquent models for Redis caching:
$normalizer = app(DoctrineNormalizer::class);
$cachedData = $normalizer->normalize($user);
Redis::set("user:{$user->id}", json_encode($cachedData), 'EX', 60*60);
Normalize DTOs for event payloads:
use Normalt\Normalizer\AggregateNormalizer;
class UserCreatedHandler
{
public function __construct(private AggregateNormalizer $normalizer) {}
public function handle(UserCreated $event)
{
$payload = $this->normalizer->normalize($event->user);
// Dispatch $payload to a queue or log it
}
}
Symfony Version Mismatch:
ClassNotFoundException for updated Symfony Serializer classes.symfony/serializer to v4.x in composer.json or fork Normalt."require": {
"symfony/serializer": "^4.4"
}
DoctrineNormalizer Limitations:
GetSetMethodNormalizer in AggregateNormalizer.$aggregateNormalizer = new AggregateNormalizer([
new GetSetMethodNormalizer(),
app(DoctrineNormalizer::class),
]);
Denormalization Edge Cases:
if (!is_array($data) || !isset($data['id'])) {
throw new \InvalidArgumentException('Invalid user data');
}
Circular References:
RecursiveReflectionNormalizer may loop infinitely on circular references (e.g., User->posts->user).ObjectNormalizer with enable_max_depth:
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
$normalizer = new RecursiveReflectionNormalizer([
new ObjectNormalizer(null, null, null, [
'enable_max_depth' => true,
]),
]);
Performance with Reflection:
RecursiveReflectionNormalizer uses reflection, which is slow for large object graphs.GetSetMethodNormalizer for POPOs.Laravel Eloquent Quirks:
Illuminate\Database\Eloquent\Model, not Doctrine\ORM\Entity.ClassMetadata or use GetSetMethodNormalizer:
$aggregateNormalizer = new AggregateNormalizer([
new GetSetMethodNormalizer(),
]);
Check Supported Types:
$normalizer = app(DoctrineNormalizer::class);
var_dump($normalizer->supportsNormalization($user)); // bool
Inspect Normalized Output:
$array = $normalizer->normalize($user);
dd($array); // Debug the structure
Enable Symfony Serializer Debug:
Add to config/services.php:
'serializer' => [
'debug' => env('APP_DEBUG', false),
],
Log Denormalization Errors:
try {
$user = $normalizer->denormalize($data, User::class);
} catch (\Exception $e) {
\Log::error("Denormalization failed: " . $e->getMessage());
throw $e;
}
How can I help you explore Laravel packages today?