Core concepts

Extensions

PHP autodoc typically attempts to determine data types using native PHP data types and PHPDoc comments. However, in some cases, you might need custom logic for specific classes or functions. This is where extensions come into play.

To enable an extension, add the extension class to extensions array in your autodoc config.

Available extension types

1. Class extension

By default, except for enums and classes implementing DateTimeInterface or Stringable, autodoc attempts to read class properties using Reflection API, analyzing their native data types and PHPDoc comments, including the PHPDoc comment above the class. However, in some cases, this behavior does not produce the desired results, and custom logic is required.

To create a custom logic for a class, create a class extending AutoDoc\Extensions\ClassExtension with any of the following methods:

use AutoDoc\Analyzer\PhpClass;
use AutoDoc\DataTypes\{ObjectType, Type};
use AutoDoc\Extensions\ClassExtension;

class CustomClassExtension extends ClassExtension
{
    public function getReturnType(PhpClass $phpClass): ?Type
    {
        // At first it is recommended to filter the classes targeted by
        // this extension.
        if (! is_subclass_of($phpClass->className, CustomResponse::class)) {
            return null;
        }

        // Return a subtype of `AutoDoc\DataTypes\Type` that matches
        // the response body.
        return new ObjectType([
            'type' => new StringType(['success', 'error', 'warning']),
            'data' => $phpClass->resolveType(),
        ]);
    }

    public function getPropertyType(PhpClass $phpClass, string $propertyName): ?Type
    {
        // Filter out the classes and properties targeted by this extension...

        // Return a subtype of `AutoDoc\DataTypes\Type` that matches
        // the property type.
    }

    public function getRequestType(PhpClass $phpClass): ?Type
    {
        // Filter out the classes targeted by this extension...

        // Return a subtype of `AutoDoc\DataTypes\Type` that matches
        // the request body.
    }
}

If the getReturnType/getRequestType method returns null, the next extension is processed. If it returns a Type, further extensions are not processed for current class/object. The getPropertyType method is only checked when accessing the property directly with -> operator - it will not trigger for each property when the associated object is processed.

2. Function call extension

To create a function call extension, create a class extending AutoDoc\Extensions\FuncCallExtension with one or both of the following methods:

use AutoDoc\Analyzer\Scope;
use AutoDoc\DataTypes\Type;
use AutoDoc\Extensions\FuncCallExtension;
use PhpParser\Node\Expr\FuncCall;

class CustomFuncCallExtension extends FuncCallExtension
{
    public function getReturnType(FuncCall $funcCall, Scope $scope): ?Type
    {
        //
    }

    public function getRequestType(FuncCall $funcCall, Scope $scope): ?Type
    {
        //
    }
}

If the getReturnType/getRequestType method returns null, the next extension is processed. If it returns a Type, further extensions are not processed for current function call.

3. Method call extension

To create a method call extension, create a class extending AutoDoc\Extensions\MethodCallExtension with one or both of the following methods:

use AutoDoc\Analyzer\Scope;
use AutoDoc\DataTypes\Type;
use AutoDoc\Extensions\MethodCallExtension;
use PhpParser\Node\Expr\MethodCall;

class CustomMethodCallExtension extends MethodCallExtension
{
    public function getReturnType(MethodCall $methodCall, Scope $scope): ?Type
    {
        //
    }

    public function getRequestType(MethodCall $methodCall, Scope $scope): ?Type
    {
        //
    }
}

If the getReturnType/getRequestType method returns null, the next extension is processed. If it returns a Type, further extensions are not processed for current method call.

4. Static call extension

To create a static method call extension, create a class extending AutoDoc\Extensions\StaticCallExtension with one or both of the following methods:

use AutoDoc\Analyzer\Scope;
use AutoDoc\DataTypes\Type;
use AutoDoc\Extensions\StaticCallExtension;
use PhpParser\Node\Expr\StaticCall;

class CustomStaticCallExtension extends StaticCallExtension
{
    public function getReturnType(StaticCall $methodCall, Scope $scope): ?Type
    {
        //
    }

    public function getRequestType(StaticCall $methodCall, Scope $scope): ?Type
    {
        //
    }
}

If the getReturnType/getRequestType method returns null, the next extension is processed. If it returns a Type, further extensions are not processed for current static method call.

5. Operation extension

Operation extensions are useful when you want to modify the generated OpenApi schema for a group (or all) of routes. For example, if you want to add API security fields that are checked in a middleware and therefore not determined from analyzed code.

To create an operation extension, create a class extending AutoDoc\Extensions\OperationExtension with a handle method:

use AutoDoc\Analyzer\Scope;
use AutoDoc\DataTypes\ObjectType;
use AutoDoc\DataTypes\StringType;
use AutoDoc\Extensions\OperationExtension;
use AutoDoc\OpenApi\MediaType;
use AutoDoc\OpenApi\Operation;
use AutoDoc\OpenApi\RequestBody;
use AutoDoc\OpenApi\Response;
use AutoDoc\Route;

class CustomOperationExtension extends OperationExtension
{
    public function handle(Operation $operation, Route $route, Scope $scope): ?Operation
    {
        /**
         * An extension that adds `client_id` and `client_secret` parameters to all POST requests.
         */
        if (strtoupper($route->method) === 'POST') {
            $extraRequestParams = [
                'client_id' => new StringType(description: 'Client ID'),
                'client_secret' => new StringType(description: 'Client secret'),
            ];

            $extraRequestParams['client_id']->required = true;
            $extraRequestParams['client_secret']->required = true;

            if (isset($operation->requestBody->content['application/json']->schema['type'])) {
                if ($operation->requestBody->content['application/json']->schema['type'] === 'object') {

                    foreach ($extraRequestParams as $key => $paramType) {
                        $operation->requestBody->content['application/json']->schema['properties'][$key] = $paramType->toSchema();
                    }
                }

            } else {
                $operation->requestBody = new RequestBody(
                    content: [
                        'application/json' => new MediaType((new ObjectType($extraRequestParams))->toSchema()),
                    ],
                );
            }
        }

        /**
         * Add a potential response with HTTP code 418 to all operations
         */
        $operation->responses['418'] ??= new Response([]);

        return $operation;
    }
}

An Operation can be handled by multiple extensions. If the handle method returns Operation instead of null, the Operation is overriden.

Previous
Workspaces