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

Dto Laravel Package

codememory/dto

Auto-hydrate PHP/Symfony DTOs from request/array data using rules and decorators. Supports name conversion (e.g., snake_case), enum casting via attributes, and event hooks during processing. Build a manager with caching and reflection for fast mapping.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup in Laravel

  1. Installation

    composer require codememory/dto
    
  2. Basic Manager Initialization Create a service provider (e.g., DtoServiceProvider) to bootstrap the manager:

    // app/Providers/DtoServiceProvider.php
    namespace App\Providers;
    
    use Illuminate\Support\ServiceProvider;
    use Codememory\Dto\DataTransferObjectManager;
    use Codememory\Dto\PropertyGrouper;
    use Codememory\Dto\Factory\PropertyExecutionContextFactory;
    use Codememory\Dto\NameConverter\SnakeCaseNameConverter;
    use Codememory\Dto\Processors\ClassDecoratorProcessor;
    use Codememory\Dto\Registrars\ClassDecoratorRegistrar;
    use Codememory\Dto\Processors\PropertyDecoratorProcessor;
    use Codememory\Dto\Registrars\DecoratorTypeRegistrar;
    use Codememory\Dto\Registrars\PropertyDecoratorRegistrar;
    use Codememory\Dto\Factory\ClassExecutionContextFactory;
    use Codememory\Dto\Factory\PropertyWrapperFactory;
    use Symfony\Component\Cache\Adapter\FilesystemAdapter;
    use Symfony\Component\EventDispatcher\EventDispatcher;
    use Codememory\Reflection\ReflectorManager;
    
    class DtoServiceProvider extends ServiceProvider
    {
        public function register()
        {
            $cache = new FilesystemAdapter('codememory', [
                'directory' => storage_path('framework/cache/codememory'),
            ]);
    
            $reflectorManager = new ReflectorManager($cache);
            $eventDispatcher = new EventDispatcher();
    
            $this->app->singleton(DataTransferObjectManager::class, function () use ($reflectorManager, $eventDispatcher) {
                return new DataTransferObjectManager(
                    $reflectorManager,
                    new PropertyGrouper(
                        new PropertyExecutionContextFactory(
                            new SnakeCaseNameConverter()
                        )
                    ),
                    new ClassDecoratorProcessor(
                        new ClassDecoratorRegistrar(),
                        $eventDispatcher
                    ),
                    new PropertyDecoratorProcessor(
                        new DecoratorTypeRegistrar(),
                        new PropertyDecoratorRegistrar(),
                        $eventDispatcher
                    ),
                    new ClassExecutionContextFactory(
                        new PropertyWrapperFactory()
                    )
                );
            });
        }
    }
    

    Register the provider in config/app.php under providers.

  3. First Use Case: Hydrating a DTO Define a DTO class with decorators:

    // app/Dtos/UserDto.php
    namespace App\Dtos;
    
    use Codememory\Dto\Decorators\Property;
    
    class UserDto
    {
        public function __construct(
            public string $name,
            #[Property\ToEnum]
            public UserRole $role,
            #[Property\ToEnum(value: true)]
            public UserStatus $status
        ) {}
    }
    
    enum UserRole { ADMIN, EDITOR, VIEWER }
    enum UserStatus: string { ACTIVE = 'active', INACTIVE = 'inactive' }
    

    Use the manager in a controller:

    // app/Http/Controllers/UserController.php
    use App\Dtos\UserDto;
    use Codememory\Dto\DataTransferObjectManager;
    
    class UserController extends Controller
    {
        public function store(Request $request, DataTransferObjectManager $manager)
        {
            $data = $request->validate([
                'name' => 'required|string',
                'role' => 'required|string',
                'status' => 'required|string',
            ]);
    
            $userDto = $manager->hydrate(UserDto::class, $data);
            // Use $userDto...
        }
    }
    

Implementation Patterns

1. DTO Design Patterns

  • Constructor-Based DTOs: Always use constructor injection for properties. Avoid setters; decorators operate on constructor parameters.
  • Snake Case Input Handling: The SnakeCaseNameConverter automatically maps snake_case input to camelCase properties (e.g., foo_bar$fooBar).
  • Nested DTOs: Support nested objects by hydrating them recursively:
    class AddressDto {}
    class UserDto {
        public function __construct(public AddressDto $address) {}
    }
    $userDto = $manager->hydrate(UserDto::class, [
        'address' => ['street' => '123 Main St']
    ]);
    

2. Decorator Workflows

  • Property Decorators: Use attributes like @Property\ToEnum, @Property\SymfonyValidation, etc., to transform or validate input.
    #[Property\ToEnum]
    public UserRole $role; // Converts 'ADMIN' string to UserRole::ADMIN
    
  • Class Decorators: Apply logic at the class level (e.g., global validation, logging) via @Decorator\Class\* attributes.
  • Decorator Priorities: Decorators with lower priority numbers execute first. Use events to validate data after specific decorator types.

3. Integration with Laravel

  • Request Validation: Combine with Laravel’s validation for early rejection of invalid data:
    $validated = $request->validate([
        'name' => 'required|string|max:255',
        'role' => 'required|in:ADMIN,EDITOR,VIEWER',
    ]);
    $dto = $manager->hydrate(UserDto::class, $validated);
    
  • Form Requests: Extend FormRequest to hydrate DTOs directly:
    use Codememory\Dto\DataTransferObjectManager;
    
    class StoreUserRequest extends FormRequest
    {
        public function authorize(): bool { return true; }
    
        public function rules(): array { return [...]; }
    
        public function hydrate(UserDto $dto, DataTransferObjectManager $manager): UserDto
        {
            return $manager->hydrate(UserDto::class, $this->validated());
        }
    }
    
  • Service Layer: Use DTOs in services to decouple business logic from request handling:
    class UserService {
        public function createUser(UserDto $dto) {
            // Business logic using $dto
        }
    }
    

4. Event-Driven Extensions

  • Validation Events: Attach listeners to validate data after decorator processing:
    $eventDispatcher->addListener(
        AfterProcessedTypeDecoratorsEvent::class,
        fn($event) => $this->validateSymfonyConstraints($event)
    );
    
  • Custom Decorators: Create reusable decorators for common transformations (e.g., @Property\Trim, @Property\DefaultValue).

5. Testing

  • Unit Tests: Mock DataTransferObjectManager to test DTO hydration:
    $manager = $this->createMock(DataTransferObjectManager::class);
    $manager->method('hydrate')->willReturn(new UserDto(...));
    
  • Decorator Tests: Isolate decorator logic by testing handlers directly:
    $handler = new SymfonyValidationHandler();
    $handler->process($decorator, $context); // Assert metadata
    

Gotchas and Tips

Pitfalls

  1. No Optional Parameters

    • Issue: The library throws errors if input data is missing required fields.
    • Fix: Validate input with Laravel’s validation or use @Property\DefaultValue decorators:
      #[Property\DefaultValue('GUEST')]
      public UserRole $role; // Defaults to UserRole::GUEST if missing
      
  2. Case Sensitivity in Enums

    • Issue: @Property\ToEnum is case-sensitive for string-backed enums.
    • Fix: Normalize input before hydration or use value: true for string enums:
      #[Property\ToEnum(value: true)]
      public UserStatus $status; // Matches 'active'/'inactive' strings
      
  3. Caching Overhead

    • Issue: ReflectorManager caches reflection data, which may cause stale metadata if classes change.
    • Fix: Clear the cache manually or configure a short TTL:
      $cache = new FilesystemAdapter('codememory', [
          'directory' => storage_path('cache'),
          'default_lifetime' => 3600, // 1 hour
      ]);
      
  4. Event Dispatcher Order

    • Issue: Events fire in registration order. Critical listeners (e.g., validation) must be added early.
    • Fix: Register listeners in a service provider’s boot() method to ensure order.
  5. Nested Object Hydration

    • Issue: Nested DTOs require their own decorators to be processed.
    • Fix: Ensure nested classes are hydrated with compatible decorators or use a recursive hydration strategy.

Debugging Tips

  1. Metadata Inspection

    • Dump class metadata to debug decorator processing:
      $metadata = $manager->getReflectorManager()
          ->getClassMetadata(UserDto::class);
      dd($metadata);
      
  2. **Event Debug

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
terminal42/code-quality-tools
codifyo/ts-generator-bundle
testo/fiber
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