> ## 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.

# Response Objects

> To maintain consistency across your API, Apivalk encourages the use of reusable response objects for common data structures like errors, pagination envelopes, or standard resource representations.

## 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:**

```php theme={null}
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:

1. **Create a Property Collection**: Extend `AbstractPropertyCollection` and add your properties.
2. **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.

```php theme={null}
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:

```php theme={null}
/** 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.
