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

Valinor Laravel Package

cuyz/valinor

Valinor maps raw inputs (JSON/arrays) into validated, strongly typed PHP objects. Supports advanced PHPStan/Psalm types (shaped arrays, generics, ranges), produces precise human-readable errors, and can normalize data back to formats like JSON or CSV.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Steps

  1. Installation:

    composer require cuyz/valinor
    
  2. Define a DTO/Entity: Use PHP 8+ features like readonly properties and type hints (including shaped arrays, generics, and ranges).

    final readonly class User {
        public function __construct(
            public string $name,
            public array $roles,
        ) {}
    }
    
  3. Map Input to Object:

    $mapper = (new MapperBuilder())->mapper();
    $user = $mapper->map(User::class, Source::json('{"name": "John", "roles": ["admin"]}'));
    
  4. Handle Errors:

    try {
        $user = $mapper->map(User::class, Source::json('{"name": 123}'));
    } catch (MappingError $error) {
        // Handle validation errors
    }
    

First Use Case

API Request Handling: Map HTTP requests (route/query/body) to controller arguments using attributes:

final class CreateUser {
    public function __invoke(
        #[FromBody] string $name,
        #[FromQuery] array $roles = [],
    ) {}
}

Implementation Patterns

Core Workflows

  1. DTO Mapping:

    • Use MapperBuilder to create a mapper instance.
    • Map JSON/arrays to strongly-typed objects with validation.
    • Example: Form submissions → domain objects.
  2. HTTP Request Mapping:

    • Attributes: Use #[FromRoute], #[FromQuery], #[FromBody] to bind parameters.
    • PSR-7 Integration: Convert ServerRequestInterface to HttpRequest:
      $request = HttpRequest::fromPsr($psrRequest, $routeParams);
      $args = (new MapperBuilder())->argumentsMapper()->mapArguments($controller, $request);
      
  3. Normalization:

    • Convert objects back to arrays/JSON:
      $normalizer = (new NormalizerBuilder())->normalizer();
      $data = $normalizer->normalize($user);
      
  4. Reusable Configuration:

    • Define configurators for shared settings (e.g., date formats, key naming):
      $mapper = (new MapperBuilder())
          ->configureWith(new MyConfigurator())
          ->mapper();
      

Integration Tips

  • Laravel Middleware: Validate incoming requests early:
    public function handle(Request $request, Closure $next) {
        $data = $request->all();
        $mapper = (new MapperBuilder())->mapper();
        $mapper->map(User::class, Source::array($data));
        return $next($request);
    }
    
  • Service Providers: Bind MapperBuilder to the container for dependency injection.
  • Testing: Use MapperBuilder to validate test inputs:
    $this->expectException(MappingError::class);
    $mapper->map(User::class, Source::json('{"name": ""}'));
    

Gotchas and Tips

Pitfalls

  1. Key Mismatches:

    • Valinor is case-sensitive. Use #[KeyName] to override property names:
      final class User {
          #[KeyName('user_name')] public string $name;
      }
      
    • Enable caseSensitiveKeys in MapperBuilder if needed.
  2. Nested Validation:

    • Ensure nested objects/arrays are properly typed. Valinor validates recursively.
  3. HTTP Request Collisions:

    • If a parameter lacks attributes and appears in multiple sources (e.g., route + query), Valinor throws a CollisionError. Use attributes to disambiguate.
  4. Default Values:

    • Default values must be compile-time constants (e.g., int $page = 1). Runtime values (e.g., time()) are unsupported.

Debugging

  • Error Messages: Valinor provides detailed paths to invalid fields (e.g., cities[0].timeZone).
  • Logging: Enable debug mode in MapperBuilder for verbose output:
    $mapper = (new MapperBuilder())->debug()->mapper();
    

Extension Points

  1. Custom Types:

    • Implement Mapper\Type\Type for unsupported types (e.g., UUIDs):
      final class UuidType implements Type {
          public function map(string $value): Uuid { ... }
      }
      
    • Register with MapperBuilder:
      $mapper = (new MapperBuilder())->withType(UuidType::class)->mapper();
      
  2. Normalization:

    • Extend Normalizer\Normalizer to customize output (e.g., exclude properties):
      $normalizer = (new NormalizerBuilder())
          ->withNormalizer(new MyNormalizer())
          ->normalizer();
      
  3. Configurators:

    • Create reusable configurators for team-wide settings (e.g., date formats):
      final class DateConfigurator implements MapperBuilderConfigurator {
          public function configure(MapperBuilder $builder): void {
              $builder->withType(DateTimeType::class);
          }
      }
      

Performance Tips

  • Reuse Mappers: Instantiate MapperBuilder once (e.g., in a service provider) and reuse the mapper.
  • Cache Mappings: For complex objects, consider caching compiled mappings (Valinor supports this via MapperBuilder::withCache()).
  • Avoid Over-Validation: Use #[Assert] sparingly; prefer type hints for performance.

Laravel-Specific Quirks

  • Request Validation:
    • Combine with Laravel’s ValidatesRequests for hybrid validation:
      public function rules() {
          return ['name' => 'required|string'];
      }
      
    • Use Valinor for strong typing and Laravel for business rules.
  • Form Requests:
    • Map Illuminate\Http\Request to DTOs:
      $mapper->map(User::class, Source::array($request->all()));
      
  • API Resources:
    • Normalize Eloquent models to API responses:
      $normalizer->normalize($user)->toJson();
      
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