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 Bundle Laravel Package

cuyz/valinor-bundle

View on GitHub
Deep Wiki
Context7

Getting Started

Install the package via Composer:

composer require cuyz/valinor-bundle

Enable the bundle in config/bundles.php:

return [
    // ...
    CuyZ\ValinorBundle\ValinorBundle::class => ['all' => true],
];

First Use Case: Replace manual request parsing in a controller with attribute-based mapping. For example, convert this:

public function listArticles(Request $request, string $authorId): Response
{
    $status = $request->query->get('status');
    $page = (int) $request->query->get('page', 1);
    // ...
}

To this:

#[Route('/api/authors/{authorId}/articles', methods: 'GET')]
#[MapRequest]
public function __invoke(
    #[FromRoute] string $authorId,
    #[FromQuery] string $status,
    #[FromQuery] positive-int $page = 1,
): Response { /* ... */ }

Key first steps:

  1. Add #[MapRequest] to your controller method.
  2. Use #[FromRoute], #[FromQuery], or #[FromBody] on method arguments.
  3. Leverage PHP 8.0+ type system (e.g., positive-int, int<10,100>) for validation.

Implementation Patterns

Core Workflow

  1. Declarative Mapping:

    • Annotate controller arguments with source attributes (#[FromRoute], #[FromQuery], #[FromBody]).
    • Let the bundle handle parsing, validation, and type conversion automatically.
  2. Grouped Mapping:

    • Use mapAll: true to map entire query/body payloads to a single DTO (e.g., #[FromQuery(mapAll: true)] ArticleFilters $filters).
    • Ideal for complex APIs with many parameters (e.g., pagination, filtering).
  3. Controller-Level Configuration:

    • Centralize mapper rules (e.g., key case conversion, date formats) via custom attributes implementing MapRequestAttribute.
    • Example: Enforce snake_case keys across all API controllers.

Integration Tips

  • Symfony Forms Integration: Combine with Symfony’s #[MapEntity] for form submissions:

    #[Route('/api/articles', methods: 'POST')]
    #[MapRequest]
    public function createArticle(
        #[FromBody] #[MapEntity] Article $article,
    ): Response { /* ... */ }
    
  • Validation Layer: Use the bundle’s error messages in your API responses (e.g., JSON:API format):

    try {
        $response = $controller->__invoke($request);
    } catch (HttpRequestMappingError $e) {
        return new JsonResponse(['errors' => $e->getErrors()], 422);
    }
    
  • Testing: Mock request objects with ValinorMapper directly:

    $mapper = new ValinorMapper($builder);
    $result = $mapper->map($request, new CreateAuthorDto());
    $this->assertSame('John', $result->name);
    
  • Legacy Code: Mix with manual parsing where needed (e.g., for non-standard request formats):

    public function __invoke(Request $request, #[FromRoute] string $id): Response
    {
        $manualData = $request->get('legacy_field');
        // ...
    }
    

Gotchas and Tips

Pitfalls

  1. Attribute Order Matters:

    • Place #[MapRequest] after #[Route] in PHPDoc to avoid IDE warnings.
    • Example:
      #[Route('/api/articles')]
      #[MapRequest] // <-- Correct order
      
  2. Type Coercion Quirks:

    • int vs. positive-int: The latter rejects 0 and negative values, while int accepts them.
    • Always prefer constrained types (e.g., int<1,100>) over loose types (int) for validation.
  3. Circular References:

    • Avoid mapping nested objects with circular references (e.g., User with self-referencing friends property).
    • Use #[IgnoreExtraFields] or #[MapExtraFields] to control behavior.
  4. Case Sensitivity:

    • Key case configurators (e.g., RestrictKeysToCamelCase) are strict. A request with snake_case keys will fail unless converted first.
  5. Performance:

    • Pre-compile mapper configurations in bootstrap/cache for production:
      php bin/console cache:clear --env=prod
      

Debugging

  • Enable Detailed Errors: Set VALINOR_DEBUG=1 in your environment to get verbose error messages during development.

  • Inspect Mapped Data: Use #[MapRequest(debug: true)] to log the mapped values (useful for troubleshooting).

  • Common Errors:

    Error Message Solution
    No mapper found for type X Ensure the type is supported or implement a custom mapper.
    Key "invalid_key" not allowed Add #[AllowExtraFields] or adjust key case configurators.
    Failed to parse date "2023-01-01" Extend supported date formats in configureMapperBuilder.

Extension Points

  1. Custom Mappers:

    • Implement CuyZ\Valinor\Mapper\MapperInterface for unsupported types (e.g., custom DTOs):
      class CustomDtoMapper implements MapperInterface
      {
          public function map(mixed $data, string $type): mixed
          {
              return new CustomDto($data['field1'], $data['field2']);
          }
      }
      
    • Register it in your MapperBuilder:
      $builder->registerMapper(CustomDto::class, new CustomDtoMapper());
      
  2. Global Configuration:

    • Override default mapper behavior in config/packages/valinor.yaml:
      valinor:
          default_mapper_configurators:
              - CuyZ\Valinor\Mapper\Configurator\ConvertKeysToCamelCase
          date_formats: ['Y-m-d', 'Y-m-d H:i:s']
      
  3. Event Listeners:

    • Listen to valinor.mapper.created events to log or modify mappers dynamically:
      $eventDispatcher->addListener(ValinorEvents::MAPPER_CREATED, function (MapperCreatedEvent $event) {
          if ($event->getType() === MyDto::class) {
              $event->setMapper(new CustomMyDtoMapper());
          }
      });
      
  4. Fallback for Missing Fields:

    • Use #[DefaultValue] to provide defaults for missing query/body fields:
      #[FromQuery] #[DefaultValue('active')] string $status
      

Pro Tips

  • API Versioning: Use custom MapRequestAttribute to enforce different validation rules per API version:

    #[MapRequest(new RestrictKeysToSnakeCase(), new AllowExtraFields())]
    
  • OpenAPI/Swagger: Generate OpenAPI specs dynamically by combining #[MapRequest] with nelmio/api-doc-bundle:

    # config/packages/nelmio_api_doc.yaml
    nelmio_api_doc:
        schemas:
            path: ./var/cache/dev/api
            context:
                valinor: true
    
  • Batch Processing: Map arrays of objects efficiently:

    #[FromBody] #[MapEntity] array<int, Article> $articles
    

    Ensure your MapperBuilder supports array mapping:

    $builder->supportArrayMapping();
    
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.
andydefer/laravel-cluster
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
christhompsontldr/laravel-inky
spatie/mailcoach-vapor
spatie/laravel-javascript-views