From 29015cf363e29317cef893637f7704cbd56d36cd Mon Sep 17 00:00:00 2001 From: Mohammed Elhaouari Date: Sun, 22 Feb 2026 01:04:07 +0100 Subject: [PATCH] Add more data objects --- composer.json | 5 ++- src/Api/Dto/Contact.php | 26 ++++++++------- src/Api/GetContactsData.php | 56 +++++++++++++++++++------------- src/helpers.php | 31 ++++++++++++++++++ tests/helpersTest.php | 64 +++++++++++++++++++++++++++++++++++++ 5 files changed, 147 insertions(+), 35 deletions(-) create mode 100644 src/helpers.php create mode 100644 tests/helpersTest.php diff --git a/composer.json b/composer.json index 7a97358..5afe3a9 100644 --- a/composer.json +++ b/composer.json @@ -35,7 +35,10 @@ "autoload": { "psr-4": { "Wati\\": "src/" - } + }, + "files": [ + "src/helpers.php" + ] }, "autoload-dev": { "psr-4": { diff --git a/src/Api/Dto/Contact.php b/src/Api/Dto/Contact.php index 6e3df96..204920f 100644 --- a/src/Api/Dto/Contact.php +++ b/src/Api/Dto/Contact.php @@ -4,19 +4,18 @@ namespace Wati\Api\Dto; -/** - * Contact DTO for nested contact data in API responses. - * - * This is a reference implementation for future nested DTO classes. - * Nested DTOs are immutable readonly classes used within response DTOs. - */ final readonly class Contact { public function __construct( public string $id, - public string $name, public string $phone, + public string $fullName, + public ?string $wAid = null, + public ?string $firstName = null, public ?string $email = null, + public ?string $contactStatus = null, + public ?string $created = null, + public ?string $lastUpdated = null, ) {} /** @@ -25,10 +24,15 @@ public function __construct( public static function fromArray(array $data): self { return new self( - id: is_string($data['id'] ?? null) ? $data['id'] : '', - name: is_string($data['name'] ?? null) ? $data['name'] : '', - phone: is_string($data['phone'] ?? null) ? $data['phone'] : '', - email: is_string($data['email'] ?? null) ? $data['email'] : null, + id: data_get($data, 'id', ''), + phone: data_get($data, 'phone', ''), + fullName: data_get($data, 'fullName', ''), + wAid: is_string($v = data_get($data, 'wAid')) ? $v : null, + firstName: is_string($v = data_get($data, 'firstName')) ? $v : null, + email: is_string($v = data_get($data, 'email')) ? $v : null, + contactStatus: is_string($v = data_get($data, 'contactStatus')) ? $v : null, + created: is_string($v = data_get($data, 'created')) ? $v : null, + lastUpdated: is_string($v = data_get($data, 'lastUpdated')) ? $v : null, ); } } diff --git a/src/Api/GetContactsData.php b/src/Api/GetContactsData.php index 4a39b56..c97a03a 100644 --- a/src/Api/GetContactsData.php +++ b/src/Api/GetContactsData.php @@ -7,46 +7,56 @@ use Psr\Http\Message\ResponseInterface; use Wati\Api\Dto\Contact; -/** - * Response DTO for GetContacts API. - * - * This is a reference implementation for future DTO classes. - * DTOs are immutable readonly classes that provide typed access to API responses. - * - * @example - * ```php - * $response = $client->send(new GetContacts()); - * $data = GetContactsData::fromResponse($response); - * // or - * $data = new GetContactsData($response); - * - * echo $data->totalContacts; - * foreach ($data->contacts as $contact) { - * echo $contact->name; - * } - * ``` - */ final readonly class GetContactsData { /** * @param array $contacts */ public function __construct( - public int $totalContacts, + public string $result, + public int $total, + public int $pageNumber, + public int $pageSize, + public ?string $prevPage, + public ?string $nextPage, public array $contacts, ) {} public static function fromResponse(ResponseInterface $response): self { - /** @var array{totalContacts?: int, contacts?: array>} $data */ + /** + * @var array{ + * result?: string, + * contact_list?: array>, + * link?: array{ + * prevPage?: string|null, + * nextPage?: string|null, + * pageNumber?: int, + * pageSize?: int, + * total?: int + * } + * } $data + */ $data = json_decode($response->getBody()->getContents(), true) ?? []; + $link = $data['link'] ?? []; + return new self( - totalContacts: $data['totalContacts'] ?? 0, + result: is_string($data['result'] ?? null) ? $data['result'] : '', + total: (int) ($link['total'] ?? 0), + pageNumber: (int) ($link['pageNumber'] ?? 1), + pageSize: (int) ($link['pageSize'] ?? 50), + prevPage: is_string($link['prevPage'] ?? null) ? $link['prevPage'] : null, + nextPage: is_string($link['nextPage'] ?? null) ? $link['nextPage'] : null, contacts: array_map( Contact::fromArray(...), - $data['contacts'] ?? [] + $data['contact_list'] ?? [] ), ); } + + public function hasMore(): bool + { + return $this->nextPage !== null; + } } diff --git a/src/helpers.php b/src/helpers.php new file mode 100644 index 0000000..985fef4 --- /dev/null +++ b/src/helpers.php @@ -0,0 +1,31 @@ + $data + * @param TDefault $default + * @return string|TDefault + */ +function data_get(array $data, string $key, mixed $default = null): mixed +{ + if (! array_key_exists($key, $data)) { + return $default; + } + + $value = $data[$key]; + + if ($value === null) { + return $default; + } + + if (is_string($value)) { + $value = trim($value); + + return $value !== '' ? $value : $default; + } + + return $value; +} diff --git a/tests/helpersTest.php b/tests/helpersTest.php new file mode 100644 index 0000000..b03a4ca --- /dev/null +++ b/tests/helpersTest.php @@ -0,0 +1,64 @@ + 'John', 'email' => 'john@example.com']; + + expect(data_get($data, 'name'))->toBe('John') + ->and(data_get($data, 'email'))->toBe('john@example.com'); +}); + +it('returns default when key does not exist', function (): void { + $data = ['name' => 'John']; + + expect(data_get($data, 'email'))->toBeNull() + ->and(data_get($data, 'email', 'default'))->toBe('default'); +}); + +it('returns default when value is null', function (): void { + $data = ['name' => null]; + + expect(data_get($data, 'name'))->toBeNull() + ->and(data_get($data, 'name', 'default'))->toBe('default'); +}); + +it('returns default when value is empty string', function (): void { + $data = ['name' => '']; + + expect(data_get($data, 'name'))->toBeNull() + ->and(data_get($data, 'name', 'default'))->toBe('default'); +}); + +it('trims string values', function (): void { + $data = ['name' => ' John ']; + + expect(data_get($data, 'name'))->toBe('John'); +}); + +it('returns default when trimmed value is empty', function (): void { + $data = ['name' => ' ']; + + expect(data_get($data, 'name'))->toBeNull() + ->and(data_get($data, 'name', 'default'))->toBe('default'); +}); + +it('returns non-string values as-is', function (): void { + $data = [ + 'count' => 42, + 'active' => true, + 'items' => ['a', 'b'], + 'price' => 19.99, + ]; + + expect(data_get($data, 'count'))->toBe(42) + ->and(data_get($data, 'active'))->toBeTrue() + ->and(data_get($data, 'items'))->toBe(['a', 'b']) + ->and(data_get($data, 'price'))->toBe(19.99); +}); + +it('returns default for missing nested keys', function (): void { + $data = ['user' => ['name' => 'John']]; + + expect(data_get($data, 'email', 'missing'))->toBe('missing'); +});