PHP code example of brahmic / clientdto

1. Go to this page and download the library: Download brahmic/clientdto library. Choose the download type require.

2. Extract the ZIP file and open the index.php.

3. Add this code to the index.php.
    
        
<?php
require_once('vendor/autoload.php');

/* Start to develop here. Best regards https://php-download.com/ */

    

brahmic / clientdto example snippets


    // 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');

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;
    }
}

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

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

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();
    }
}


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);
    }
}

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, AbstractRequest $request) {
                $dto->displayName = ucfirst($dto->firstName . ' ' . $dto->lastName);
                
                // Access request parameters for additional logic
                if ($request->

// Simple function handler for all resolved data
$client->addResolvedHandler(function($dto, AbstractRequest $request) {
    // Process any resolved data
    // The $request parameter provides access to the original request context
});

// Type-specific handler with parameter hints
$client->addResolvedHandler(
    function(TelegramResponseDto $dto, AbstractRequest $request) {
        // Modify DTO based on response data
        $dto->processedAt = now();
        
        // Access request properties and methods for conditional logic
        if ($request instanceof SearchTelegramByPhoneRequest) {
            $dto->searchType = 'phone';
            $dto->userName = '6787'; // Example modification
        }
        
        // Use request parameters
        if ($request->debugMode ?? false) {
            $dto->requestTrace = [
                'class' => get_class($request),
                'url' => $request->getFullUrl(),
                'timestamp' => microtime(true)
            ];
        }
    },
    TelegramResponseDto::class
);

use Brahmic\ClientDTO\Contracts\ResolvedHandlerInterface;
use Brahmic\ClientDTO\Requests\AbstractRequest;

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 for conditional processing
            if ($request->debugMode ?? false) {
                $dto->debug = [
                    'request_id' => $request->getTrackingId() ?? uniqid(),
                    'request_class' => get_class($request),
                    'processed_at' => now()->toISOString()
                ];
            }
            
            // Access request-specific properties
            if ($request instanceof GetUserProfileRequest) {
                $dto->

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
            );
    }
}

$client->addResolvedHandler(
    function(TelegramCheckResponseDto $dto, AbstractRequest $request) {
        if ($request instanceof SearchTelegramByPhoneRequest) {
            $dto->searchMethod = 'phone';
            $dto->phoneNumber = $request->phone;
        } elseif ($request instanceof SearchTelegramByUsernameRequest) {
            $dto->searchMethod = 'username';
            $dto->username = $request->username;
        }
    },
    TelegramCheckResponseDto::class
);

$client->addResolvedHandler(
    function(UserDto $dto, AbstractRequest $request) {
        // Use request flags to control data processing
        if ($request->format === 'mobile') {
            $dto->avatar = $this->generateMobileAvatar($dto->avatar);
        }
    },
    UserDto::class
);

$client->addResolvedHandler(function($dto, AbstractRequest $request) {
    if (is_object($dto)) {
        // Add request tracing information
        $dto->_metadata = [
            'request_id' => $request->getRequestId(),
            'execution_time' => $request->getExecutionTime(),
            'cache_hit' => $request->wasCacheHit(),
            'api_version' => $request->getApiVersion()
        ];
    }
});

class DataValidationHandler implements ResolvedHandlerInterface
{
    public function handle(mixed $dto, AbstractRequest $request): void
    {
        if ($dto instanceof ApiResponseDto && $request instanceof CriticalDataRequest) {
            // Apply stricter validation for critical requests
            $this->validateCriticalData($dto);
            $dto->validationLevel = 'critical';
        }
    }
}

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

$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
);

$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()
        ];
    }
});

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;
}

$user = UserProfileDto::from([
    'id' => 123,
    'name' => 'John Doe', 
    'email' => '[email protected]',
    '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' => '[email protected]'
    ],
    'created_at' => [
        'key' => 'created_at',
        'name' => 'Registration Date', 
        'value' => '2023-01-15'
    ],
    'status' => [
        'key' => 'status',
        'name' => 'Profile Status',
        'value' => null  // null values 

#[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]

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;
}

// 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

use Brahmic\ClientDTO\Support\LabelUtility;

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

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

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

// 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();

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;
}

$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' => '[email protected]'
        ]
    ],
    'delivery' => [                    // ← DeliveryDto without labels
        'method' => 'express',
        'address' => '123 Main St'
    ]
]
*/

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,
        ]);
    }
}

class HouseCompleteInfoDto extends Data
{
    /**
     * House basic information (from HouseInfoRequest)
     */
    public ?HouseInfoDto $houseInfoDto;
    
    /**
     * House passport data (from HousePassportRequest)  
     */
    public ?HousePassportDto $housePassportDto;
}

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);
    }
}

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

// 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);
}

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)
    }
}

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
}

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

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