digital-craftsman/self-aware-normalizers
Installation:
composer require digital-craftsman/self-aware-normalizers
Ensure your project uses Symfony 6.4+ or Laravel 10+ (via Symfony components).
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);
}
}
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"
Value Object Normalization:
NormalizableInterface for objects that need to serialize to scalars (e.g., string, int).class Money implements NormalizableInterface
{
public function normalize(): array { return ['amount' => $this->amount, 'currency' => $this->currency]; }
}
Denormalization for Input Handling:
DenormalizableInterface to reconstruct objects from raw input (e.g., API requests, form data).class UserId implements DenormalizableInterface
{
public static function denormalize(mixed $value): self
{
return new self(Uuid::fromString((string)$value));
}
}
Composite Normalization:
Normalizer to handle nested objects (e.g., DTOs with embedded value objects):
$userDto = new UserDto($user);
$normalizer->normalize($userDto); // Recursively normalizes all NormalizableInterface objects
Laravel-Specific Patterns:
prepareForValidation():
public function prepareForValidation()
{
$this->merge([
'user_id' => UserId::denormalize($this->user_id),
]);
}
toArray() to leverage normalization:
public function toArray($request)
{
return [
'email' => $this->resource->email->normalize(),
];
}
Custom Normalizers:
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]);
}
}
Circular References:
Normalizer throws CircularReferenceException if objects reference each other. Handle this by:
ignoreMissing() or ignoreCircularReferences() in the normalizer.Type Safety in Denormalization:
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);
}
Performance with Deep Objects:
Symfony\Component\Cache).Normalizer::normalizeToCache() for repeated operations.Laravel Service Container Conflicts:
NormalizerInterface, bind the package’s Normalizer explicitly:
$this->app->bind(
\Symfony\Component\Serializer\Normalizer\NormalizerInterface::class,
\DigitalCraftsman\SelfAwareNormalizers\Normalizer::class
);
Enable Verbose Logging:
DEBUG environment variable to log normalization/denormalization steps:
putenv('DEBUG=1');
Inspect Normalized Output:
var_dump() or dd() to verify scalar output matches expectations:
$normalized = $normalizer->normalize($object);
dd($normalized); // Debug the structure
Denormalization Edge Cases:
null, empty strings, or invalid types:
try {
Email::denormalize(null); // Should throw or handle gracefully
} catch (\Exception $e) {
// Handle or log
}
Custom Interfaces:
NormalizableInterface or DenormalizableInterface for domain-specific needs:
interface JsonNormalizableInterface extends NormalizableInterface
{
public function jsonSerialize(): array;
}
Normalizer Decorators:
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);
}
}
Integration with Laravel Packages:
Data classes:
use DigitalCraftsman\SelfAwareNormalizers\NormalizableInterface;
class UserData extends Data implements NormalizableInterface
{
public function normalize(): array
{
return [
'email' => $this->email->normalize(),
];
}
}
# config/packages/api_platform.yaml
api_platform:
formats:
jsonld:
normalization_context:
groups: ['api']
normalizers: [DigitalCraftsman\SelfAwareNormalizers\Normalizer]
How can I help you explore Laravel packages today?