alexanevsky/output-normalizer-bundle
Install the Package Add the package to your Laravel project via Composer:
composer require alexanevsky/output-normalizer-bundle
Register the bundle in config/app.php under providers (if not auto-discovered).
Define an Output Class
Create a class implementing OutputInterface to define the output structure. Example:
use Alexanevsky\OutputNormalizerBundle\Output\OutputInterface;
class UserOutput implements OutputInterface {
public int $id;
public string $name;
}
Inject the Normalizer
Add the OutputNormalizer to your controller/service constructor:
use Alexanevsky\OutputNormalizerBundle\OutputNormalizer;
public function __construct(private OutputNormalizer $outputNormalizer) {}
Normalize an Entity
Call normalize() with your entity and output class:
$user = new User();
$output = $this->outputNormalizer->normalize($user, UserOutput::class);
// Returns: ['id' => 1, 'name' => 'John Doe']
Use the normalizer to transform Eloquent models into API responses with only the required fields, avoiding bloated JSON:
public function show(User $user) {
$output = $this->outputNormalizer->normalize($user, UserOutput::class);
return response()->json($output);
}
Define Output Classes
Create OutputInterface classes for each Eloquent model (e.g., UserOutput, PostOutput). Use public properties or getters/setters to control output.
Leverage Getters for Transformation Use getters in output classes to modify values during normalization:
class UserOutput implements OutputInterface {
public string $email;
public function getEmail(): string {
return strtolower($this->email);
}
}
Handle Relationships
Normalize nested objects by defining output classes for related models (e.g., PostOutput for Post model). Use EntityToId for identifiers:
class UserOutput implements OutputInterface {
#[EntityToId]
public User $author;
}
Global Normalization Rules
Implement ObjectNormalizerInterface for reusable normalization logic (e.g., formatting dates or hashing sensitive fields):
class DateNormalizer implements ObjectNormalizerInterface {
public function supports(object $object): bool {
return $object instanceof \DateTime;
}
public function normalize(object $object): string {
return $object->format('Y-m-d');
}
}
Modify Output Dynamically
Use OutputModifierInterface to filter or transform data post-normalization (e.g., removing sensitive fields):
class UserOutputModifier implements OutputModifierInterface {
public function supports(object $output, object $source): bool {
return $output instanceof UserOutput;
}
public function modify(OutputInterface $output, object $source): void {
unset($output->password);
}
}
ApiResource with this package for fine-grained control over serialization.OutputNormalizer in unit tests to isolate business logic from serialization concerns.Getter/Setter Precedence If a property has both a getter and a public field, the getter’s value is used. Override properties with getters to control output:
// Avoids exposing private $password directly
public function getPassword(): string {
return '*****';
}
Circular References
Normalizing objects with circular references (e.g., User ↔ Post) may cause infinite loops. Use EntityToId or break cycles manually:
class UserOutput implements OutputInterface {
#[EntityToId]
public User $author; // Avoids deep nesting
}
Case Sensitivity
Property names in the output are converted to snake_case. Override with custom getters if needed:
public function getFullName(): string {
return $this->firstName . ' ' . $this->lastName;
}
Modifier Order
OutputModifierInterface methods are called in registration order. Use dependency injection to enforce order:
// Register modifiers in a specific sequence in your service provider
$container->register(UserOutputModifier::class, [], Tags::MODIFIER);
Inspect Normalized Data
Use var_dump() or Laravel’s dd() to debug intermediate output:
$output = $this->outputNormalizer->normalize($user, UserOutput::class);
dd($output);
Check Modifier Support
Ensure supports() in modifiers returns true for the correct output/source pairs. Log conditions for debugging:
public function supports(object $output, object $source): bool {
if (!$output instanceof UserOutput) {
logger()->debug('Modifier skipped: Output not UserOutput');
}
return $output instanceof UserOutput;
}
Custom Normalizers
Extend ObjectNormalizerInterface to handle domain-specific types (e.g., Money, UUID).
Dynamic Output Classes Generate output classes at runtime using reflection or factories to avoid manual definitions.
Caching Normalized Output Cache normalized results for performance-critical APIs:
$cacheKey = 'user.' . $user->id;
return Cache::remember($cacheKey, now()->addHour(), function () use ($user) {
return $this->outputNormalizer->normalize($user, UserOutput::class);
});
Validation Integration Combine with Laravel’s validation to ensure output matches API contracts:
$output = $this->outputNormalizer->normalize($user, UserOutput::class);
$validator = Validator::make($output, [
'email' => 'required|email',
'roles' => 'array',
]);
Symfony Integration
Use Symfony’s Serializer alongside this package for hybrid normalization (e.g., JSON:API compliance).
How can I help you explore Laravel packages today?