Core concepts
Extensions
Every project has unique requirements, and PHP autodoc is designed to be highly extensible, allowing you to customize its behavior to fit your specific needs.
In cases where PHP autodoc’s default type detection — based on native PHP types and PHPDoc comments — isn’t enough, you can define custom logic using extensions.
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_a($phpClass->className, CustomResponse::class, true)) {
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...
if (! is_a($phpClass->className, CustomResponse::class, true)) {
return null;
}
// Return a subtype of `AutoDoc\DataTypes\Type` that matches
// the property type.
if ($propertyName === 'data') {
return new UnionType([
new ArrayType(itemType: new StringType),
new NullType,
]);
}
return null;
}
public function getRequestType(PhpClass $phpClass): ?Type
{
// Filter out the classes targeted by this extension...
if (! is_a($phpClass->className, CustomRequest::class, true)) {
return null;
}
// Return a subtype of `AutoDoc\DataTypes\Type` that matches
// the request body.
return new ObjectType([
'data' => $phpClass->resolveType(),
'token' => new StringType(description: 'API access token'),
]);
}
}
If the getReturnType
/getPropertyType
/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.
6. TypeScript export extension
To perform custom manipulations on classes before they are exported as TypeScript types, create a class extending AutoDoc\Extensions\TypescriptExportExtension
with a handle
method:
use AutoDoc\Analyzer\Scope;
use AutoDoc\DataTypes\Type;
use AutoDoc\Extensions\TypeScriptExportExtension;
use AutoDoc\PhpClass;
use AutoDoc\Route;
class CustomTypeScriptExportExtension extends TypeScriptExportExtension
{
public function handle(PhpClass $phpClass, Type $type): ?Type
{
// Do something with `$type` and return it.
return $type;
}
}
A PhpClass
can be processed by multiple TypeScriptExportExtension
classes. If the handle
method returns a Type
instead of null
, the Type is overriden.