PHPackages                             brahmic/clientdto - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Utility &amp; Helpers](/categories/utility)
4. /
5. brahmic/clientdto

ActiveLibrary[Utility &amp; Helpers](/categories/utility)

brahmic/clientdto
=================

1.0.19(7mo ago)062MITPHPPHP ^8.4

Since Apr 3Pushed 7mo ago1 watchersCompare

[ Source](https://github.com/brahmic/clientdto)[ Packagist](https://packagist.org/packages/brahmic/clientdto)[ Docs](https://github.com/brahmic/clientdto)[ RSS](/packages/brahmic-clientdto/feed)WikiDiscussions master Synced 1mo ago

READMEChangelog (6)Dependencies (7)Versions (30)Used By (0)

ClientDTO
---------

[](#clientdto)

#### What does it do?

[](#what-does-it-do)

- ✅ Integration with external API
- ✅ Error and state handling, always predictable response
- ✅ Request grouping
- ✅ Working with files and archives
- ✅ Support for queries with pagination
- ✅ Additional query attempts with flexible configuration
- ✅ **HTTP Request Caching** - intelligent caching with RAW/DTO modes
- ✅ Simplifies working with API

### Using examples

[](#using-examples)

```
    // In the Controller
    // Get resource and sets the data from the Request to the Children request
    public function (Person $person, Request $request) {
        return $person->children()->set($request)->send();
    }

    // The request data will be automatically filled in from the Request.
    public function (Children $children) {
        return $children->send();
    }
```

```
    // Returns CustomResponse (extended of ClientResponse, implements Illuminate\Contracts\Support\Responsable)
    $customClient->person()->children()->send()

    // Returns null or the received answer as DTO object - MyPageableResultDto in this case.
    $customClient->person()->children()->send()->resolved()

    /** @var FileResponse|null $fileResponse */
    $fileResponse = $customClient->person()->report()->send()->->resolved()

    // Also we can get size, MIME type, set new name, etc.
    // If it is an archive, it will be automatically unpacked.
    $fileResponse->file()
        ->openInBrowser(true)
        ->saveAs('path/someReport');

    $fileResponse->files()->saveTo('path/someReport');
```

### Custom client example

[](#custom-client-example)

```
class CustomClient extends ClientDTO
{

    public function __construct(array $config = [])
    {
        $this
            ->cache(false)
            ->setBaseUrl('https://customapiservice.com/')
            ->setTimeout(60)
            ->requestCache()                    // Enable HTTP request caching
            ->requestCacheRaw()                 // Enable RAW response caching
            ->postIdempotent()                  // Allow POST request caching
            ->requestCacheTtl(3600)             // Cache TTL: 1 hour
            ->requestCacheSize(5 * 1024 * 1024) // Cache size limit: 5MB
            ->onClearCache(function () {
                $this->report()->clearCache();
            })
            ->setDebug(app()->hasDebugModeEnabled());
    }

    public function person(): Person
    {
        return new Person();
    }

    //Primary processing of the response, all further resources along the chain receive the transformed response.
    public static function handle(array $data): mixed
    {
        if (array_key_exists('response', $data)) {
            return DefaultDto::from($data);
        }

        if (array_key_exists('query_type', $data) && array_key_exists('uuid', $data)) {
            return OtherDto::from($data);
        }

        return $data;
    }

    /**
     * @throws \Exception
     */
    public function validation(mixed $data, AbstractRequest $abstractRequest, Response $response): mixed
    {
        if ($data instanceof DefaultDto) {

            $exceptionTitle = $data->statusTitle();

            return match ($data->status) {
                SecondaryStatus::AnswerReceived->value => true,
                SecondaryStatus::WaitingForAResponse->value => throw new AttemptNeededException($exceptionTitle, 202),
                SecondaryStatus::SourceUnavailable->value => throw new AttemptNeededException($exceptionTitle, 523),
                SecondaryStatus::CriticalError->value => throw new Exception($exceptionTitle, 500),
                SecondaryStatus::UnknownError->value, => throw new Exception($exceptionTitle, 520),
                default => throw new \Exception($exceptionTitle, 500)
            };
        }

        if ($abstractRequest instanceof Report) {
            return $data;
        }

        if ($response->getStatusCode() === 200) {
            if ($status = Arr::get($data, 'status')) {
                SecondaryStatus::check($status);
            }

            throw new UnresolvedResponseException("Response received but not recognized", $response);
        }

        throw new Exception("Unknown request. Check the request parameters, the request processing chain, including data processing via the `handle` method.", 500);
    }

    public function beforeExecute(AbstractRequest $request): void
    {
        if ($request instanceof CheckRequest) {
            $this->addQueryParam('token', '123bbd2113da3s3f1xc23c8b4927');
        }
    }

    public function getResponseClass(): string
    {
        return CustomResponse::class;
    }
}
```

### Resource example

[](#resource-example)

```
class Person extends AbstractResource
{
    public const string NAME = 'Person';    //optional

    public function children(): Children
    {
        return new Children();
    }

    public function report(): PersonReport
    {
        return new PersonReport();
    }
}
```

### Request example

[](#request-example)

```
class Children extends GetRequest implements PaginableRequestInterface
{
    use Uuid, Paginable;

    public const string NAME = "The person's children"; //optional

    #[Wrapped(CaseDto::class)]
    protected ?string $dto = MyPageableResultDto::class;

    public const string URI = 'person/{uuid}/children';

    //Optional. This name will be used when sending a request to the remote API instead of "filterText"
    #[MapOutputName('filter_text')]
    //Optional. This information can be extracted later to create a registry of queries created.
    #[Filter(title: 'Search string', description: 'Enter text', note: 'Search by name')]
    public ?string $filterText = null;

    public function set(
        string      $uuid,
        ?int        $page = null,
        ?int        $rows = null,
        ?string     $filterText = null,
    ): static
    {
        return $this->assignSetValues();
    }
}
```

### File Request example

[](#file-request-example)

```
class PersonReport extends GetRequest
{
use Uuid;

    public const string NAME = 'Get report';    //optional

    public const string URI = 'person/{uuid}/report';

    protected ?string $dto = FileResponse::class;

    public Format $event = Format::Pdf;

    public function set(string $uuid, ?Format $event = null): static
    {
        return $this->assignSetValues();
    }

    public function postProcess(FileResponse $fileResponse): void
    {
        $fileResponse->file()->openInBrowser(false)->prependFilename(self::NAME);
    }
}
```

Resolved Data Handlers
----------------------

[](#resolved-data-handlers)

ClientDTO allows you to register handlers that process resolved data after DTO creation. This provides a flexible way to modify or enhance response data before it's returned to the user.

### Basic Usage

[](#basic-usage)

```
class MyClient extends ClientDTO
{
    public function __construct()
    {
        $this->setBaseUrl('https://api.example.com');

        // Handler for all resolved data
        $this->addResolvedHandler(function($dto, $request) {
            if (is_object($dto) && property_exists($dto, 'timestamp')) {
                $dto->timestamp = now();
            }
        });

        // Handler for specific DTO class only
        $this->addResolvedHandler(
            function(UserDto $dto, $request) {
                $dto->displayName = ucfirst($dto->firstName . ' ' . $dto->lastName);

                // Access request parameters for additional logic
                if ($request->includePermissions) {
                    $dto->permissions = $this->loadUserPermissions($dto->id);
                }
            },
            UserDto::class
        );
    }
}
```

### Handler Types

[](#handler-types)

**Function Handlers:**

```
// Simple function handler
$client->addResolvedHandler(function($dto, $request) {
    // Process any resolved data
});

// Type-specific handler with parameter hints
$client->addResolvedHandler(
    function(TelegramResponseDto $dto, AbstractRequest $request) {
        $dto->processedAt = now();

        // Access request properties
        if ($request instanceof SearchByPhoneRequest) {
            $dto->searchType = 'phone';
        }
    },
    TelegramResponseDto::class
);
```

**Class Handlers:**

```
use Brahmic\ClientDTO\Contracts\ResolvedHandlerInterface;

class UserDataEnhancer implements ResolvedHandlerInterface
{
    public function handle(mixed $dto, AbstractRequest $request): void
    {
        if ($dto instanceof UserDto) {
            // Enhance user data
            $dto->avatar = $this->generateAvatarUrl($dto->email);
            $dto->lastSeen = $this->formatLastSeen($dto->lastSeenAt);

            // Use request context
            if ($request->isDebug()) {
                $dto->debug = ['request_id' => $request->getTrackingId()];
            }
        }
    }
}

// Register class handler
$client->addResolvedHandler(new UserDataEnhancer(), UserDto::class);
```

### Caching Behavior

[](#caching-behavior)

Resolved handlers work intelligently with ClientDTO's caching system:

**RAW Cache Mode (`requestCacheRaw(true)`):**

- Handlers execute **every time** data is accessed (even from cache)
- Raw HTTP response is cached, DTO is rebuilt each time
- Handlers always have fresh context

**DTO Cache Mode (default):**

- Handlers execute **once** before caching
- Processed DTO is cached with handler modifications
- Subsequent cache hits return pre-processed data

```
class MyClient extends ClientDTO
{
    public function __construct()
    {
        $this
            ->requestCache()        // Enable caching
            ->requestCacheRaw()     // RAW mode: handlers run each time

            // This handler will run every time in RAW mode
            // or once before caching in DTO mode
            ->addResolvedHandler(
                function(ApiResponseDto $dto) {
                    $dto->processingTime = microtime(true) - $dto->startTime;
                },
                ApiResponseDto::class
            );
    }
}
```

### Handler Parameters

[](#handler-parameters)

All handlers receive two parameters:

ParameterTypeDescription`$dto``mixed`The resolved data (DTO object, string, array, etc.)`$request``AbstractRequest`The original request object with parameters and context### Use Cases

[](#use-cases)

**Data Enhancement:**

```
$client->addResolvedHandler(
    function(ProductDto $dto, $request) {
        $dto->discountedPrice = $dto->price * (1 - $dto->discountPercent / 100);
        $dto->currencySymbol = $this->getCurrencySymbol($dto->currency);
    },
    ProductDto::class
);
```

**Conditional Processing:**

```
$client->addResolvedHandler(
    function(OrderDto $dto, $request) {
        // Only process for admin requests
        if ($request->userRole === 'admin') {
            $dto->internalNotes = $this->loadInternalNotes($dto->id);
            $dto->profitMargin = $dto->revenue - $dto->cost;
        }
    },
    OrderDto::class
);
```

**Debug Information:**

```
$client->addResolvedHandler(function($dto, $request) {
    if ($request->isDebug() && is_object($dto)) {
        $dto->_debug = [
            'request_class' => get_class($request),
            'response_time' => $request->getResponseTime(),
            'cache_hit' => $request->wasCacheHit()
        ];
    }
});
```

Human-Readable Labels for DTO Fields
------------------------------------

[](#human-readable-labels-for-dto-fields)

ClientDTO provides automatic transformation of DTO fields into human-readable labeled format, perfect for frontend display and API responses.

### Basic Usage

[](#basic-usage-1)

Add labels to DTO properties and enable automatic transformation:

```
use Brahmic\ClientDTO\Attributes\Label;
use Brahmic\ClientDTO\Attributes\WithLabels;
use Brahmic\ClientDTO\Support\Data;

#[WithLabels(autoTransform: true)]
class UserProfileDto extends Data
{
    #[Label('User ID')]
    public ?int $id;

    #[Label('Full Name')]
    public ?string $name;

    #[Label('Email Address')]
    public ?string $email;

    #[Label('Registration Date')]
    public ?Carbon $created_at;

    #[Label('Profile Status')]
    public ?string $status;
}
```

### Automatic Transformation

[](#automatic-transformation)

With `#[WithLabels(autoTransform: true)]`, all labeled fields are automatically transformed when converting DTO to array:

```
$user = UserProfileDto::from([
    'id' => 123,
    'name' => 'John Doe',
    'email' => 'john@example.com',
    'created_at' => '2023-01-15',
    'status' => null  // null values are also handled
]);

// Automatic labeled transformation via toArray()
$result = $user->toArray();

/*
Result:
[
    'id' => [
        'key' => 'id',
        'name' => 'User ID',
        'value' => 123
    ],
    'name' => [
        'key' => 'name',
        'name' => 'Full Name',
        'value' => 'John Doe'
    ],
    'email' => [
        'key' => 'email',
        'name' => 'Email Address',
        'value' => 'john@example.com'
    ],
    'created_at' => [
        'key' => 'created_at',
        'name' => 'Registration Date',
        'value' => '2023-01-15'
    ],
    'status' => [
        'key' => 'status',
        'name' => 'Profile Status',
        'value' => null  // null values included with labels
    ]
]
*/
```

### Manual Labeled Access

[](#manual-labeled-access)

Get labeled data without automatic transformation:

```
#[WithLabels(autoTransform: false)]  // Disable auto transformation
class ProductDto extends Data
{
    #[Label('Product Name')]
    public ?string $name;

    #[Label('Price (USD)')]
    public ?float $price;
}

$product = ProductDto::from(['name' => 'iPhone', 'price' => 999.99]);

// Manual labeled access
$labeled = $product->asLabeled();
/*
[
    [
        'key' => 'name',
        'name' => 'Product Name',
        'value' => 'iPhone'
    ],
    [
        'key' => 'price',
        'name' => 'Price (USD)',
        'value' => 999.99
    ]
]
*/

// Regular object access still works
echo $product->name;   // 'iPhone'
echo $product->price;  // 999.99

// Regular toArray() without labels (since autoTransform: false)
$regular = $product->toArray();  // ['name' => 'iPhone', 'price' => 999.99]
```

### Working with Existing Transformers

[](#working-with-existing-transformers)

Label functionality respects existing `#[WithTransformer]` attributes:

```
use Spatie\LaravelData\Attributes\WithTransformer;
use Spatie\LaravelData\Transformers\DateTimeInterfaceTransformer;

#[WithLabels(autoTransform: true)]
class EventDto extends Data
{
    #[Label('Event Name')]
    public ?string $title;

    #[Label('Event Date')]
    #[WithTransformer(DateTimeInterfaceTransformer::class, format: 'Y-m-d H:i')]
    public ?Carbon $event_date;  // Custom transformer takes priority

    #[Label('Participant Count')]
    public ?int $participants;
}
```

### Frontend Integration Examples

[](#frontend-integration-examples)

**Vue.js Template:**

```

    {{ field.name }}:
    {{ field.value || 'N/A' }}

export default {
  data() {
    return {
      userFields: [] // Will be populated from API
    }
  },
  async mounted() {
    const response = await api.getUser(123);
    this.userFields = Object.values(response.data); // Labeled format
  }
}

```

**React Component:**

```
function UserProfile({ userId }) {
  const [userFields, setUserFields] = useState([]);

  useEffect(() => {
    api.getUser(userId).then(response => {
      setUserFields(Object.values(response.data));
    });
  }, [userId]);

  return (

      {userFields.map(field => (

          {field.name}:
          {field.value ?? 'N/A'}

      ))}

  );
}
```

### Performance and Caching

[](#performance-and-caching)

Label functionality includes intelligent caching for optimal performance:

- **Reflection caching** - Class and property metadata cached automatically
- **Production optimization** - Full caching in production environment
- **Development friendly** - Automatic cache clearing in testing
- **Process-scoped** - Cache lives for request lifecycle

```
// First call: Reflection + caching
$user = UserDto::from($data);
$labeled1 = $user->toArray(); // ← Reflection analysis

// Subsequent calls: From cache (instant)
$labeled2 = $user->toArray(); // ← From cache
$labeled3 = $user->toArray(); // ← From cache
```

### Cache Management

[](#cache-management)

For rare edge cases, manual cache clearing is available:

```
use Brahmic\ClientDTO\Support\LabelUtility;

// Clear all label caches (rarely needed)
LabelUtility::clearAllCaches();
```

### Use Cases

[](#use-cases-1)

**API Responses:**

```
// Transform DTO for consistent API responses
return response()->json([
    'user' => $userDto->toArray(),  // Automatic labeled format
    'status' => 'success'
]);
```

**Form Generation:**

```
// Generate dynamic forms from DTO structure
$fields = $userDto->asLabeled();
foreach ($fields as $field) {
    echo "{$field['name']}";
    echo "";
}
```

**Data Tables:**

```
// Create table headers from DTO labels
$users = collect($apiResponse)->map(fn($user) => UserDto::from($user));
$headers = array_keys($users->first()->toArray());
$data = $users->map(fn($user) => $user->toArray())->toArray();
```

### Property-level WithLabels (Composite DTOs)

[](#property-level-withlabels-composite-dtos)

For complex DTOs containing other DTOs, you can control labeling behavior at the property level using `#[WithLabels]` attributes:

```
use Brahmic\ClientDTO\Attributes\WithLabels;

class OrderDto extends Data
{
    #[Label('Order Number')]
    public ?string $number;

    #[Label('Order Date')]
    public ?Carbon $created_at;

    // Force labels ON for this property (overrides CustomerDto class-level setting)
    #[WithLabels(autoTransform: true, withKey: false)]
    public ?CustomerDto $customer;

    // Force labels OFF for this property
    #[WithLabels(autoTransform: false)]
    public ?DeliveryDto $delivery;
}

#[WithLabels(autoTransform: false, withKey: true)]  // Class-level default: no labels
class CustomerDto extends Data
{
    #[Label('Customer Name')]
    public ?string $name;

    #[Label('Customer Email')]
    public ?string $email;
}

class DeliveryDto extends Data
{
    // No labels defined - regular DTO
    public ?string $method;
    public ?string $address;
}
```

**Result:**

```
$order = OrderDto::from([...]);
$result = $order->toArray();

/*
[
    'number' => [
        'name' => 'Order Number',
        'value' => 'ORD-123'
    ],
    'created_at' => [
        'name' => 'Order Date',
        'value' => '2023-01-15'
    ],
    'customer' => [
        'name' => [                    // ← CustomerDto with labels (property-level override)
            'name' => 'Customer Name',
            'value' => 'John Doe'
        ],
        'email' => [
            'name' => 'Customer Email',
            'value' => 'john@example.com'
        ]
    ],
    'delivery' => [                    // ← DeliveryDto without labels
        'method' => 'express',
        'address' => '123 Main St'
    ]
]
*/
```

**Priority System:**

1. **Property-level** `#[WithLabels]` on parent DTO properties (HIGHEST priority)
2. **Class-level** `#[WithLabels]` on child DTO classes
3. **Default** behavior (no labels)

**Key Features:**

- **Hierarchical control:** Parent DTOs control how their child DTOs are transformed
- **Automatic inheritance:** Child DTOs inherit context from their parents
- **Override capability:** Property-level settings always override class-level settings
- **Zero boilerplate:** Just add attributes, no additional code needed

**⚠️ Thread Safety Note:** Property-level WithLabels uses static context and is not thread-safe in async PHP environments (Swoole, ReactPHP).

GroupedRequest - Composite API Calls
------------------------------------

[](#groupedrequest---composite-api-calls)

ClientDTO provides `GroupedRequest` functionality to combine multiple API calls into a single logical operation. This is useful when you need data from multiple endpoints to build a complete picture.

### Basic GroupedRequest

[](#basic-groupedrequest)

```
use Brahmic\ClientDTO\Contracts\GroupedRequest;
use Illuminate\Support\Collection;

class HouseCompleteInfoRequest extends GetRequest implements GroupedRequest
{
    public const string NAME = 'Complete house information';

    // Final DTO that will be returned to user
    protected ?string $dto = HouseCompleteInfoDto::class;

    protected bool $groupedWithKeys = true;

    public ?string $house_id = null;

    public function set(string $house_id): static
    {
        return $this->assignSetValues();
    }

    // Define which requests to execute
    public function getRequestClasses(): Collection
    {
        return collect([
            HouseInfoRequest::class,
            HousePassportRequest::class,
        ]);
    }
}
```

### Composite DTO Structure

[](#composite-dto-structure)

```
class HouseCompleteInfoDto extends Data
{
    /**
     * House basic information (from HouseInfoRequest)
     */
    public ?HouseInfoDto $houseInfoDto;

    /**
     * House passport data (from HousePassportRequest)
     */
    public ?HousePassportDto $housePassportDto;
}
```

### Advanced: Data Processing with handle()

[](#advanced-data-processing-with-handle)

For cases where you need to process the collected data before creating the final DTO, use the `handle()` method:

```
class HouseAnalyticsRequest extends GetRequest implements GroupedRequest
{
    public const string NAME = 'House analytics with processing';

    // Final DTO (what user gets)
    protected ?string $dto = HouseAnalyticsDto::class;

    // Intermediate DTO (for data collection)
    protected ?string $groupedDto = HouseCompleteInfoDto::class;

    protected bool $groupedWithKeys = true;

    public ?string $house_id = null;

    public function set(string $house_id): static
    {
        return $this->assignSetValues();
    }

    public function getRequestClasses(): Collection
    {
        return collect([
            HouseInfoRequest::class,
            HousePassportRequest::class,
        ]);
    }

    /**
     * Process the intermediate DTO before final transformation
     * Returns array for creating HouseAnalyticsDto::from()
     */
    public static function handle(HouseCompleteInfoDto $intermediateDto): array
    {
        // Process the collected data and return array for final DTO creation
        return [
            'condition_score' => static::calculateScore($intermediateDto),
            'investment_rating' => static::calculateRating($intermediateDto),
            'recommendations' => static::generateRecommendations($intermediateDto),
            'analysis_date' => now()->toDateString(),
            'processed_data_count' => 2, // HouseInfo + HousePassport
        ];
    }

    private static function calculateScore(HouseCompleteInfoDto $data): int
    {
        $score = 100;

        if ($data->houseInfoDto?->deterioration > 50) {
            $score -= 30;
        }

        return max(0, $score);
    }
}
```

### Analytics DTO

[](#analytics-dto)

```
class HouseAnalyticsDto extends Data
{
    public ?int $condition_score;
    public ?float $investment_rating;
    public ?array $recommendations;
}
```

### Execution Flow

[](#execution-flow)

**Standard GroupedRequest:**

1. Execute child requests → `HouseInfoDto` + `HousePassportDto`
2. Collect data into composite DTO → `HouseCompleteInfoDto`
3. Return result → `HouseCompleteInfoDto`

**GroupedRequest with handle():**

1. Execute child requests → `HouseInfoDto` + `HousePassportDto`
2. Collect into intermediate DTO → `HouseCompleteInfoDto`
3. **Call handle() method** → Process intermediate data, return array
4. Create final DTO → `HouseAnalyticsDto::from(array)`
5. Return result → `HouseAnalyticsDto`

### Usage Examples

[](#usage-examples)

```
// Standard composite request
$response = $client->houses()
    ->getCompleteInfo()
    ->set('house-guid-123')
    ->send();

if ($response->success()) {
    $data = $response->resolved; // HouseCompleteInfoDto
    echo $data->houseInfoDto->address;
    echo $data->housePassportDto->total_area;
}

// Analytics request with processing
$analytics = $client->houses()
    ->getAnalytics()
    ->set('house-guid-123')
    ->send();

if ($analytics->success()) {
    $result = $analytics->resolved; // HouseAnalyticsDto
    echo "Score: {$result->condition_score}/100";
    print_r($result->recommendations);
}
```

### Key Features

[](#key-features)

- **No URI required** - Virtual request that doesn't hit a single endpoint
- **Automatic parameter passing** - Public properties are passed to child requests
- **Structured results** - `$groupedWithKeys = true` creates properly keyed DTOs
- **Data processing** - Optional `handle()` method for custom data transformation
- **Type safety** - Full IDE support and type hints throughout the process

**⚠️ Performance Note:** GroupedRequest executes multiple HTTP requests. Consider API rate limits and use caching when appropriate.

HTTP Request Caching
--------------------

[](#http-request-caching)

ClientDTO provides intelligent HTTP request caching with support for both RAW and DTO modes.

### Basic Configuration

[](#basic-configuration)

```
class MyClient extends ClientDTO
{
    public function __construct()
    {
        $this
            ->requestCache()                    // Enable HTTP request caching
            ->requestCacheRaw()                 // Enable RAW response caching (optional)
            ->postIdempotent()                  // Allow POST request caching (optional)
            ->requestCacheTtl(3600)             // Cache TTL: 1 hour (optional)
            ->requestCacheSize(5 * 1024 * 1024); // Cache size limit: 5MB (optional)
    }
}
```

### Caching Methods

[](#caching-methods)

MethodDescriptionDefault`requestCache()`Enable HTTP request caching`false``requestCacheRaw()`Cache raw HTTP responses instead of DTOs`false``postIdempotent()`Allow POST requests to be cached`false``requestCacheTtl(int $seconds)`Set cache TTL in seconds`null` (no limit)`requestCacheSize(int $bytes)`Set max cache entry size`1MB`### Caching Modes

[](#caching-modes)

**DTO Caching (default):**

- Caches resolved DTO objects
- Smaller memory footprint
- Faster access to structured data

**RAW Caching:**

- Caches original HTTP response body
- Preserves exact server response
- Useful for debugging or when raw data is needed

### Per-Request Control

[](#per-request-control)

Use the `#[Cacheable]` attribute to control caching for specific requests:

```
use Brahmic\ClientDTO\Attributes\Cacheable;

#[Cacheable(enabled: true, ttl: 7200)]  // Cache for 2 hours
class GetUserRequest extends GetRequest
{
    // This request will be cached regardless of global settings
}

#[Cacheable(enabled: false)]  // Never cache
class CreateUserRequest extends PostRequest
{
    // This request will never be cached
}
```

### Cache Behavior

[](#cache-behavior)

**Default Behavior:**

- GET requests: Cached if `requestCache()` is enabled
- POST requests: Not cached unless `postIdempotent()` is called
- Cache keys include request class, method, URL, and parameters
- RAW and DTO caches are separate (different cache keys)

**Priority Order:**

1. `#[Cacheable]` attribute on request class
2. `postIdempotent()` setting for POST requests
3. Global `requestCache()` setting

### Cache Management

[](#cache-management-1)

```
// Clear all ClientDTO caches
$client->clearRequestCache();

// Access cached response info
$response = $client->users()->get()->send();
if ($response->getMessage() === 'Successful (cached)') {
    // Response came from cache
}
```

###  Health Score

38

—

LowBetter than 85% of packages

Maintenance62

Regular maintenance activity

Popularity8

Limited adoption so far

Community4

Small or concentrated contributor base

Maturity65

Established project with proven stability

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~6 days

Total

29

Last Release

231d ago

Major Versions

0.0.15 → 1.0.02025-06-05

### Community

Maintainers

![](https://www.gravatar.com/avatar/cea8ac4414d8607598f188a099448d83cff4d5aa547bba145c21b6184ca3695d?d=identicon)[brahmic](/maintainers/brahmic)

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/brahmic-clientdto/health.svg)

```
[![Health](https://phpackages.com/badges/brahmic-clientdto/health.svg)](https://phpackages.com/packages/brahmic-clientdto)
```

###  Alternatives

[illuminate/pagination

The Illuminate Pagination package.

10532.5M862](/packages/illuminate-pagination)[illuminate/broadcasting

The Illuminate Broadcasting package.

7126.5M178](/packages/illuminate-broadcasting)[glhd/conveyor-belt

14797.0k](/packages/glhd-conveyor-belt)[illuminate/redis

The Illuminate Redis package.

8314.0M314](/packages/illuminate-redis)[spatie/laravel-pjax

A pjax middleware for Laravel 5

513371.8k11](/packages/spatie-laravel-pjax)[aedart/athenaeum

Athenaeum is a mono repository; a collection of various PHP packages

245.2k](/packages/aedart-athenaeum)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
