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

Openapi Psr7 Validator Laravel Package

league/openapi-psr7-validator

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Install the package:

    composer require league/openapi-psr7-validator
    
  2. Load your OpenAPI spec (YAML/JSON):

    use League\OpenAPIValidation\PSR7\ValidatorBuilder;
    
    $validator = (new ValidatorBuilder())
        ->fromYamlFile(__DIR__.'/api.yaml')
        ->getServerRequestValidator();
    
  3. Validate a PSR-7 request (e.g., in a middleware or controller):

    $match = $validator->validate($request);
    

First Use Case: API Gateway Validation

Validate incoming requests against your OpenAPI spec before routing:

$validator = (new ValidatorBuilder())
    ->fromYamlFile(config('openapi.spec'))
    ->getServerRequestValidator();

try {
    $match = $validator->validate($request);
    // Proceed with matched operation
} catch (\League\OpenAPIValidation\PSR7\Exception\ValidationFailed $e) {
    abort(400, 'Invalid request: ' . $e->getMessage());
}

Implementation Patterns

1. Middleware Integration (PSR-15)

Workflow:

  1. Create middleware in app/Http/Middleware/ValidateOpenApi.php:

    namespace App\Http\Middleware;
    
    use League\OpenAPIValidation\PSR15\ValidationMiddlewareBuilder;
    use Closure;
    
    class ValidateOpenApi
    {
        public function __construct()
        {
            $this->middleware = (new ValidationMiddlewareBuilder())
                ->fromYamlFile(config('openapi.spec'))
                ->getValidationMiddleware();
        }
    
        public function handle($request, Closure $next)
        {
            return $this->middleware->process($request, $next);
        }
    }
    
  2. Register in app/Http/Kernel.php:

    protected $middleware = [
        \App\Http\Middleware\ValidateOpenApi::class,
    ];
    

2. Controller-Level Validation

For known routes, use RoutedRequestValidator:

use League\OpenAPIValidation\PSR7\OperationAddress;

public function store(Request $request)
{
    $validator = (new ValidatorBuilder())
        ->fromYamlFile(config('openapi.spec'))
        ->getRoutedRequestValidator();

    $address = new OperationAddress('/users', 'post');
    $validator->validate($address, $request);

    // Proceed with business logic
}

3. Response Validation

Validate API responses before sending:

use League\OpenAPIValidation\PSR7\OperationAddress;

public function show(User $user)
{
    $response = response()->json($user);

    $validator = (new ValidatorBuilder())
        ->fromYamlFile(config('openapi.spec'))
        ->getResponseValidator();

    $address = new OperationAddress('/users/{id}', 'get');
    $validator->validate($address, $response);

    return $response;
}

4. Custom Format Validation

Extend built-in formats (e.g., for Laravel's Carbon instances):

use League\OpenAPIValidation\Schema\TypeFormats\FormatsContainer;

FormatsContainer::registerFormat('string', 'date-time', function ($value) {
    return Carbon::parse($value) instanceof Carbon;
});

5. Caching for Performance

Cache the OpenAPI schema to avoid re-parsing:

use League\OpenAPIValidation\PSR7\ValidatorBuilder;
use Symfony\Contracts\Cache\CacheInterface;

public function __construct(CacheInterface $cache)
{
    $this->validator = (new ValidatorBuilder())
        ->fromYamlFile(config('openapi.spec'))
        ->setCache($cache, 3600) // 1-hour TTL
        ->getServerRequestValidator();
}

Gotchas and Tips

Pitfalls

  1. Case-Sensitive Paths OpenAPI paths are case-sensitive. Ensure your Laravel routes match exactly:

    # api.yaml
    paths:
      /users:
        get: ...
    
    // Laravel route (correct)
    Route::get('/users', ...);
    
    // Laravel route (will fail validation)
    Route::get('/Users', ...);
    
  2. Content-Type Headers Missing or incorrect Content-Type headers cause NoContentType exceptions. Always set them:

    $request->getBody()->rewind();
    $request = $request->withHeader('Content-Type', 'application/json');
    
  3. Query Parameter Validation OpenAPI validates query params strictly. Use explode for multi-value params:

    # api.yaml
    parameters:
      - in: query
        name: tags
        schema:
          type: array
          items:
            type: string
    
    // Laravel request (correct)
    $request->query('tags', ['tag1', 'tag2']);
    
    // Laravel request (will fail)
    $request->query('tags', 'tag1,tag2');
    
  4. Response Status Codes Validate responses against the exact status code defined in OpenAPI:

    # api.yaml
    responses:
      200:
        description: OK
    
    // Laravel response (correct)
    return response()->json([], 200);
    
    // Laravel response (will fail)
    return response()->json([], 201); // Not in OpenAPI spec
    

Debugging Tips

  1. Inspect Validation Errors Catch ValidationFailed and inspect nested exceptions:

    try {
        $validator->validate($request);
    } catch (\League\OpenAPIValidation\PSR7\Exception\ValidationFailed $e) {
        foreach ($e->getErrors() as $error) {
            dump($error->getMessage()); // e.g., "Invalid query parameter 'limit': must be integer"
        }
    }
    
  2. Validate Standalone Schemas Test JSON schemas independently:

    use League\OpenAPIValidation\Schema\SchemaValidator;
    
    $validator = new SchemaValidator();
    $schema = cebe\openapi\Reader::readFromYaml(file_get_contents('api.yaml'))->schema;
    
    try {
        $validator->validate($data, $schema);
    } catch (\League\OpenAPIValidation\Schema\Exception\KeywordMismatch $e) {
        dump($e->keyword(), $e->data()); // e.g., "type", "string"
    }
    
  3. Log Matched Operations Debug route matching:

    $match = $validator->validate($request);
    dump($match->getPath(), $match->getMethod()); // e.g., "/users", "GET"
    

Extension Points

  1. Custom Error Responses Override default error handling in middleware:

    public function handle($request, Closure $next)
    {
        try {
            return $next($request);
        } catch (\League\OpenAPIValidation\PSR7\Exception\ValidationFailed $e) {
            return response()->json([
                'errors' => collect($e->getErrors())
                    ->map(fn($error) => $error->getMessage())
                    ->all()
            ], 400);
        }
    }
    
  2. Dynamic Schema Loading Load OpenAPI specs from a database or external API:

    $spec = Cache::remember('openapi-spec', 3600, function () {
        return file_get_contents(config('openapi.url'));
    });
    
    $validator = (new ValidatorBuilder())
        ->fromJson($spec)
        ->getServerRequestValidator();
    
  3. Integration with Laravel Packages

    • Laravel API Resources: Validate serialized responses:
      $validator->validate(
          new OperationAddress('/users/{id}', 'get'),
          response()->json($this->toArray($user))
      );
      
    • Laravel Sanctum/Passport: Validate auth headers:
      # api.yaml
      components:
        securitySchemes:
          bearerAuth:
            type: http
            scheme: bearer
      security:
        - bearerAuth: []
      
  4. Testing Validations Use in PHPUnit tests:

    public function test_valid_request()
    {
        $validator = (new ValidatorBuilder())
            ->fromYamlFile(__DIR__.'/api.yaml')
            ->getServerRequestValidator();
    
        $request = new ServerRequest(
            'GET',
            '/users',
            ['Content-Type' => 'application/json']
        );
    
        $this->assertInstanceOf(
            OperationAddress::class,
            $validator->validate($request)
        );
    }
    

Performance Quirks

  1. Schema Caching

    • Enable PSR-6 caching for large specs:
      $validator->setCache(new ArrayCachePool(), 86400); // 24h TTL
      
    • Cache Key Overrides: Use custom keys for shared schemas:
      $validator->overrideCacheKey('shared-openapi-spec');
      
  2. Middleware Overhead

    • Disable validation in `config
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