ioncurly/vetmanager-api-gateway

兽医管理系统 API 网关

2.4.1 2024-06-20 11:11 UTC

README

Vetmanager Logo

兽医管理系统 API 工具。负责以下功能:

  1. 认证;
  2. 获取数据并以对象和对象的类型属性的形式展示;
  3. 方便通过获取到的对象(模型)的方法获取相关模型;
  4. 发送新模型、编辑、删除;
  5. 实现缓存的便利性:可以以数组的形式提供模型,并从数组创建模型;
  6. 统一不同获取方式下对模型的工作方式。

该库基于 Vetmanager API 请求的 Query 构建库 - 这里还有复杂 API 请求的类(Builder 和 PagedQuery)的使用文档

兽医管理系统 REST API 文档

兽医管理系统 REST API Postman 集合

兽医管理系统官网 Vetmanager.ru

为什么使用?

通过这个库,可以方便地从兽医管理系统 API 获取数据。数据以 Active Record 的形式到来。每个 Active Record 都与一个或多个其他 Active Record 相关联。代码示例

$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 对象是与兽医管理系统 API 交互的桥梁。不需要再创建其他东西

  1. 使用子域名和 API 密钥
    $subDomain = 'kras-best';   // субдомен клиники в ветменеджер
    $apiKey = 'xXdfxfsfsdffsf'; // АПИ ключ к домену в ветменеджер
    $isProduction = true;       // рабочий или тестовый сервер будет использоваться
    $apiGateway = VetmanagerApiGateway\ApiGateway::fromSubdomainAndApiKey($subDomain, $apiKey, $isProduction);
  2. 使用服务器完整路径和 API 密钥
    $apiGateway = VetmanagerApiGateway\ApiGateway::fromFullUrlAndApiKey('https://xxx', 'apiKey', true);
  3. 使用子域名、API 服务名称和 API 密钥(用于特殊内部服务)
    $apiGateway = VetmanagerApiGateway\ApiGateway::fromSubdomainAndServiceNameAndApiKey('subDomain', 'serviceName', 'apiKey', true);
  4. 使用服务器完整路径、API 服务名称和 API 密钥(用于特殊内部服务)
    $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 中,包含了 API 请求的逻辑和从响应中获取 Active Record 的逻辑。例如,在 Client 的 Facade 中,包含了通过 ID 获取相应 Active Record 的方法。同样,在 Facade 中还包含了其他获取方法(包括通过更复杂的查询 - 例如,通过过滤)。

有些 Active Records 只能通过特定的 API 请求获取。例如,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); 

获取所有对象

总是返回模型数组。即使只获取一个对象,也要通过数组来访问。

$apiGateway = VetmanagerApiGateway\ApiGateway::fromSubdomainAndApiKey('subDomain', 'apiKey', true);
$invoices = $apiGateway->getInvoice()->getAll(maxLimitOfReturnedModels: 20); // В параметре можем дописать лимит возвращаемых моделей (иначе 100 по умолчанию)
if (!empty($invoices)) {
    $invoiceDescription = $invoices[0]->getDescription();
}

通过查询获取对象

与获取所有模型一样,查询请求总是返回对象数组。也就是说,即使返回一个对象,也要通过数组来访问。

以下是同一查询的3种变体

  1. 使用 查询构建器

    链接到包含大量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 // Опциональный параметр - лимит возвращаемых моделей
    );
  2. 使用 分页查询

    链接到包含大量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) // Лимит возвращаемых моделей
    );
  3. 以字符串形式使用 获取参数

    可以传递所有与Postman集合中使用的相同Get参数。有关过滤器、排序等更详细的信息,请参阅 - Vetmanager REST API文档

    $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和setter

$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和setter

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

模型数据表示示例

通过get方法获取每个属性。每个方法都有类型化

$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 - 执行API请求以获取对象

    3 - 将模型作为数组放入缓存以供下一个PHP会话使用

获取模型数组

返回的数据与通过API获得的数据相同

$apiGateway = VetmanagerApiGateway\ApiGateway::fromSubdomainAndApiKey('subDomain', 'apiKey', true);
$clientAsArray = $apiGateway->getClient()->getById(13)->getAsArray();
// Получим массив модели вида: ["id" => "1", "address" => "", "home_phone" => "3322122", ... ]

从数组数据创建对象

  1. 从缓存数组模型(例如 ['id' => '12', '...' => ...])中获取一个对象。
    $apiGateway = VetmanagerApiGateway\ApiGateway::fromSubdomainAndApiKey('subDomain', 'apiKey', true);
    $client = $apiGateway->getClient()->fromSingleModelAsArray($cachedClientAsArray);
  2. 从这样的数组数组中获取对象数组
    $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 序列化器 - 用于从数组数据(反序列化的 JSON)创建 DTO 及其反向过程(规范化)

  • Guzzle HTTP 客户端 - 用于所有使用的请求

  • vetmanager-rest-api - 用于查询 Vetmanager API 的助手

    仅限开发时使用

  • PSALM

  • PHPUnit

内部特性

对于 Vetmanager API 的大多数模型,除了请求的模型外,还会返回其他相关联的模型的内容。这也取决于请求的类型 - 例如,在按 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); 

开发

  1. 从示例中复制环境文件
    cp .env.example .env
  2. 填写 .env
  3. 安装库
    docker compose run php-fpm composer install
  4. 运行单元测试
    docker compose run php-fpm composer test-unit

可以运行其他命令,示例可以在 composer.jsonscripts 部分查看。例如

docker compose run php-fpm composer style-check