Помощник для работы с АПИ Ветменеджера. Берет на себя:
- авторизация;
- получение данных и представление их в виде объектов и типизированных свойств этих объектов;
- удобное получения связанных моделей с помощью методов полученных объектов (моделей);
- отправка новых моделей, редактирование, удаление;
- удобство для реализации кеширования: возможность предоставление модели в виде массива, и создание модели из массива;
- унификация работы с моделями вне зависимости от способа получения.
Используется в основе библиотека для составления 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);
$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 запросы всегда возвращают массив объектов. То есть даже когда 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. Можно и другие свойства так же получить
$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". Можно и другие свойства так же получить
$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);
$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();
Например, у моделей 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 есть метод возвращающий телефон с кодом страны и выбранной маской номера, например в виде: +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