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

Self Aware Normalizers Laravel Package

digital-craftsman/self-aware-normalizers

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Installation:

    composer require digital-craftsman/self-aware-normalizers
    

    Ensure your project uses Symfony 6.4+ or Laravel 10+ (via Symfony components).

  2. First Use Case: Create a self-aware value object (e.g., Email):

    use DigitalCraftsman\SelfAwareNormalizers\NormalizableInterface;
    use DigitalCraftsman\SelfAwareNormalizers\DenormalizableInterface;
    
    class Email implements NormalizableInterface, DenormalizableInterface
    {
        private string $value;
    
        public function __construct(string $value) { $this->value = $value; }
    
        // Normalization: Convert to scalar (e.g., for API responses)
        public function normalize(): string { return $this->value; }
    
        // Denormalization: Reconstruct from scalar (e.g., from API input)
        public static function denormalize(mixed $value): self
        {
            return new self((string)$value);
        }
    }
    
  3. Integration with Laravel: Use the normalizer in API resources or serializers (e.g., Spatie’s Laravel Data):

    use DigitalCraftsman\SelfAwareNormalizers\Normalizer;
    
    $normalizer = new Normalizer();
    $normalized = $normalizer->normalize(new Email('test@example.com')); // "test@example.com"
    

Implementation Patterns

Core Workflows

  1. Value Object Normalization:

    • Implement NormalizableInterface for objects that need to serialize to scalars (e.g., string, int).
    • Useful for API responses, caching, or database storage.
    • Example:
      class Money implements NormalizableInterface
      {
          public function normalize(): array { return ['amount' => $this->amount, 'currency' => $this->currency]; }
      }
      
  2. Denormalization for Input Handling:

    • Implement DenormalizableInterface to reconstruct objects from raw input (e.g., API requests, form data).
    • Example:
      class UserId implements DenormalizableInterface
      {
          public static function denormalize(mixed $value): self
          {
              return new self(Uuid::fromString((string)$value));
          }
      }
      
  3. Composite Normalization:

    • Use Normalizer to handle nested objects (e.g., DTOs with embedded value objects):
      $userDto = new UserDto($user);
      $normalizer->normalize($userDto); // Recursively normalizes all NormalizableInterface objects
      
  4. Laravel-Specific Patterns:

    • Form Requests: Denormalize input in prepareForValidation():
      public function prepareForValidation()
      {
          $this->merge([
              'user_id' => UserId::denormalize($this->user_id),
          ]);
      }
      
    • API Resources: Override toArray() to leverage normalization:
      public function toArray($request)
      {
          return [
              'email' => $this->resource->email->normalize(),
          ];
      }
      
  5. Custom Normalizers:

    • Extend Normalizer to add logic (e.g., skip normalization for certain fields):
      class CustomNormalizer extends Normalizer
      {
          protected function shouldNormalize($object): bool
          {
              return !in_array(get_class($object), [$this->skipClass]);
          }
      }
      

Gotchas and Tips

Pitfalls

  1. Circular References:

    • The Normalizer throws CircularReferenceException if objects reference each other. Handle this by:
      • Using ignoreMissing() or ignoreCircularReferences() in the normalizer.
      • Breaking cycles manually (e.g., exclude recursive properties).
  2. Type Safety in Denormalization:

    • Always validate input in denormalize() to avoid runtime errors:
      public static function denormalize(mixed $value): self
      {
          if (!is_string($value) || !filter_var($value, FILTER_VALIDATE_EMAIL)) {
              throw new \InvalidArgumentException('Invalid email');
          }
          return new self($value);
      }
      
  3. Performance with Deep Objects:

    • Normalizing large nested objects can be slow. Optimize by:
      • Caching normalized results (e.g., with Symfony\Component\Cache).
      • Using Normalizer::normalizeToCache() for repeated operations.
  4. Laravel Service Container Conflicts:

    • If using Symfony’s NormalizerInterface, bind the package’s Normalizer explicitly:
      $this->app->bind(
          \Symfony\Component\Serializer\Normalizer\NormalizerInterface::class,
          \DigitalCraftsman\SelfAwareNormalizers\Normalizer::class
      );
      

Debugging Tips

  1. Enable Verbose Logging:

    • Set the DEBUG environment variable to log normalization/denormalization steps:
      putenv('DEBUG=1');
      
  2. Inspect Normalized Output:

    • Use var_dump() or dd() to verify scalar output matches expectations:
      $normalized = $normalizer->normalize($object);
      dd($normalized); // Debug the structure
      
  3. Denormalization Edge Cases:

    • Test with null, empty strings, or invalid types:
      try {
          Email::denormalize(null); // Should throw or handle gracefully
      } catch (\Exception $e) {
          // Handle or log
      }
      

Extension Points

  1. Custom Interfaces:

    • Extend NormalizableInterface or DenormalizableInterface for domain-specific needs:
      interface JsonNormalizableInterface extends NormalizableInterface
      {
          public function jsonSerialize(): array;
      }
      
  2. Normalizer Decorators:

    • Decorate the Normalizer to add pre/post-processing:
      class LoggingNormalizer implements NormalizerInterface
      {
          private Normalizer $decorated;
      
          public function normalize($object, string $format = null, array $context = []): mixed
          {
              \Log::debug("Normalizing: " . get_class($object));
              return $this->decorated->normalize($object, $format, $context);
          }
      }
      
  3. Integration with Laravel Packages:

    • Spatie Laravel Data: Use normalizers in Data classes:
      use DigitalCraftsman\SelfAwareNormalizers\NormalizableInterface;
      
      class UserData extends Data implements NormalizableInterface
      {
          public function normalize(): array
          {
              return [
                  'email' => $this->email->normalize(),
              ];
          }
      }
      
    • API Platform: Annotate entities with custom normalizers:
      # config/packages/api_platform.yaml
      api_platform:
          formats:
              jsonld:
                  normalization_context:
                      groups: ['api']
                      normalizers: [DigitalCraftsman\SelfAwareNormalizers\Normalizer]
      
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.
hamzi/corewatch
minionfactory/raw-hydrator
hexters/coinpayment
rjcodes/rjcms
act-training/laravel-permissions-manager
alimarchal/laravel-chart-of-accounts
babenkoivan/elastic-scout-driver
mkwebdesign/filament-watchdog-v5
renatomarinho/laravel-page-speed
zedmagdy/filament-business-hours
renatovdemoura/blade-elements-ui
devgeek/beacon-admin
benjamin-rqt/data-watcher-bundle
atriumphp/atrium
sandermuller/package-boost-laravel
sandermuller/boost-skills
redaxo/core
yusufgenc/filament-api-forge
l3aro/rating-star-for-filament
leek/filament-subtenant-scope