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 input (JSON/arrays) into fully typed PHP objects, validating along the way with clear, human-readable errors. Supports advanced types (PHPStan/Psalm), shaped arrays, generics, ranges, and can normalize objects back to JSON/CSV while preserving structure.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Installation:

    composer require cuyz/valinor
    
  2. Basic Mapping: Define a DTO class with typed properties (e.g., Country with name and cities). Use MapperBuilder to map raw input (JSON/array) to the DTO:

    $country = (new \CuyZ\Valinor\MapperBuilder())
        ->mapper()
        ->map(Country::class, \CuyZ\Valinor\Mapper\Source\Source::json($json));
    
  3. Error Handling: Wrap mapping in a try-catch to handle MappingError:

    try {
        $country = $mapper->map(Country::class, $source);
    } catch (\CuyZ\Valinor\Mapper\MappingError $error) {
        // Log or return user-friendly errors
    }
    

First Use Case: API Request Validation

Map HTTP request data (e.g., JSON payload) to a DTO for validation:

$mapper = (new MapperBuilder())->mapper();
$requestData = json_decode(file_get_contents('php://input'), true);
$dto = $mapper->map(UserCreateRequest::class, Source::array($requestData));

Implementation Patterns

1. DTO Design

  • Strict Typing: Use PHPStan/Psalm annotations (e.g., non-empty-string, list<City>) for runtime validation.
  • Readonly Properties: Prefer readonly properties to enforce immutability.
  • Nested Objects: Define nested DTOs (e.g., City inside Country) for complex structures.

2. Mapper Workflows

  • Reusable Mappers: Instantiate MapperBuilder once (e.g., as a service) and reuse the mapper:
    $mapper = (new MapperBuilder())->mapper();
    $container->bind('mapper', fn() => $mapper);
    
  • Source Handling: Use Source::json(), Source::array(), or Source::file() for different input types.

3. Integration with Frameworks

  • Symfony: Use cuyz/valinor-bundle for automatic request mapping in controllers.
  • Mezzio: Leverage mezzio/mezzio-valinor for PSR-7 request handling.
  • Custom Frameworks: Manually resolve arguments via ArgumentsMapper:
    $arguments = $argumentsMapper->mapArguments($controller, HttpRequest::fromPsr($psrRequest));
    

4. Normalization

Convert DTOs back to arrays/JSON for APIs or storage:

$normalizer = (new NormalizerBuilder())->normalizer(Format::json());
$json = $normalizer->normalize($country);

5. Custom Constructors

Override default mapping for complex objects:

$mapperBuilder->withConstructorResolver(new CustomConstructorResolver());

Gotchas and Tips

Pitfalls

  1. Cache Invalidation:

    • Forgetting to clear the cache (MapperBuilder::clearCache()) after changing DTOs causes stale mappings.
    • In development, use FileWatchingCache to auto-invalidate on file changes:
      $cache = new FileWatchingCache(new FileSystemCache('cache_dir'));
      $mapperBuilder->withCache($cache);
      
  2. Performance:

    • Cold Starts: Warm the cache during bootstrapping:
      $mapperBuilder->warmupCacheFor([Country::class, City::class]);
      
    • Avoid Rebuilding: Reuse MapperBuilder instances; rebuilding mappers is expensive.
  3. Static Analysis:

    • False Positives: Annotate purity violations with @phpstan-ignore or @psalm-suppress if needed.
    • Global Suppression: Include valinor-phpstan-suppress-pure-errors.php in phpstan.neon to ignore all purity errors.
  4. Flexible Modes:

    • Enable MapperBuilder::withFlexibleMode() to allow partial mapping (e.g., missing optional fields), but validate strictly by default.

Debugging Tips

  • Detailed Errors: Use MappingError::getErrors() to inspect validation failures:
    foreach ($error->getErrors() as $path => $error) {
        echo "Path $path: {$error->getMessage()}\n";
    }
    
  • Type Mismatches: Psalm/PHPStan will catch type errors early (e.g., string * int in shaped arrays).

Extension Points

  1. Custom Normalizers: Add support for new formats (e.g., CSV) by implementing NormalizerInterface.
  2. Source Adapters: Extend Source to handle custom input formats (e.g., XML).
  3. Validation Rules: Use MapperBuilder::withValidationRules() to add custom rules (e.g., regex validation):
    $mapperBuilder->withValidationRules([
        'email' => new EmailValidationRule(),
    ]);
    

Pro Tips

  • Immutable DTOs: Combine with spatie/immutable-array for immutable nested arrays.
  • API Responses: Normalize DTOs to JSON for consistent API responses:
    return response()->json($normalizer->normalize($dto));
    
  • Testing: Use MapperBuilder::withTestingMode() to skip cache and validate edge cases.
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.
davejamesmiller/laravel-breadcrumbs
artisanry/parsedown
christhompsontldr/phpsdk
enqueue/dsn
bunny/bunny
enqueue/test
enqueue/null
enqueue/amqp-tools
milesj/emojibase
bower-asset/punycode
bower-asset/inputmask
bower-asset/jquery
bower-asset/yii2-pjax
laravel/nova
spatie/laravel-mailcoach
spatie/laravel-superseeder
laravel/liferaft
nst/json-test-suite
danielmiessler/sec-lists
jackalope/jackalope-transport