Weave Code
Code Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Normalt Laravel Package

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.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup for Laravel

  1. Install the Package:

    composer require bernard/normalt
    

    Ensure symfony/serializer (v4.x or v5.x) is installed (Laravel 8+ includes this by default).

  2. 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);
    
  3. 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).


Implementation Patterns

1. Service Container Integration

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]);
});

2. Normalizing Eloquent Collections

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());

3. Handling Nested Objects

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' => '...']

4. Denormalization Workflow

// 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

5. Custom Normalizers

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),
]);

6. API Resource Integration

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);
    }
}

7. Caching Layer

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);

8. Command Bus/Event Dispatching

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
    }
}

Gotchas and Tips

Pitfalls

  1. Symfony Version Mismatch:

    • Normalt’s latest release (v1.2.0) targets Symfony 4. Laravel 8+ uses Symfony 5+, which may cause:
      • ClassNotFoundException for updated Symfony Serializer classes.
      • Fix: Pin symfony/serializer to v4.x in composer.json or fork Normalt.
      "require": {
          "symfony/serializer": "^4.4"
      }
      
  2. DoctrineNormalizer Limitations:

    • Only normalizes mapped Doctrine entities (not plain PHP objects).
    • Workaround: Combine with GetSetMethodNormalizer in AggregateNormalizer.
    $aggregateNormalizer = new AggregateNormalizer([
        new GetSetMethodNormalizer(),
        app(DoctrineNormalizer::class),
    ]);
    
  3. Denormalization Edge Cases:

    • Fails if the array structure doesn’t match the expected class hierarchy.
    • Tip: Validate input arrays before denormalization:
      if (!is_array($data) || !isset($data['id'])) {
          throw new \InvalidArgumentException('Invalid user data');
      }
      
  4. Circular References:

    • RecursiveReflectionNormalizer may loop infinitely on circular references (e.g., User->posts->user).
    • Fix: Use Symfony’s ObjectNormalizer with enable_max_depth:
      use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
      
      $normalizer = new RecursiveReflectionNormalizer([
          new ObjectNormalizer(null, null, null, [
              'enable_max_depth' => true,
          ]),
      ]);
      
  5. Performance with Reflection:

    • RecursiveReflectionNormalizer uses reflection, which is slow for large object graphs.
    • Tip: Cache normalized results or use GetSetMethodNormalizer for POPOs.
  6. Laravel Eloquent Quirks:

    • Eloquent models extend Illuminate\Database\Eloquent\Model, not Doctrine\ORM\Entity.
    • Workaround: Mock the Doctrine ClassMetadata or use GetSetMethodNormalizer:
      $aggregateNormalizer = new AggregateNormalizer([
          new GetSetMethodNormalizer(),
      ]);
      

Debugging Tips

  1. Check Supported Types:

    $normalizer = app(DoctrineNormalizer::class);
    var_dump($normalizer->supportsNormalization($user)); // bool
    
  2. Inspect Normalized Output:

    $array = $normalizer->normalize($user);
    dd($array); // Debug the structure
    
  3. Enable Symfony Serializer Debug: Add to config/services.php:

    'serializer' => [
        'debug' => env('APP_DEBUG', false),
    ],
    
  4. Log Denormalization Errors:

    try {
        $user = $normalizer->denormalize($data, User::class);
    } catch (\Exception $e) {
        \Log::error("Denormalization failed: " . $e->getMessage());
        throw $e;
    }
    

Extension Points

  1. Custom Metadata: Add metadata to control
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
besmartand-pro/php-quality-config
sentix/ai-chatbot
codifyo/ts-generator-bundle
mintobit/jobqueue
a4sex/maintenance-bundle
a4sex/entity-date-update
a4sex/client-identifier
a4sex/base-utilites
a4sex/key-value-storage
a4sex/micro-status
chilldev/dependency-injection-extra
datinglibre/datinglibre-app-api
biberltd/corebundle
bricre/symfony-bundle-test
biberltd/logbundle
dominium/http-adapter-bundle
dominium/google-analytics
a4sex/auto-clean-entity
christhompsontldr/laravel-inky
spatie/mailcoach-vapor