ioncurly / vetmanager-api-gateway
Vetmanager API Gateway
Requires
- php: >=8.1
- ext-mbstring: *
- guzzlehttp/guzzle: ^7.5
- otis22/vetmanager-rest-api: ^0.1.11
- phpdocumentor/reflection-docblock: ^5.3
- symfony/property-access: ^6.3
- symfony/serializer: ^6.3
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.16
- phpunit/phpunit: ^10
- vimeo/psalm: ^5.9
This package is auto-updated.
Last update: 2024-11-20 12:01:10 UTC
README
Помощник для работы с АПИ Ветменеджера. Берет на себя:
- авторизация;
- получение данных и представление их в виде объектов и типизированных свойств этих объектов;
- удобное получения связанных моделей с помощью методов полученных объектов (моделей);
- отправка новых моделей, редактирование, удаление;
- удобство для реализации кеширования: возможность предоставление модели в виде массива, и создание модели из массива;
- унификация работы с моделями вне зависимости от способа получения.
Используется в основе библиотека для составления Query для АПИ-запросов Vetmanager - тут же документация к использованию классов для сложных АПИ-запросов: Builder и PagedQuery
Vetmanager REST API Postman Collection
Официальный сайт Vetmanager.ru
Для чего?
С помощью этой библиотеки удобно получать данные с АПИ Ветменеджера. Данные приходят в виде Active Records. Каждый Active Record связан с одним или с несколькими другими Active Records. Пример кода:
$apiGateway = VetmanagerApiGateway\ApiGateway::fromSubdomainAndApiKey('subDomain', 'apiKey', true); $invoice = $apiGateway->getInvoice()->getById(7); // Получение модели Счета по ID 7 $invoiceDiscount = $invoice->getDiscount(); // Получение скидки из Счета $petBreedTitle = $invoice->getPetBreed()->getTitle(); // Получение названия типа питомца из счета $pet = $invoice->getPet(); // Получение модели Питомца из Счета $petAlias = $pet->getAlias(); // Получение клички Питомца из Счета
Установка
composer require ioncurly/vetmanager-api-gateway
Короткое оглавление
- Начало работы/Конфигурация подключения
- Первоначальное получение объектов
- Создание новых моделей
- Редактирование моделей
- Удаление моделей
- Пример представления данных
- Связанные запросы
- Работа как с первоначальными массивами, кэширование
- Дополнительные особенности
- Немного об устройстве библиотеки
Подробное использование:
Начало работы/Конфигурация подключения
Простыми словами, объект ApiGateway - связующее звено с работой с АПИ Ветменеджера. Больше ничего создавать не нужно будет
- С помощью субдомена и АПИ ключа
$subDomain = 'kras-best'; // субдомен клиники в ветменеджер $apiKey = 'xXdfxfsfsdffsf'; // АПИ ключ к домену в ветменеджер $isProduction = true; // рабочий или тестовый сервер будет использоваться $apiGateway = VetmanagerApiGateway\ApiGateway::fromSubdomainAndApiKey($subDomain, $apiKey, $isProduction);
- С помощью полного пути к серверу и АПИ ключа
$apiGateway = VetmanagerApiGateway\ApiGateway::fromFullUrlAndApiKey('https://xxx', 'apiKey', true);
- С помощью субдомена, имени АПИ-сервиса и АПИ ключа (для специальных внутренних сервисов)
$apiGateway = VetmanagerApiGateway\ApiGateway::fromSubdomainAndServiceNameAndApiKey('subDomain', 'serviceName', 'apiKey', true);
- С помощью полного пути к серверу, имени АПИ-сервиса и АПИ ключа (для специальных внутренних сервисов)
$apiGateway = VetmanagerApiGateway\ApiGateway::fromFullUrlAndServiceNameAndApiKey('https://xxx', 'serviceName', 'apiKey', true);
Первоначальное получение объектов
Вся логика получения Active Record вынесена в соответсвующий Facade. Вот пример получения клиента по ID:
$apiGateway = VetmanagerApiGateway\ApiGateway::fromSubdomainAndApiKey('subDomain', 'apiKey', true); $clientFacade = $apiGateway->getClient(); // выделение фасада в переменную лишь для наглядности $client = $clientFacade->getById(33);
То есть в этой библиотеке для каждого вида Active Record существует свой Facade. В Facade выделена логика работы АПИ-запросов и получения из ответов Active Record. Например, в Facade для Active Record Client содержится метод получения соответствующего Active Record по ID. Так же в Facade содержатся и другие методы получения (в том числе - через более сложные запросы - например, через фильтры).
Существуют Active Records, которые могут быть получены лишь с помощью конкретного АПИ-запроса. Например, MedicalCardByClient можно получить лишь по ID клиента. Недоступные методы получения в соответствующем фасаде просто не существуют
$apiGateway = VetmanagerApiGateway\ApiGateway::fromSubdomainAndApiKey('subDomain', 'apiKey', true); $medicalCards = $apiGateway->getMedicalCardByClient()->getByClientId(33);
Получение объекта по ID
$apiGateway = VetmanagerApiGateway\ApiGateway::fromSubdomainAndApiKey('subDomain', 'apiKey', true); $client = $apiGateway->getClient()->getById(33);
Получение всех объектов
Всегда возвращает массив моделей. Даже когда 1 объект получаем - обращаемся к нему через массив.
$apiGateway = VetmanagerApiGateway\ApiGateway::fromSubdomainAndApiKey('subDomain', 'apiKey', true); $invoices = $apiGateway->getInvoice()->getAll(maxLimitOfReturnedModels: 20); // В параметре можем дописать лимит возвращаемых моделей (иначе 100 по умолчанию) if (!empty($invoices)) { $invoiceDescription = $invoices[0]->getDescription(); }
Получение объекта по Query запросу
Точно так же как и получение всех моделей, Query запросы всегда возвращают массив объектов. То есть даже когда 1 объект возвращается - обращаемся к нему через массив.
Ниже перечислены 3 варианта одного и того же запроса
-
С помощью Query Builder
Ссылка на используемую библиотеку с большим количество примеров использования Builder
$apiGateway = VetmanagerApiGateway\ApiGateway::fromSubdomainAndApiKey('subDomain', 'apiKey', true); $comboManualItems = $apiGateway->getComboManualItem()->getByQueryBuilder( (new Otis22\VetmanagerRestApi\Query\Builder()) ->where('value', '7') ->where('combo_manual_id', '1'), 1 // Опциональный параметр - лимит возвращаемых моделей );
-
С помощью PagedQuery
Ссылка на используемую библиотеку с большим количество примеров использования PagedQuery
С помощью этого объекта удобнее работать с пагинацией.
$apiGateway = VetmanagerApiGateway\ApiGateway::fromSubdomainAndApiKey('subDomain', 'apiKey', true); $comboManualItems = $apiGateway->getComboManualItem()->getByPagedQuery( (new Otis22\VetmanagerRestApi\Query\Builder()) ->where('value', '7') ->where('combo_manual_id', '1') ->top(1) // Лимит возвращаемых моделей );
-
С помощью Get Parameters в виде строки
Сюда можно передать все те же Get-параметры, используемые в коллекции Postman. Более подробно о фильтрах, сортировке и т.д. здесь - Vetmanager REST API Docs
$apiGateway = VetmanagerApiGateway\ApiGateway::fromSubdomainAndApiKey('subDomain', 'apiKey', true); $comboManualItems = $apiGateway->getComboManualItem()->getByGetParametersAsString( "filter=[{'property':'combo_manual_id', 'value':'1'},{'property':'value', 'value':'7'}]&limit=1" );
Альтернативные способы получения для конкретных моделей
use VetmanagerApiGateway\DTO\ComboManualName\NameEnum; $apiGateway = VetmanagerApiGateway\ApiGateway::fromSubdomainAndApiKey('subDomain', 'apiKey', true); $clinicLanguage = $apiGateway->getProperty()->getByClinicIdAndPropertyName($clinicId = 13, $property = 'lang')->getTitle(); $clientMedicalCards = $apiGateway->getMedicalCardByClient()->getByClientId(77); $petVaccinations = $apiGateway->getMedicalCardAsVaccination()->getByPetId(11); $admissionType = $apiGateway->getComboManualItem()->getByAdmissionTypeId(11); $admissionTypeTitle = $admissionType->getTitle(); // Пример использования содержимого модели $result = $apiGateway->getComboManualItem()->getByAdmissionResultId(11); $petColor = $apiGateway->getComboManualItem()->getByPetColorId(11); $vaccineType = $apiGateway->getComboManualItem()->getByVaccineTypeId(11); $admissionResult13 = $apiGateway->getComboManualItem()->getOneByValueAndComboManualName(13, NameEnum::AdmissionResult); $admissionResultManual = $apiGateway->getComboManualName()->getByNameAsString('admission_result'); $admissionResultManualId = $apiGateway->getComboManualName()->getIdByNameAsString('admission_result'); $admissionResultManual = $apiGateway->getComboManualName()->getByNameAsEnum(NameEnum::AdmissionResult); $admissionResultManualId = $apiGateway->getComboManualName()->getIdByNameAsEnum(NameEnum::AdmissionResult); $clientAdmissions = $apiGateway->getAdmission()->getByClientId(40); $petAdmissions = $apiGateway->getAdmission()->getByPetId(88);
Создание новых моделей
С помощью фасада и массива
$apiGateway = VetmanagerApiGateway\ApiGateway::fromSubdomainAndApiKey('subDomain', 'apiKey', true); // Отправляет новый город и возвращает объект (соответствующий Active Record) с созданной моделью от АПИ $newCity = $apiGateway->getCity()->createNewUsingArray(["title" => "New City", "type_id" => "1"]); echo $newCity->getId(); // Получим новый присвоенный ID. Можно и другие свойства так же получить
С помощью Active Record и сеттеров
$apiGateway = VetmanagerApiGateway\ApiGateway::fromSubdomainAndApiKey('subDomain', 'apiKey', true); $newEmptyCity = $apiGateway->getCity()->getNewEmpty(); $cityWithSetValues = $newEmptyCity->setTitle("New City")->setTypeId(1); $newCity = $cityWithSetValues->create(); echo $newCity->getId(); // Получим новый присвоенный ID. Можно и другие свойства так же получить
Редактирование моделей
С помощью фасада и массива
$apiGateway = VetmanagerApiGateway\ApiGateway::fromSubdomainAndApiKey('subDomain', 'apiKey', true); // Отправляет массив данных для изменения модели и возвращает объект (соответствующий Active Record) с созданной моделью от АПИ $updatedCity = $apiGateway->getCity()->updateUsingIdAndArray(13, ["title" => "New City", "type_id" => "1"]); // В $updatedCity будет модель полученная из ответа по АПИ echo $updatedCity->getTitle(); // Получим "New City". Можно и другие свойства так же получить
С помощью Active Record и сеттеров
$apiGateway = VetmanagerApiGateway\ApiGateway::fromSubdomainAndApiKey('subDomain', 'apiKey', true); $city = $apiGateway->getCity()->getById(13); $updatedCity = $city->setTitle("New City")->update(); // Без вызова update - модель не отправится // В $updatedCity будет модель полученная из ответа по АПИ echo $updatedCity->getTitle(); // Получим "New City". Можно и другие свойства так же получить
Удаление моделей
С помощью фасада
$apiGateway = VetmanagerApiGateway\ApiGateway::fromSubdomainAndApiKey('subDomain', 'apiKey', true); $apiGateway->getCity()->delete(13);
С помощью Active Record
$apiGateway = VetmanagerApiGateway\ApiGateway::fromSubdomainAndApiKey('subDomain', 'apiKey', true); $city = $apiGateway->getCity()->getById(13); $city->delete();
Пример представления данных модели
Получать каждое свойство через гет-метод. Типизация в каждом методе есть
$apiGateway = VetmanagerApiGateway\ApiGateway::fromSubdomainAndApiKey('subDomain', 'apiKey', true); $client = $apiGateway->getClient()->getById(13); $clientEmail = $client->getEmail(); // Объявлено, что только строка, возможно пустая $clientCityId = $client->getCityId(); // Объявлено, что только int или null может прийти $clientDateRegister = $client->getDateRegisterAsDateTime()?->format('Y-m-d H:i:s'); //dateRegister содержит DateTime (или null, если отсутствует дата) $clientName = $client->getFullName()->getInitials(); // FullName - вспомогательный объект для удобного форматирования имени $clientStatus = $client->getStatusAsEnum(); // Возвращается одно из возможных значений соответствующего Enum
Пример связанных запросов
Есть свойства объекта, которые вместо скалярных данных, возвращают другие объекты или массив объектов.
$apiGateway = VetmanagerApiGateway\ApiGateway::fromSubdomainAndApiKey('subDomain', 'apiKey', true); $client = $apiGateway->getClient()->getById(13); $clientStreet = $client->getStreet(); // В переменной будет null или Модель Улицы $streetName = $clientStreet?->getTitle() ?? ''; // Название улицы или пустая строка $clientPets = $client->getPetsAlive(); // Массив с Моделями Питомцев $firstPet = (!empty($client->petsAlive)) ? $clientPets[0] : null; // Будет Питомец или null $firstPetName = $firstPet?->getColor()?->getTitle() : ''; // Получение названия цвета питомца или пустая строка, если нет
Пример обращения посложнее:
$apiGateway = VetmanagerApiGateway\ApiGateway::fromSubdomainAndApiKey('subDomain', 'apiKey', true)->getClient()->getById(13)->getMedicalCards(); $firstMedicalCardOfClient = !empty($clientMedicalCards) ? $clientMedicalCards[0] : null; $middleNameOfFirstMedicalCardDoctor = $firstMedicalCardOfClient?->getUser()?->getMiddleName();
Та же запись, но с дополнительными переменными для понимания:
$apiGateway = VetmanagerApiGateway\ApiGateway::fromSubdomainAndApiKey('subDomain', 'apiKey', true); // Получение объекта ApiGateway $clientFacade = $apiGateway->getClient(); // Получение фасада для моделей Клиента с разными методами для работы с ними $client = $clientFacade->getById(13); // Получение через АПИ-запрос используя ID модель Клиента (1ый АПИ запрос) $clientMedicalCards = $client->getMedicalCards(); // Получение всех карт Клиента (2ой АПИ запрос) $firstMedicalCardOfClient = !empty($clientMedicalCards) ? $clientMedicalCards[0] : null; // Получим первую карту Клиента или null, если нет карт $firstMedicalCardDoctor = $firstMedicalCardOfClient?->getUser(); // Получение модели Доктора из медицинской карты (3ий запрос) $middleNameOfFirstMedicalCardDoctor = $firstMedicalCardDoctor?->getMiddleName(); // Получение отчества доктора из первой Карты Клиента
Нормализация объектов (приведение в массив)
С помощью этих методов несложно реализовать кеширование модели.
Каждый раз, когда нужно сделать повторяющийся одинаковый запрос:
1 - Попытаться получить уже закешированный объект в виде массива.
-
Если нашелся кеш:
2 - создать объект из этого кеша.
-
Если не нашелся кеш:
2 - сделать АПИ запрос на получение объекта
3 - в кеш закинуть модель в виде массива для следующей PHP сессии
Получение модели в виде массива
Возвращаются данные в том же виде, в котором и были получены по АПИ
$apiGateway = VetmanagerApiGateway\ApiGateway::fromSubdomainAndApiKey('subDomain', 'apiKey', true); $clientAsArray = $apiGateway->getClient()->getById(13)->getAsArray(); // Получим массив модели вида: ["id" => "1", "address" => "", "home_phone" => "3322122", ... ]
Создание объекта из данных в виде массива
- Получение одного объекта из закешированного массива модели (в виде ['id' => '12', '...' => ...]).
$apiGateway = VetmanagerApiGateway\ApiGateway::fromSubdomainAndApiKey('subDomain', 'apiKey', true); $client = $apiGateway->getClient()->fromSingleModelAsArray($cachedClientAsArray);
- Получение массива объектов из массива таких массивов с моделямм
$apiGateway = VetmanagerApiGateway\ApiGateway::fromSubdomainAndApiKey('subDomain', 'apiKey', true); $clients = $apiGateway->getClient()->fromMultipleModelsAsArrays($cachedClientAsArray); // Дальше продолжаем использовать будто получили массив моделей как обычно $firstClientId = $clients[0]->getId();
Дополнительные особенности
Вспомогательный объект FullName
Например, у моделей User и Client есть геттер для FullName. У объекта FullName есть методы возвращающие полное имя в разном формате:
$clientFullName = VetmanagerApiGateway\ApiGateway::fromSubdomainAndApiKey('subDomain', 'apiKey', true)->getClient()->getById(9)->getFullName(); echo $clientFullName->getFullStartingWithFirst();// Возвращает: "Имя Отчество Фамилия" echo $clientFullName->getFullStartingWithLast(); // Возвращает: "Фамилия Имя Отчество" echo $clientFullName->getLastPlusInitials(); // Возвращает: "Фамилия И. О." echo $clientFullName->getInitials(); // Возвращает: "Ф. И. О."
Если, предположим, отчества не будет, то каждый из методов просто пропустит слово без создания лишних пробелов, точек и т.д.
Вспомогательный объект FullPhone
Например, у модели Клиники есть метод возвращающий FullPhone. У FullPhone есть метод возвращающий телефон с кодом страны и выбранной маской номера, например в виде: +7(918)-277-21-21
$clinicFullPhone = VetmanagerApiGateway\ApiGateway::fromSubdomainAndApiKey('subDomain', 'apiKey', true)->getClinic()->getById(9)->getFullPhone(); echo $clinicFullPhone->getAsMaskedWithCountryCode(); // Выведет телефона в виде +7(918)-277-21-21 echo $clinicFullPhone; // То же самое, что и прошлая строка - _toString() вызывает тот же метод echo $clinicFullPhone->mask; // Подобные маски могут вернуться: '(___)-__-__-__', '(__)___-____' или '____-____' echo $clinicFullPhone->countryCode; // +7 или +38 и т.д.
Узнать возможность онлайн записи в клинике
Несколько вариантов. Возвращается bool:
$apiGateway = VetmanagerApiGateway\ApiGateway::fromSubdomainAndApiKey('subDomain', 'apiKey', true) $bool1 = $apiGateway->getProperty()->getIsOnlineSigningUpAvailableForClinic(13); // Один АПИ-запрос $bool2 = $apiGateway->getClinic()->getIsOnlineSigningUpAvailable(13); // Один АПИ-запрос $bool3 = $apiGateway->getClinic()->getById(13)->getIsOnlineSigningUpAvailable(); // Два АПИ-запроса
Немного об устройстве библиотеки
Используемые библиотеки
-
Symfony Serializer - Для создания DTO из массива данных (десериализованный JSON) и обратного процесса ( нормализации)
-
Guzzle HTTP client - Для всех используемых запросов
-
vetmanager-rest-api - Помощник с Query-запросами АПИ Ветменджера
Только при разработке:
-
PSALM
-
PHPUnit
Внутренние особенности
Для большинства моделей АПИ Ветменджер кроме запрашиваемой модели отдает и содержимое других связанных моделей. Это также зависит от вида запроса - например, при запросе по ID обычно приходит наибольшее число связанных моделей. Для пользователя этой библиотеки это скрыто: в любом случае вне зависимости от какой вида Active Record получен - все методы и связи с другими моделями доступны. А как именно при вызове метода будут получаться данные: из уже полученных данных или при помощи дополнительного запроса - это тоже берет на себя библиотека.
Вот пример для понимания устройства. Но для использования это вовсе неважно. В использовании каждый из полученных Active Record идентичен (так как обладают одним и тем же набором доступных методов, которые просто реализованы по-разному):
$apiGateway = VetmanagerApiGateway\ApiGateway::fromSubdomainAndApiKey('subDomain', 'apiKey', true); /** @var VetmanagerApiGateway\Facade\Client $clientFacade Содержит методы связанные с моделью для осуществления АПИ-запросов и созданию Active Records */ $clientFacade = $apiGateway->getClient(); /** @var VetmanagerApiGateway\ActiveRecord\Client\ClientOnly[] $clients Содержит в себе DTO Client и связи со связанным Active Records*/ $clients = $clientFacade->getAll(); /** @var VetmanagerApiGateway\ActiveRecord\Client\ClientPlusTypeAndCity $client Содержит в себе DTO для моделей Client, Client Type, City */ $client = $clientFacade->getById(33);
Разработка
- Копируем файл окружения из примера
cp .env.example .env
- Заполняем
.env
- Устанавливаем библиотеки
docker compose run php-fpm composer install
- Запуск юнит-тестов
docker compose run php-fpm composer test-unit
Можно запускать другие команды, примеры можно посмотреть в composer.json
в разделе scripts
. Например:
docker compose run php-fpm composer style-check