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.0+ constructor property promotion with type hints and PHPDoc annotations (e.g., @var non-empty-string).

    final class User {
        public function __construct(
            public string $name,
            public int $age,
        ) {}
    }
    
  3. Map Input to Object: Use MapperBuilder to convert raw input (JSON, array, HTTP request) into a strongly-typed object.

    $user = (new MapperBuilder())
        ->mapper()
        ->map(User::class, Source::json('{"name": "John", "age": 30}'));
    
  4. Handle Errors: Wrap mapping in a try-catch block to handle MappingError exceptions.

    try {
        $user = $mapper->map(User::class, Source::json('{"name": "", "age": "invalid"}'));
    } catch (MappingError $error) {
        // Log or return validation errors
        foreach ($error->getErrors() as $error) {
            echo $error->getMessage();
        }
    }
    

First Use Case

API Request Validation: Map HTTP request data (route, query, or body) to a DTO for validation before processing.

use CuyZ\Valinor\Mapper\Http\FromBody;

final class CreateUser {
    public function __invoke(
        #[FromBody] User $user,
    ) {
        // $user is validated and typed
    }
}

Implementation Patterns

Core Workflows

  1. DTO Mapping: Use Valinor to convert untrusted input (e.g., JSON, form data) into typed objects.

    $data = json_decode(file_get_contents('php://input'), true);
    $dto = $mapper->map(MyDto::class, Source::array($data));
    
  2. HTTP Request Handling: Leverage HttpRequest and attributes (#[FromRoute], #[FromQuery], #[FromBody]) for framework-agnostic request binding.

    $request = HttpRequest::fromPsr($psrRequest, $routeParams);
    $arguments = $mapper->mapArguments(new MyController(), $request);
    
  3. Nested Structures: Handle complex nested objects/arrays using PHPDoc annotations.

    final class Post {
        public function __construct(
            public string $title,
            public array $tags, // @var list<string>
        ) {}
    }
    
  4. Normalization: Convert objects back to arrays/JSON for APIs or storage.

    $normalizer = (new NormalizerBuilder())->normalizer();
    $array = $normalizer->normalize($object);
    

Integration Tips

  • Laravel Service Providers: Bind MapperBuilder and NormalizerBuilder as singletons in AppServiceProvider.

    $this->app->singleton(MapperBuilder::class, fn() => new MapperBuilder());
    
  • Middleware for Validation: Create middleware to validate incoming requests using Valinor.

    public function handle(Request $request, Closure $next) {
        $dto = $mapper->map(MyDto::class, Source::json($request->getContent()));
        $request->merge(['dto' => $dto]);
        return $next($request);
    }
    
  • Custom Sources: Extend Source for domain-specific inputs (e.g., database records).

    class DatabaseSource implements Source {
        public function __construct(private array $data) {}
        public function getContent(): array { return $this->data; }
    }
    
  • Reusable Configurators: Define configurators for shared settings (e.g., date parsing, custom types).

    $mapperBuilder->configureWith(new DateTimeConfigurator());
    

Gotchas and Tips

Pitfalls

  1. Case Sensitivity: Valinor is case-sensitive by default. Use MapperBuilder::withKeysCaseRestriction() to enforce snake_case or camelCase.

    $mapperBuilder->withKeysCaseRestriction(KeysCaseRestriction::CAMEL_CASE);
    
  2. Missing Required Fields: Omit default values for required fields. Valinor will throw MappingError if missing.

    // ❌ Throws error if 'name' is missing
    public function __construct(public string $name) {}
    
    // ✅ Optional field with default
    public function __construct(public ?string $name = null) {}
    
  3. Type Mismatches: Ensure PHPDoc types match constructor types. Valinor uses PHPDoc for validation.

    // ❌ Incorrect: PHPDoc says 'string' but constructor expects 'int'
    /** @var string */
    public function __construct(public int $age) {}
    
  4. Circular References: Avoid circular references in nested objects (e.g., UserPost). Use #[AsArray] or lazy loading.

  5. HTTP Request Collisions: If a key exists in multiple sources (e.g., route and query), use attributes (#[FromRoute], #[FromQuery]) to disambiguate.

Debugging

  • Detailed Errors: Use $error->getErrors() to get granular validation messages.

    foreach ($error->getErrors() as $error) {
        echo $error->getPath() . ': ' . $error->getMessage();
    }
    
  • Logging: Enable debug mode in MapperBuilder for verbose output.

    $mapperBuilder->withDebugMode(true);
    
  • Validation Rules: Use PHPDoc to enforce custom rules (e.g., @var int<1, 100> for ranges).

Extension Points

  1. Custom Types: Register custom types via MapperBuilder::withCustomType().

    $mapperBuilder->withCustomType(new UuidType());
    
  2. Normalization: Extend Normalizer to handle custom objects.

    $normalizer->registerNormalizer(new MyObjectNormalizer());
    
  3. Sources: Implement Source interface for new input formats (e.g., XML, CSV).

  4. Configurators: Create reusable configurators for shared settings (e.g., date formats, custom validators).

    class MyConfigurator implements MapperBuilderConfigurator {
        public function configure(MapperBuilder $builder) {
            $builder->withDateTimeFormat('Y-m-d H:i:s');
        }
    }
    

Performance Tips

  • Reuse Mappers: Instantiate MapperBuilder once and reuse it (e.g., as a singleton).
  • Avoid Redundant Validation: Use MapperBuilder::withValidationMode(ValidationMode::LOOSE) for trusted inputs (e.g., internal APIs).
  • Batch Processing: For bulk operations, map arrays of data using MapperBuilder::withBatchMode(true).
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.
nexmo/api-specification
capell-app/block-library
axium/identity
cetria/laravel-dummy-models
cetria/reflection-helper
agropredict/sso-auth-bundle
evolvestudio/spam-protection
datacore/hub-sdk
develia/commons
cuci/prototurk-sdk
cuci/prototurk-sdk-symfony
develia/geo-bundle
dreamzy/livewire-charts
touchestate-sdk/php-sdk
ecotone/kafka
22h/doctrine-garbage-collection-bundle
agtp/agtp-php
agtp/mod-php
splash/sonata-admin
splash/metadata