Documentation Index
Fetch the complete documentation index at: https://docs.apivalk.com/llms.txt
Use this file to discover all available pages before exploring further.
The Concept
Reusable response objects are implemented as subclasses of AbstractObjectProperty. They define a fixed set of properties that can be included in multiple ApivalkResponseDocumentation definitions.
Built-in Examples
ValidationErrorObject
This object is used to represent a single validation error or a generic error message.
Structure:
parameter (string): The name of the field that caused the error.
message (string): The human-readable error message.
errorKey (string): The machine-readable error code.
Usage in Controller:
public static function getResponseDocumentation(): ApivalkResponseDocumentation
{
$doc = new ApivalkResponseDocumentation();
$doc->addProperty(new ValidationErrorObject());
return $doc;
}
Creating Your Own
You can create custom reusable objects by following these steps:
- Create a Property Collection: Extend
AbstractPropertyCollection and add your properties.
- Create the Object: Extend
AbstractObjectProperty and return your collection in getPropertyCollection().
Example: UserObject
This example shows an object schema used for documentation and, optionally, as a runtime data container.
- The constructor must call
parent::__construct($name, $description).
- Do not hydrate runtime values in the constructor. The documentation needs to be built independently of real values.
- Populate the object only when you actually need an instantiated object at runtime, for example in
populate(), a builder, or from a DTO.
class UserPropertyCollection extends AbstractPropertyCollection
{
public function __construct(string $mode)
{
$this->addProperty(new StringProperty('id', 'User UUID'));
$this->addProperty(new StringProperty('email', 'User email address'));
}
}
class UserObject extends AbstractObjectProperty
{
private string $id;
private string $email;
/**
* Defines the object's schema name and description.
*
* Important:
* - Always required.
* - Do not hydrate runtime values here.
* - Documentation is built without needing a populated instance.
*/
public function __construct()
{
parent::__construct('user', 'User details');
}
public function getPropertyCollection(): AbstractPropertyCollection
{
return new UserPropertyCollection(AbstractPropertyCollection::MODE_VIEW);
}
/**
* Optional runtime population.
*
* Populate the object only when you want to use the instance as an
* actual data container at runtime.
*/
public function populate(string $id, string $email): void
{
$this->id = $id;
$this->email = $email;
}
/** Populated response data. This is called when you use the object as a response object in response documentation - and that is converted to array. */
public function toArray(): array
{
return [
'id' => $this->id,
'email' => $this->email,
];
}
}
Usage:
/** Your response class */
class UserListResponse extends AbstractApivalkResponse
{
/** @var UserObject[] */
private $users;
public function __construct(array $users = [])
{
$this->users = $users;
}
public static function getDocumentation(): ApivalkResponseDocumentation
{
$responseDocumentation = new ApivalkResponseDocumentation();
$responseDocumentation->setDescription('User list');
$responseDocumentation->addProperty(
new ArrayProperty(
'users',
'List of users',
new UserObject()
)
);
return $responseDocumentation;
}
public static function getStatusCode(): int
{
return self::HTTP_200_OK;
}
public function toArray(): array
{
$usersArray = [];
foreach ($this->users as $error) {
$usersArray[] = $error->toArray();
}
return ['users' => $usersArray];
}
}
/** And somewhere in the controller */
$userObject = new UserObject();
$userObject->populate('0f3b6c7f-7b1b-4f1f-9d0a-1b2c3d4e5f60', 'test@test.test');
return new UserListResponse([$userObject]);
Alternative population strategies
You can populate the object however you prefer, for example:
- from a database record
- via setters/getters
- with a fluent builder
- by mapping a DTO
The key rule is: constructor is for schema identity (name, description), not for data hydration.
Benefits
- Consistency: All endpoints returning a “User” will have the exact same structure.
- Maintenance: If you add a field to the
UserObject, it is automatically updated in the OpenAPI documentation for all endpoints that use it.
- DRY: You don’t have to redefine the same properties repeatedly.