PHP code example of jkbennemann / laravel-api-documentation

1. Go to this page and download the library: Download jkbennemann/laravel-api-documentation library. Choose the download type require.

2. Extract the ZIP file and open the index.php.

3. Add this code to the index.php.
    
        
<?php
require_once('vendor/autoload.php');

/* Start to develop here. Best regards https://php-download.com/ */

    

jkbennemann / laravel-api-documentation example snippets


use JkBennemann\LaravelApiDocumentation\Middleware\CaptureApiResponseMiddleware;

// In a test service provider or TestCase::setUp()
$this->app['router']->pushMiddlewareToGroup('api', CaptureApiResponseMiddleware::class);

use JkBennemann\LaravelApiDocumentation\Attributes\{Tag, Summary, DataResponse};

#[Tag('Users')]
#[Summary('List all users')]
#[DataResponse(200, description: 'Paginated user list', resource: UserResource::class)]
public function index(): ResourceCollection
{
    return UserResource::collection(User::paginate());
}

'capture' => [
    'enabled' => env('DOC_CAPTURE_MODE', false),
    'storage_path' => base_path('.schemas/responses'),
    'capture' => [
        'requests'  => true,   // Capture request bodies and query params
        'responses' => true,   // Capture response bodies
        'headers'   => true,   // Capture relevant headers
        'examples'  => true,   // Store sanitized examples
    ],
    'sanitize' => [
        'enabled' => true,
        'sensitive_keys' => [
            'password', 'token', 'secret', 'api_key', 'access_token',
            'credit_card', 'cvv', 'ssn', // ... and more
        ],
        'redacted_value' => '***REDACTED***',
    ],
    'rules' => [
        'max_size' => 1024 * 100,  // Skip responses over 100KB
        'exclude_routes' => ['telescope/*', 'horizon/*', '_debugbar/*', 'sanctum/*'],
    ],
],

'analysis' => [
    'priority' => 'static_first',  // or 'captured_first'
],

use JkBennemann\LaravelApiDocumentation\Attributes\Tag;

#[Tag('Users')]
class UserController extends Controller {}

// With description
#[Tag('Widgets', description: 'Operations for managing widgets and their configurations.')]
public function index() {}

// Multiple tags
#[Tag(['Users', 'Admin'])]
public function promoteUser() {}

use JkBennemann\LaravelApiDocumentation\Attributes\Summary;
use JkBennemann\LaravelApiDocumentation\Attributes\Description;

#[Summary('List all users')]
#[Description('Returns a paginated list of users with optional filtering by status and role.')]
public function index() {}

use JkBennemann\LaravelApiDocumentation\Attributes\DataResponse;

#[DataResponse(200, description: 'User details', resource: UserResource::class)]
#[DataResponse(404, description: 'User not found', resource: ['message' => 'string'])]
public function show(string $id) {}

use JkBennemann\LaravelApiDocumentation\Attributes\Parameter;

// On a FormRequest's rules() method
#[Parameter(name: 'email',  */ }

// On a JsonResource's toArray() method
#[Parameter(name: 'id', type: 'string', format: 'uuid', description: 'Unique identifier')]
#[Parameter(name: 'avatar_url', type: 'string', format: 'uri', description: 'Profile image URL')]
public function toArray($request): array { /* ... */ }

use JkBennemann\LaravelApiDocumentation\Attributes\PathParameter;

#[PathParameter(name: 'id', type: 'string', format: 'uuid', description: 'User ID', example: '550e8400-e29b-41d4-a716-446655440000')]
public function show(string $id) {}

use JkBennemann\LaravelApiDocumentation\Attributes\QueryParameter;

#[QueryParameter(name: 'status', description: 'Filter by status', enum: ['active', 'inactive', 'banned'])]
#[QueryParameter(name: 'per_page', type: 'integer', description: 'Items per page', example: 25)]
public function index() {}

use JkBennemann\LaravelApiDocumentation\Attributes\ResponseHeader;

#[ResponseHeader(name: 'X-Request-Id', description: 'Unique request identifier', format: 'uuid')]
#[ResponseHeader(name: 'X-RateLimit-Remaining', type: 'integer', description: 'Remaining requests')]
public function index() {}

use JkBennemann\LaravelApiDocumentation\Attributes\RequestBody;
use JkBennemann\LaravelApiDocumentation\Attributes\ResponseBody;

#[RequestBody(description: 'Webhook payload', contentType: 'application/json', dataClass: WebhookPayload::class)]
#[ResponseBody(statusCode: 202, description: 'Accepted')]
public function handleWebhook() {}

use JkBennemann\LaravelApiDocumentation\Attributes\ExcludeFromDocs;

// Exclude entire controller
#[ExcludeFromDocs]
class InternalController extends Controller {}

// Exclude single method
class UserController extends Controller
{
    #[ExcludeFromDocs]
    public function debug() {}
}

use JkBennemann\LaravelApiDocumentation\Attributes\AdditionalDocumentation;

#[AdditionalDocumentation(url: 'https://docs.example.com/auth', description: 'Authentication guide')]
public function login() {}

use JkBennemann\LaravelApiDocumentation\Attributes\DocumentationFile;

#[DocumentationFile('internal-api')]
class InternalApiController extends Controller {}

use JkBennemann\LaravelApiDocumentation\Attributes\IgnoreDataParameter;

#[IgnoreDataParameter(parameters: 'internal_field')]
public function store(CreateUserData $data) {}

/**
 * Get a list of users
 *
 * Returns all active users with optional filtering.
 *
 * @queryParam per_page integer Number of results per page. Example: 25
 * @queryParam search string Search by name or email. Example: john
 * @queryParam status string Filter by account status. Example: active
 *
 * @return \Illuminate\Http\Resources\Json\ResourceCollection<UserResource>
 *
 * @deprecated Use /api/v2/users instead
 */
public function index(Request $request): ResourceCollection {}

'open_api_version' => '3.1.0',
'version' => '1.0.0',
'title' => 'My API',  // Defaults to APP_NAME

''excluded_routes' => [
    'telescope/*',
    'horizon/*',
],
'excluded_methods' => ['HEAD', 'OPTIONS'],

'servers' => [
    ['url' => env('APP_URL', 'http://localhost'), 'description' => 'Local'],
    ['url' => 'https://api.example.com', 'description' => 'Production'],
],

'analysis' => [
    'priority' => 'static_first',  // 'static_first' or 'captured_first'
    'cache_ttl' => 3600,           // AST cache TTL in seconds (0 disables)
    'cache_path' => null,          // Defaults to storage_path()
],

'code_samples' => [
    'enabled' => false,
    'languages' => ['bash', 'javascript', 'php', 'python'],
    'base_url' => null,  // null uses '{baseUrl}' placeholder
],

'error_responses' => [
    'enabled' => true,
    'defaults' => [
        'status_messages' => [
            '400' => 'The request could not be processed.',
            '401' => 'Authentication credentials are sts.',
            '500' => 'An internal server error occurred.',
        ],
    ],
],

'smart_requests' => [
    'rule_types' => [
        'string'   => ['type' => 'string'],
        'integer'  => ['type' => 'integer'],
        'boolean'  => ['type' => 'boolean'],
        'email'    => ['type' => 'string', 'format' => 'email'],
        'uuid'     => ['type' => 'string', 'format' => 'uuid'],
        'url'      => ['type' => 'string', 'format' => 'uri'],
        'file'     => ['type' => 'string', 'format' => 'binary'],
        'image'    => ['type' => 'string', 'format' => 'binary'],
        // ... and more
    ],
],

// config/api-documentation.php
'tags' => [
    'Users' => 'Operations for managing user accounts.',
    'Billing' => '## Billing API\nAll endpoints 

#[Tag('Users', description: 'Operations for managing user accounts.')]

'tag_groups' => [
    ['name' => 'User Management', 'tags' => ['Users', 'Roles', 'Permissions']],
    ['name' => 'Commerce', 'tags' => ['Products', 'Orders', 'Billing']],
],

'tag_groups_

'trait_tags' => [
    [
        'name' => 'Getting Started',
        'description' => "# Welcome\n\nThis API uses Bearer token authentication. See the [Authentication](#section/Authentication) section for details.",
    ],
    [
        'name' => 'Changelog',
        'description' => "# Changelog\n\n## v2.0\n- New billing endpoints\n- Improved error responses",
    ],
],

'external_docs' => [
    'url' => 'https://docs.example.com',
    'description' => 'Full developer documentation',
],

'domains' => [
    'public' => [
        'title' => 'Public API',
        'tags' => ['Users' => 'Public user endpoints.'],
        'tag_groups' => [
            ['name' => 'Core', 'tags' => ['Users', 'Products']],
        ],
        'external_docs' => ['url' => 'https://docs.example.com'],
    ],
    'internal' => [
        'title' => 'Internal API',
        'tags' => ['Admin' => 'Internal admin operations.'],
    ],
],

'domains' => [
    'public' => [
        'title' => 'Public API',
        'main' => 'https://api.example.com',
        'servers' => [
            ['url' => 'https://api.example.com', 'description' => 'Production'],
        ],
    ],
    'internal' => [
        'title' => 'Internal API',
        'main' => 'https://internal.example.com',
        'servers' => [
            ['url' => 'https://internal.example.com', 'description' => 'Internal'],
        ],
    ],
],

'ui' => [
    'storage' => [
        'files' => [
            'default' => [
                'name' => 'Public API',
                'filename' => 'api-documentation.json',
                'process' => true,
            ],
            'internal' => [
                'name' => 'Internal API',
                'filename' => 'internal-api.json',
                'process' => true,
            ],
        ],
    ],
],

'ui' => [
    'default' => 'swagger',  // 'swagger', 'redoc', or 'scalar'

    'swagger' => [
        'enabled' => true,
        'route' => '/documentation/swagger',
        'version' => '5.17.14',
        'middleware' => ['web'],
    ],

    'redoc' => [
        'enabled' => true,
        'route' => '/documentation/redoc',
        'version' => '2.2.0',
        'middleware' => ['web'],
    ],

    'scalar' => [
        'enabled' => true,
        'route' => '/documentation/scalar',
        'version' => '2.2.0',
        'middleware' => ['web'],
    ],
],

use JkBennemann\LaravelApiDocumentation\Contracts\Plugin;

interface Plugin
{
    public function name(): string;
    public function boot(PluginRegistry $registry): void;
    public function priority(): int;
}

// config/api-documentation.php
'plugins' => [
    App\Documentation\MyPlugin::class,
],

use JkBennemann\LaravelApiDocumentation\LaravelApiDocumentation;

LaravelApiDocumentation::extend(new MyPlugin());



namespace App\Documentation;

use JkBennemann\LaravelApiDocumentation\Contracts\OperationTransformer;
use JkBennemann\LaravelApiDocumentation\Contracts\Plugin;
use JkBennemann\LaravelApiDocumentation\Data\AnalysisContext;
use JkBennemann\LaravelApiDocumentation\PluginRegistry;

class CorrelationIdPlugin implements Plugin, OperationTransformer
{
    public function name(): string
    {
        return 'correlation-id';
    }

    public function boot(PluginRegistry $registry): void
    {
        $registry->addOperationTransformer($this, priority: 30);
    }

    public function priority(): int
    {
        return 30;
    }

    public function transform(array $operation, AnalysisContext $ctx): array
    {
        $operation['parameters'][] = [
            'name' => 'X-Correlation-ID',
            'in' => 'header',
            '

// config/api-documentation.php
'plugins' => [
    App\Documentation\CorrelationIdPlugin::class,
],

class MyPlugin implements Plugin, ResponseExtractor, QueryParameterExtractor
{
    public function name(): string { return 'my-plugin'; }
    public function priority(): int { return 35; }

    public function boot(PluginRegistry $registry): void
    {
        $registry->addResponseExtractor($this, priority: 35);
        $registry->addQueryExtractor($this, priority: 35);
    }

    public function extract(AnalysisContext $ctx): array
    {
        // This method serves both interfaces - differentiate
        // by checking what's being requested via the context
        return [];
    }
}



namespace App\Documentation;

use JkBennemann\LaravelApiDocumentation\Contracts\Plugin;
use JkBennemann\LaravelApiDocumentation\Contracts\SecuritySchemeDetector;
use JkBennemann\LaravelApiDocumentation\Data\AnalysisContext;
use JkBennemann\LaravelApiDocumentation\PluginRegistry;

class ApiKeyAuthPlugin implements Plugin, SecuritySchemeDetector
{
    /**
     * Map your middleware aliases to their API key configuration.
     * Adjust these to match your application's middleware names.
     */
    private const MIDDLEWARE_MAP = [
        'api-key'      => ['header', 'X-API-Key'],
        'api_key'      => ['header', 'X-API-Key'],
        'api.key'      => ['header', 'X-API-Key'],
        'client-token' => ['header', 'X-Client-Token'],
    ];

    public function name(): string
    {
        return 'api-key-auth';
    }

    public function boot(PluginRegistry $registry): void
    {
        $registry->addSecurityDetector($this, 45);
    }

    public function priority(): int
    {
        return 45;
    }

    public function detect(AnalysisContext $ctx): ?array
    {
        foreach ($ctx->route->middleware as $middleware) {
            $name = explode(':', $middleware)[0];

            if (isset(self::MIDDLEWARE_MAP[$name])) {
                [$in, $paramName] = self::MIDDLEWARE_MAP[$name];

                return [
                    'name' => 'apiKeyAuth',
                    'scheme' => [
                        'type' => 'apiKey',
                        'in' => $in,          // 'header' or 'query'
                        'name' => $paramName,  // The header/query parameter name
                    ],
                ];
            }
        }

        return null;
    }
}



namespace App\Documentation;

use JkBennemann\LaravelApiDocumentation\Contracts\ExceptionSchemaProvider;
use JkBennemann\LaravelApiDocumentation\Contracts\Plugin;
use JkBennemann\LaravelApiDocumentation\Data\ResponseResult;
use JkBennemann\LaravelApiDocumentation\Data\SchemaObject;
use JkBennemann\LaravelApiDocumentation\PluginRegistry;

class ProblemDetailsPlugin implements Plugin, ExceptionSchemaProvider
{
    /**
     * Map exception classes to their Problem Details type URI and title.
     * Customize this for your application's exception hierarchy.
     */
    private const EXCEPTION_MAP = [
        \Illuminate\Auth\AuthenticationException::class => [
            'status' => 401,
            'type' => 'https://httpstatuses.com/401',
            'title' => 'Unauthenticated',
        ],
        \Illuminate\Auth\Access\AuthorizationException::class => [
            'status' => 403,
            'type' => 'https://httpstatuses.com/403',
            'title' => 'Forbidden',
        ],
        \Illuminate\Database\Eloquent\ModelNotFoundException::class => [
            'status' => 404,
            'type' => 'https://httpstatuses.com/404',
            'title' => 'Resource Not Found',
        ],
        \Illuminate\Validation\ValidationException::class => [
            'status' => 422,
            'type' => 'https://httpstatuses.com/422',
            'title' => 'Validation Failed',
        ],
    ];

    public function name(): string
    {
        return 'problem-details';
    }

    public function boot(PluginRegistry $registry): void
    {
        $registry->addExceptionSchemaProvider($this);
    }

    public function priority(): int
    {
        return 35;
    }

    public function provides(string $exceptionClass): bool
    {
        return isset(self::EXCEPTION_MAP[$exceptionClass]);
    }

    public function getResponse(string $exceptionClass): ResponseResult
    {
        $config = self::EXCEPTION_MAP[$exceptionClass];

        $properties = [
            'type' => new SchemaObject(type: 'string', format: 'uri', example: $config['type']),
            'title' => new SchemaObject(type: 'string', example: $config['title']),
            'status' => new SchemaObject(type: 'integer', example: $config['status']),
            'detail' => SchemaObject::string(),
            'instance' => new SchemaObject(type: 'string', format: 'uri'),
        ];

        // Validation errors 



namespace App\Documentation;

use JkBennemann\LaravelApiDocumentation\Contracts\Plugin;
use JkBennemann\LaravelApiDocumentation\Contracts\ResponseExtractor;
use JkBennemann\LaravelApiDocumentation\Data\AnalysisContext;
use JkBennemann\LaravelApiDocumentation\Data\ResponseResult;
use JkBennemann\LaravelApiDocumentation\Data\SchemaObject;
use JkBennemann\LaravelApiDocumentation\PluginRegistry;
use PhpParser\Node;
use PhpParser\Node\ArrayItem;
use PhpParser\Node\Scalar\String_;
use PhpParser\Node\Stmt\Return_;
use PhpParser\NodeFinder;
use PhpParser\ParserFactory;

class FractalPlugin implements Plugin, ResponseExtractor
{
    public function name(): string
    {
        return 'league-fractal';
    }

    public function boot(PluginRegistry $registry): void
    {
        // Only activate when Fractal is installed
        if (! class_exists(\League\Fractal\TransformerAbstract::class)) {
            return;
        }

        $registry->addResponseExtractor($this, 35);
    }

    public function priority(): int
    {
        return 35;
    }

    public function extract(AnalysisContext $ctx): array
    {
        if (! $ctx->hasAst() || ! $ctx->hasReflection()) {
            return [];
        }

        // Find Fractal transformer usage in the controller method's AST
        $transformerClass = $this->detectTransformer($ctx);
        if ($transformerClass === null) {
            return [];
        }

        // Parse the transformer's transform() method to extract the schema
        $schema = $this->analyzeTransformer($transformerClass);
        if ($schema === null) {
            return [];
        }

        return [
            new ResponseResult(
                statusCode: 200,
                schema: $schema,
                description: 'Success',
                source: 'plugin:fractal',
            ),
        ];
    }

    /**
     * Look for patterns like:
     *   $fractal->item($model, new UserTransformer)
     *   $fractal->collection($models, new UserTransformer)
     *   return fractal($model, new UserTransformer)->toArray()
     */
    private function detectTransformer(AnalysisContext $ctx): ?string
    {
        $nodeFinder = new NodeFinder;
        $news = $nodeFinder->findInstanceOf(
            $ctx->astNode->stmts ?? [],
            Node\Expr\New_::class,
        );

        foreach ($news as $new) {
            if (! $new->class instanceof Node\Name) {
                continue;
            }

            $className = $new->class->toString();
            $resolved = $this->resolveClassName($className, $ctx);

            if ($resolved !== null
                && class_exists($resolved)
                && is_subclass_of($resolved, \League\Fractal\TransformerAbstract::class)
            ) {
                return $resolved;
            }
        }

        return null;
    }

    private function analyzeTransformer(string $transformerClass): ?SchemaObject
    {
        try {
            $ref = new \ReflectionClass($transformerClass);
            $fileName = $ref->getFileName();
            if ($fileName === false || ! file_exists($fileName)) {
                return null;
            }
        } catch (\Throwable) {
            return null;
        }

        // Parse the transformer file's AST
        $parser = (new ParserFactory)->createForNewestSupportedVersion();
        $stmts = $parser->parse(file_get_contents($fileName));
        if ($stmts === null) {
            return null;
        }

        // Find the transform() method
        $nodeFinder = new NodeFinder;
        $methods = $nodeFinder->findInstanceOf($stmts, Node\Stmt\ClassMethod::class);

        foreach ($methods as $method) {
            if ($method->name->toString() !== 'transform') {
                continue;
            }

            return $this->extractSchemaFromTransformMethod($method);
        }

        return null;
    }

    private function extractSchemaFromTransformMethod(Node\Stmt\ClassMethod $method): ?SchemaObject
    {
        $nodeFinder = new NodeFinder;
        $returns = $nodeFinder->findInstanceOf($method->stmts ?? [], Return_::class);

        foreach ($returns as $return) {
            if (! $return->expr instanceof Node\Expr\Array_) {
                continue;
            }

            $properties = [];
            foreach ($return->expr->items as $item) {
                if (! $item instanceof ArrayItem || ! $item->key instanceof String_) {
                    continue;
                }

                $properties[$item->key->value] = $this->inferType($item->value);
            }

            if (! empty($properties)) {
                return SchemaObject::object($properties);
            }
        }

        return null;
    }

    private function inferType(Node\Expr $expr): SchemaObject
    {
        // $model->id, $model->count — infer from property name
        if ($expr instanceof Node\Expr\PropertyFetch
            && $expr->name instanceof Node\Identifier
        ) {
            return $this->inferFromName($expr->name->toString());
        }

        // (int) $value, (bool) $value
        if ($expr instanceof Node\Expr\Cast\Int_) {
            return SchemaObject::integer();
        }
        if ($expr instanceof Node\Expr\Cast\Bool_) {
            return SchemaObject::boolean();
        }
        if ($expr instanceof Node\Expr\Cast\Double) {
            return SchemaObject::number('double');
        }

        return SchemaObject::string();
    }

    private function inferFromName(string $name): SchemaObject
    {
        return match (true) {
            $name === 'id' => SchemaObject::integer(),
            str_ends_with($name, '_id') => SchemaObject::integer(),
            str_ends_with($name, '_count') => SchemaObject::integer(),
            str_starts_with($name, 'is_') || str_starts_with($name, 'has_') => SchemaObject::boolean(),
            str_ends_with($name, '_at') => SchemaObject::string('date-time'),
            str_contains($name, 'email') => SchemaObject::string('email'),
            str_contains($name, 'url') || str_contains($name, 'link') => SchemaObject::string('uri'),
            str_contains($name, 'uuid') => SchemaObject::string('uuid'),
            str_contains($name, 'price') || str_contains($name, 'amount') => SchemaObject::number('double'),
            default => SchemaObject::string(),
        };
    }

    private function resolveClassName(string $shortName, AnalysisContext $ctx): ?string
    {
        // Try fully qualified
        if (class_exists($shortName)) {
            return $shortName;
        }

        // Try same namespace as controller
        $controllerClass = $ctx->controllerClass();
        if ($controllerClass !== null) {
            $ns = substr($controllerClass, 0, (int) strrpos($controllerClass, '\\'));
            $fqcn = $ns . '\\' . $shortName;
            if (class_exists($fqcn)) {
                return $fqcn;
            }

            // Try Transformers sub-namespace
            $parentNs = substr($ns, 0, (int) strrpos($ns, '\\'));
            $fqcn = $parentNs . '\\Transformers\\' . $shortName;
            if (class_exists($fqcn)) {
                return $fqcn;
            }
        }

        return null;
    }
}



namespace App\Documentation;

use JkBennemann\LaravelApiDocumentation\Contracts\OperationTransformer;
use JkBennemann\LaravelApiDocumentation\Contracts\Plugin;
use JkBennemann\LaravelApiDocumentation\Data\AnalysisContext;
use JkBennemann\LaravelApiDocumentation\PluginRegistry;

class VersioningHeaderPlugin implements Plugin, OperationTransformer
{
    public function __construct(
        private string $headerName = 'X-API-Version',
        private array $supportedVersions = ['2024-01-01', '2025-01-01'],
        private ?string $defaultVersion = null,
    ) {
        $this->defaultVersion ??= end($this->supportedVersions) ?: null;
    }

    public function name(): string
    {
        return 'versioning-header';
    }

    public function boot(PluginRegistry $registry): void
    {
        $registry->addOperationTransformer($this, 25);
    }

    public function priority(): int
    {
        return 25;
    }

    public function transform(array $operation, AnalysisContext $ctx): array
    {
        // Only add to routes that match your versioned API prefix
        if (! str_starts_with($ctx->route->uri, 'api/')) {
            return $operation;
        }

        $operation['parameters'] ??= [];
        $operation['parameters'][] = [
            'name' => $this->headerName,
            'in' => 'header',
            '

// config/api-documentation.php
'plugins' => [
    new App\Documentation\VersioningHeaderPlugin(
        headerName: 'X-API-Version',
        supportedVersions: ['2024-01-01', '2024-07-01', '2025-01-01'],
    ),
],



namespace App\Documentation;

use JkBennemann\LaravelApiDocumentation\Contracts\OperationTransformer;
use JkBennemann\LaravelApiDocumentation\Contracts\Plugin;
use JkBennemann\LaravelApiDocumentation\Data\AnalysisContext;
use JkBennemann\LaravelApiDocumentation\PluginRegistry;

class CacheHeaderPlugin implements Plugin, OperationTransformer
{
    /**
     * Middleware names that indicate the response supports caching.
     */
    private const CACHE_MIDDLEWARE = [
        'cache.headers',
        'etag',
        'last-modified',
    ];

    public function name(): string
    {
        return 'cache-headers';
    }

    public function boot(PluginRegistry $registry): void
    {
        $registry->addOperationTransformer($this, 20);
    }

    public function priority(): int
    {
        return 20;
    }

    public function transform(array $operation, AnalysisContext $ctx): array
    {
        // Only apply to GET requests with caching middleware
        if (strtoupper($ctx->route->httpMethod()) !== 'GET') {
            return $operation;
        }

        $cacheMiddleware = $this->detectCacheMiddleware($ctx);
        if ($cacheMiddleware === null) {
            return $operation;
        }

        // Add conditional request headers
        $operation['parameters'] ??= [];
        $operation['parameters'][] = [
            'name' => 'If-None-Match',
            'in' => 'header',
            'ue)) {
                return $middleware;
            }
        }

        return null;
    }

    private function buildCacheControlExample(string $middleware): string
    {
        // Parse cache.headers:public;max_age=3600 format
        if (str_starts_with($middleware, 'cache.headers:')) {
            $directives = substr($middleware, 14);

            return str_replace(['_', ';'], ['-', ', '], $directives);
        }

        return 'public, max-age=3600';
    }
}

class CreateUserData extends Data
{
    public function __construct(
        public string $name,
        public string $email,
        public ?string $bio = null,
    ) {}
}

// Automatically generates request body schema with name (

$users = QueryBuilder::for(User::class)
    ->allowedFilters(['name', 'email', 'status'])
    ->allowedSorts(['name', 'created_at'])
    ->allowedIncludes(['posts', 'profile'])
    ->paginate();
// Generates: filter[name], filter[email], filter[status], sort (enum), 

class CreateUser
{
    use AsAction;
    use AsController;

    public function rules(): array
    {
        return ['name' => '
bash
php artisan vendor:publish --tag="api-documentation-config"
bash
php artisan api:generate
bash
php artisan api:generate
xml
<php>
    <env name="DOC_CAPTURE_MODE" value="true"/>
</php>
bash
php artisan test
php artisan api:generate
bash
php artisan api:generate [options]
bash
# Standard generation
php artisan api:generate

# YAML output
php artisan api:generate --format=yaml

# Debug a single route
php artisan api:generate --route=api/users --method=GET --verbose-analysis

# Watch mode during development
php artisan api:generate --watch

# Export as Postman collection
php artisan api:generate --format=postman
bash
php artisan api:lint [options]
bash
php artisan api:diff <old-spec> <new-spec> [options]
bash
# Compare specs
php artisan api:diff public/api-v1.json public/api-v2.json

# Use in CI to block breaking changes
php artisan api:diff old.json new.json --fail-on-breaking
bash
php artisan api:types [options]
bash
php artisan api:clear-cache [options]
bash
php artisan api:plugins
bash
php artisan api:generate --domain=public
bash
php artisan vendor:publish --tag="api-documentation-views"
bash
php artisan api:generate
# Output: storage/app/public/api-documentation.json
bash
php artisan api:generate --format=yaml
bash
php artisan api:generate --format=postman
bash
php artisan api:types
# Output: resources/js/types/api.d.ts
bash
php artisan api:generate --format=json
bash
DOC_CAPTURE_MODE=true php artisan test
php artisan api:generate