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

Simple Hydrator Laravel Package

aljerom/simple-hydrator

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Steps

  1. Installation

    composer require aljerom/simple-hydrator
    
    • Verify PHP version (php -v) is 8.3+ (required for named arguments and enums).
  2. First Hydration

    use Aljerom\SimpleHydrator\Hydrator;
    
    $hydrator = new Hydrator();
    $arrayData = [
        'first_name' => 'John',
        'last_name'  => 'Doe',
        'email'      => '[email protected]'
    ];
    
    $user = $hydrator->hydrate($arrayData, User::class);
    // $user->firstName, $user->lastName, $user->email are now populated
    
  3. Where to Look First

    • Source Code: src/Hydrator.php (core logic, ~50 lines).
    • Tests: tests/HydratorTest.php (edge cases like nested arrays, private properties).
    • Laravel Integration: Focus on Request hydration or API Resource transformation.

Implementation Patterns

1. Laravel Request Hydration

Use Case: Convert validated request data into domain objects.

use Aljerom\SimpleHydrator\Hydrator;
use Illuminate\Http\Request;

public function store(Request $request, Hydrator $hydrator) {
    $validated = $request->validate([
        'first_name' => 'required|string',
        'last_name'  => 'required|string',
    ]);

    $user = $hydrator->hydrate($validated, User::class);
    // $user->firstName, $user->lastName are auto-populated
}

2. API Response Transformation

Use Case: Convert snake_case API responses to camelCase objects.

public function getUsers() {
    $apiResponse = Http::get('https://api.example.com/users')->json();
    $users = array_map(
        fn($user) => (new Hydrator())->hydrate($user, UserDto::class),
        $apiResponse['data']
    );
    return response()->json($users);
}

3. Service Layer Integration

Use Case: Replace manual new Entity($array) in services.

public function createUser(array $data) {
    $user = (new Hydrator())->hydrate($data, User::class);
    $user->save(); // Eloquent or custom persistence
}

4. Nested Object Hydration

Use Case: Hydrate arrays containing nested arrays/objects.

$data = [
    'user' => [
        'name' => 'John',
        'address' => ['street' => '123 Main']
    ]
];

$user = (new Hydrator())->hydrate($data, UserWithAddress::class);
// $user->user->name, $user->user->address->street

5. Laravel Service Provider Binding

Use Case: Register hydrator as a singleton for dependency injection.

// app/Providers/AppServiceProvider.php
public function register() {
    $this->app->singleton(Hydrator::class, fn() => new Hydrator());
}

Usage:

public function __construct(private Hydrator $hydrator) {}

6. Custom Type Mapping

Use Case: Override default type casting (e.g., strings to dates).

$hydrator = new Hydrator();
$hydrator->setTypeMap([
    'created_at' => DateTime::class,
    'is_active'  => 'bool',
]);

$data = ['created_at' => '2023-01-01'];
$model = $hydrator->hydrate($data, Model::class);
// $model->createdAt is now a DateTime object

7. Collection Hydration

Use Case: Hydrate arrays of data into collections of objects.

use Illuminate\Support\Collection;

$users = collect($apiData['users'])
    ->map(fn($user) => (new Hydrator())->hydrate($user, User::class))
    ->values();

8. Integration with Laravel Validation

Use Case: Hydrate validated data directly into models.

public function update(Request $request, User $user) {
    $validated = $request->validate([
        'first_name' => 'sometimes|string',
        'last_name'  => 'sometimes|string',
    ]);

    (new Hydrator())->hydrate($validated, $user);
    $user->save();
}

9. Dynamic Property Handling

Use Case: Hydrate objects with dynamic properties (e.g., from JSON).

$dynamicData = json_decode($jsonString, true);
$object = (new Hydrator())->hydrate($dynamicData, stdClass::class);

10. Testing with Hydration

Use Case: Simplify unit tests by hydrating mock data.

public function testUserCreation() {
    $data = ['first_name' => 'Test', 'last_name' => 'User'];
    $user = (new Hydrator())->hydrate($data, User::class);

    $this->assertEquals('Test', $user->firstName);
}

Gotchas and Tips

Pitfalls

  1. Private/Protected Properties

    • Issue: Hydrator skips private/protected properties by default (reflection limitation).
    • Fix: Use setAccessibleProperties() to include them:
      $hydrator->setAccessibleProperties(true);
      
  2. Circular References

    • Issue: Nested objects with circular references cause infinite loops.
    • Fix: Limit hydration depth or use a custom hydrator subclass:
      $hydrator = new class extends Hydrator {
          protected int $depth = 0;
          protected const MAX_DEPTH = 5;
      
          public function hydrate(array $data, string $class, ?array $options = null): object {
              if ($this->depth >= self::MAX_DEPTH) {
                  throw new \RuntimeException('Max hydration depth exceeded');
              }
              $this->depth++;
              try {
                  return parent::hydrate($data, $class, $options);
              } finally {
                  $this->depth--;
              }
          }
      };
      
  3. Type Mismatches

    • Issue: Silent type conversion (e.g., "1"1) may cause bugs.
    • Fix: Explicitly set type maps or validate data first:
      $hydrator->setTypeMap(['id' => 'int', 'is_active' => 'bool']);
      
  4. Overwriting Existing Properties

    • Issue: Hydration overwrites existing object properties.
    • Fix: Use merge mode for partial updates:
      $hydrator->hydrate($data, $existingObject, ['mode' => 'merge']);
      
  5. Performance with Large Arrays

    • Issue: Reflection overhead slows down bulk hydration.
    • Fix: Cache hydrated classes or use manual mapping for hot paths:
      $cachedHydrator = fn($data) => (new Hydrator())->hydrate($data, User::class);
      
  6. Non-Standard Naming Conventions

    • Issue: Hardcoded snake_casecamelCase may conflict with existing code.
    • Fix: Subclass and override convertPropertyName():
      class CustomHydrator extends Hydrator {
          protected function convertPropertyName(string $name): string {
              return str_replace('_', '', $name); // PascalCase
          }
      }
      
  7. Laravel Model Binding Conflicts

    • Issue: Hydration may interfere with Laravel’s implicit model binding.
    • Fix: Hydrate before binding or use explicit instantiation:
      // Bad: May conflict with route model binding
      $user = (new Hydrator())->hydrate($data, User::class);
      
      // Good: Explicit new instance
      $user = new User();
      (new Hydrator())->hydrate($data, $user);
      
  8. PHP 8.3+ Features

    • Issue: Uses named arguments/enums; may break on older PHP.
    • Fix: Downgrade to v1.0.0 or use polyfills.

Debugging Tips

  1. Enable Reflection Logging Add this to inspect hydration behavior:
    $hydrator = new class extends Hydrator {
        public function hydrate(array $data, string $class, ?array $
    
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.
phpshko/laravel-livewire-depdrop
larasell-dev/larasell
calliostro/spotify-bundle
calmfox/watch-sylius
damienfern/grpc-symfony-bundle
atoolo/index-bundle
atoolo/genai-bundle
coprotoai/laravel-ticket
davidjln/llm-carbon-bundle
cryonighter/valid-request-bundle
coolms/taxonomy-bundle
coolms/field-bundle
articulate-orm/symfony
aaix/laravel-tall-architect
ephoto/akeneo-connector
emmanuelballery/eb-plantumlbundle
emielburgman/symfony-visitor-beacon
emielburgman/symfony-visit-storage
emielburgman/symfony-security-headers
emielburgman/symfony-log-viewer