andy87/knockknock

PHP Facade Curl library - developed by and_y87

1.3.0 2024-06-09 16:53 UTC

This package is auto-updated.

Last update: 2024-09-28 07:02:25 UTC


README

KnockKnock php curl facade

KnockKnock - это простая библиотека, реализующая Фасад и предоставляющая удобный интерфейс для выполнения запросов в PHP, используя расширение cURL. Она упрощает работу, предоставляя более высокоуровневый API и быстрый доступ к настройкам.

Цель: сделать простой и лёгкий в настройке компонента и запроса пакет.

P.S. я знаю про существование таких библиотек как: Guzzle, Client (в моём любимом Yii2), но хотелось попробовать создать свою реализацию.
Без "лишних" данных, вызовов и настроек, nullWarningStyle - только то, что нужно: сухо, лаконично, минималистично.
Разумеется, это не конкурент, а просто попытка создать что-то своё

Содержание:

Установка

Требования

  • php 8.0
  • ext cURL
  • ext JSON

Composer

Добавление пакета в проект

Используя: консольные команды. (Предпочтительней)

  • при composer, установленном локально:
composer require andy87/knockknock
  • при использовании composer.phar:
php composer.phar require andy87/knockknock

Далее: обновление зависимостей composer update

Используя: файл `composer.json`

Открыть файл composer.json
В раздел, ключ require добавить строку
"andy87/knockknock": "*"
Далее: обновление зависимостей composer update

Используя: подключение авто загрузчика

В месте, где необходимо использовать библиотеку, подключите авто загрузчик:

require_once 'путь/к/корню/проекта/autoload.php';

Примечания:

  • Убедитесь, что путь к autoload.php правильный и соответствует структуре вашего проекта.

- - - - -

Логика работы библиотеки (блок-схема)

логика схемы работы приложения

Простой пример работы.

use andy87\knock_knock\lib\Method;
use andy87\knock_knock\lib\ContentType;
use andy87\knock_knock\core\Operator;
use andy87\knock_knock\core\Response;

// Получаем компонент реализующий отправку запросов
$operator = new Operator( $_ENV['API_HOST'] )->disableSSL();  

/**
 * Краткая форма записи (с не очевидным объектом запроса) 
 */
$content = $operator->send( $operator->constructRequest(Method::GET, 'info/me') )->content;

/** 
 * Детальная форма записи с дополнительными возможностями
 */
$request = $operator->constructRequest(Method::GET, 'info/me'); // Создаём объект запроса
$request->setCurlInfo([ CURLINFO_CONTENT_TYPE ]); // Назначаем опции cURL
$response = $operator->send($request); // Отправляем запрос и получаем ответ

$content = $response->content; // Получаем данные ответа
$curlOptions = $response->request->curlOptions; // Получаем опции cURL

$output = ( $curlOptions[CURLINFO_CONTENT_TYPE] === ContentType::JSON ) ? json_decode( $content ) : $content;

print_r( $output );

- - - - -

Базовый класс

use andy87\knock_knock\core\Operator;

PHP Фасад\Адаптер для отправки запросов через ext cURL

ReadOnly свойства:

  • commonRequest
    • Объект содержащий параметры, назначаемые всем исходящим запросам
  • realRequest
    • Используемый запрос
  • eventHandlers
    • Список обработчиков событий
  • host
    • Хост, на который будет отправляться запросы
  • logs
    • Список логов

Возможности/фичи:

  • Настройки параметров запросов
  • Защита данных от перезаписи
  • Обработчики событий
  • Инкапсуляция
  • Singleton
  • логирование

ВАЖНЫЙ МОМЕНТ!

  • В классах применяется инкапсуляция, поэтому для доступа к свойствам компонентов используются ReadOnly свойства.
  • CURL_OPTIONS по умолчанию пустые! В большинстве случаев, для получения валидных ответов, требуется задать необходимые настройки.

"Получение" объекта/экземпляра класса

Передавая параметры напрямую в конструктор:

$operator = new Operator( $_ENV['API_HOST'], $commonRequestParams );

Применяя, паттерн Singleton:

$operator = Operator::getInstance( $_ENV['API_HOST'], $commonRequestParams );

Методы возвращают объект(экземпляр класса Operator), принимая на вход два аргумента:

  • string $host - хост
  • array $operatorConfig - массив с настройками для всех исходящих запросов.

При создании объекта Operator будет вызван метод init(), который запускает пользовательские инструкции.
После выполнения init() запускается обработчик события привязанный к ключу EVENT_AFTER_CONSTRUCT

Общие настройки запросов

Что бы указать настройки применяемые ко всем исходящим запросам, при создании объекта `Operator` передаётся массив (ключ - значение), с необходимыми настройками.

Пример настройки:

// настройки для последующих исходящих запросов
$commonRequestParams = [
    Request::SETUP_PROTOCO => $_ENV['API_PROTOCOL'],
    Request::SETUP_CONTENT_TYPE => Request::CONTENT_TYPE_JSON,
    Request::SETUP_CURL_OPTIONS => [
        CURLOPT_HEADER => false,
        CURLOPT_RETURNTRANSFER => true
    ]
];
// Получаем компонент для отправки запросов
$operator = new Operator( $_ENV['API_HOST'], $commonRequestParams );

//Применяя, паттерн Singleton:
$operator = Operator::getInstance( $_ENV['API_HOST'], $commonRequestParams );

Доступные ключи для настройки(константы класса Request):

  • SETUP_PROTOCOL
  • SETUP_HOST
  • SETUP_METHOD
  • SETUP_HEADERS
  • SETUP_CONTENT_TYPE
  • SETUP_DATA
  • SETUP_CURL_OPTIONS
  • SETUP_CURL_INFO

Обработчики событий

Список событий

  • EVENT_AFTER_CONSTRUCT после создания объекта knockKnock
  • EVENT_CREATE_REQUEST после создания объекта запроса
  • EVENT_BEFORE_SEND перед отправкой запроса
  • EVENT_CURL_Operator перед отправкой curl запроса
  • EVENT_CREATE_RESPONSE после создания объекта ответа
  • EVENT_AFTER_SEND после получения ответа
Пример установки обработчиков событий
$operator->setupEventHandlers([
    Operator::EVENT_AFTER_CONSTRUCT => function( Operator $operator ) {
        // ...
    },
    Operator::EVENT_CREATE_REQUEST => function( Operator $operator, Request $request ) {
        // ...
    },
    Operator::EVENT_BEFORE_SEND => function( Operator $operator, Request $request ) {
        // ...
    },
    Operator::EVENT_CURL_HANDLER => function( Operator $operator, resource $ch ) {
        // ...
    },
    Operator::EVENT_CREATE_RESPONSE => function( Operator $operator, Response $response ) {
        // ...
    },
    Operator::EVENT_AFTER_SEND => function( Operator $operator, Response $response ) {
        // ...
    }
]);

Первый аргумент - ключ события, второй - callback функция.

Все callback функции принимают первым аргументом объект/экземпляр класса Operaotr.
Вторым аргументом передаётся объект/экземпляр класса в зависимости от события:

  • Request - для событий EVENT_CREATE_REQUEST, EVENT_BEFORE_SEND
  • Response - для событий EVENT_CREATE_RESPONSE, EVENT_AFTER_SEND

- - - - -

Запрос

use andy87\knock_knock\core\Request;

Объект запроса, содержащий данные для отправки запроса.

ReadOnly свойства:

  • protocol - протокол
  • host - хост
  • endpoint - конечная точка
  • method - метод
  • headers - заголовки
  • contentType - тип контента
  • data - данные
  • curlOptions - опции cURL
  • curlInfo - информация cURL
  • params - параметры запроса
  • url - полный URL
  • params - все свойства в виде массива
  • fakeResponse - установленные фэйковые данные ответа
  • errors - лог ошибок

Создание объекта запроса

Передавая параметры напрямую в конструктор:

$request = new Request( 'info/me', [
    Request::METHOD => Method::POST,
    Request::DATA => [ 'client_id' => 34 ],
    Request::HEADERS => [ 'api-secret-key' => $_ENV['API_SECRET_KEY'] ],
    Request::CURL_OPTIONS => [ CURLOPT_TIMEOUT => 10 ],
    Request::CURL_INFO => [
        CURLINFO_CONTENT_TYPE,
        CURLINFO_HEADER_SIZE,
        CURLINFO_TOTAL_TIME
    ],
    Request::CONTENT_TYPE => ContentType::FORM_DATA,
]);

Методом, который вызывает callback функцию, привязанную к ключу EVENT_CREATE_REQUEST

$request = $operator->constructRequest(Method::GET, 'info/me', [
    Request::METHOD => Method::POST,
    Request::DATA => [ 'client_id' => 45 ],
    Request::HEADERS => [ 'api-secret-key' => $_ENV['API_SECRET_KEY'] ],
    Request::CURL_OPTIONS => [ CURLOPT_TIMEOUT => 10 ],
    Request::CURL_INFO => [
        CURLINFO_CONTENT_TYPE,
        CURLINFO_HEADER_SIZE,
        CURLINFO_TOTAL_TIME
    ],
    Request::CONTENT_TYPE => ContentType::FORM_DATA,
]);

Клонируя существующий объект запроса:

$request = $operator->constructRequest(Method::GET, 'info/me');

$response = $operator->send($request);

//Клонирование объекта запроса (без статуса отправки)
$cloneRequest = $request->clone();

// Отправка клона запроса
$response = $operator->setupRequest( $cloneRequest )->send();

Назначение/Изменение/Получение отдельных параметров запроса (set/get)

Таблица set/get методов для взаимодействия с отдельными свойствами запроса

$request = $operator->constructRequest(Method::GET, 'info/me');

$request->setMethod( Method::GET );
$request->setData(['client_id' => 67]);
$request->setHeaders(['api-secret-key' => 'secretKey67']);
$request->setCurlOptions([
    CURLOPT_TIMEOUT => 10,
    CURLOPT_RETURNTRANSFER => true
]);
$request->setCurlInfo([
    CURLINFO_CONTENT_TYPE,
    CURLINFO_HEADER_SIZE,
    CURLINFO_TOTAL_TIME
]);
$request->setContentType( ContentType::JSON );

$protocol = $request->getPrococol(); // String
$host = $request->getHost(); // String
// ... аналогичным образом доступны и другие подобные методы для получения свойств запроса

Назначение запроса с переназначением свойств

$operator->setupRequest( $request, [
    Request::SETUP_HOST => $_ENV['API_HOST'],
    Request::SETUP_HEADERS => [
        'api-secret' => $_ENV['API_SECRET_KEY']
    ],
]);

setupRequest( Request $request, array $options = [] ): self

addError( string $error )

Добавление ошибки в лог ошибок

$request = $operator->constructRequest(Method::GET, 'info/me');

$request->addError('Ошибка!');

- - - - -

Ответ

use andy87\knock_knock\core\Response;

Объект ответа, содержащий данные ответа на запрос.

ReadOnly свойства

  • content
    • данные ответа
  • httpCode
    • код ответа
  • request
    • объект запроса, содержащий данные о запросе
  • curlOptions
    • быстрый доступ к request->curlOptions
  • curlInfo
    • быстрый доступ к request->curlInfo

Создание объекта ответа

Передавая параметры напрямую в конструктор:

$response = new Response('{"id" => 806034, "name" => "and_y87"}', 200 );

Методом, который вызывает callback функцию, привязанную к ключу EVENT_CREATE_RESPONSE

$response = $operator->constructResponse([
    Response::CONTENT => [
        'id' => 806034,
        'name' => 'and_y87'
    ],
    Response::HTTP_CODE => 400,
], $request );

constructResponse( array $responseParams, ?Request $request = null ): Response

Отправка запроса

send( ?Request $request = null ): Response Вызов возвращает объект/экземпляр класса Response.
Срабатывают callback функции, привязанные к ключам:

  • EVENT_AFTER_SEND
  • EVENT_CREATE_RESPONSE
  • EVENT_BEFORE_SEND
  • EVENT_CURL_HANDLER
$operator = new Operator( $_ENV['API_HOST'] );
$request = $operator->constructRequest(Method::GET, 'info/me');
$response = $operator->send($request);

// Аналог
$operator = new Operator( $_ENV['API_HOST'] );
$response = $operator->send( $operator->constructRequest(Method::GET, 'info/me') );

Нельзя повторно отправить запрос, выбрасывается исключение RequestCompleteException. Для повторной отправки запроса, необходимо создать новый объект запроса и использовать его:

$operator = new Operator( $_ENV['API_HOST'] );
$request = $operator->constructRequest(Method::GET, 'info/me');
$response = $operator->send($request);

// повторная отправка запроса
$response = $operator->send($request->clone());

Отправка запроса с фэйковым ответом

// параметры возвращаемого ответа
$fakeResponse = [
    Response::HTTP_CODE => 200,
    Response::CONTENT => '{"id" => 8060345, "nickName" => "and_y87"}'
];
$request->setFakeResponse( $fakeResponse );

$response = $operator->send( $request );

объект $response будет содержать в свойствах content, httpCode данные переданные в аргументе $fakeResponse

Данные в ответе

В созданный объект Response, чей запрос не был отправлен, разрешено задавать данные, используя методы группы set.

$response = $operator->send($request);

$response
    ->setHttpCode(200)
    ->setContent('{"id" => 8060345, "nickName" => "and_y87"}');

Внимание! Если данные в объекте уже существуют, повторно задать их нельзя выбрасывается ParamUpdateException.
В случае необходимости заменить данные, используется вызов метода replace( string $key, mixed $value ) см. далее

Подмена данных

Это сделано для явного действия, когда необходимо заменить данные в объекте `Response`.
$response = $operator->send($request);

$response
    ->replace( Response::HTTP_CODE, 200 )
    ->replace( Response::CONTENT, '{"id" => 8060345, "nickName" => "and_y87"}' );

Данные запроса из ответа

Для получения из объекта Response данных запроса, необходимо обратиться к ReadOnly свойству request
и далее взаимодействовать с ним аналогично объекту Request

$operator = new Operator( $_ENV['API_HOST'] );
$response = $operator->setRequest( $operator->constructRequest(Method::GET, 'info/me') )->send();

// Получение компонента запроса
$request = $response->request;

$method = $request->method; // получение метода запроса

Получения свойств cURL запроса

$operator = new Operator( $_ENV['API_HOST'] );
$response = $operator->setRequest( $operator->constructRequest(Method::GET, 'info/me') )->send();

$response->request;

// Получение свойств через объект запроса
$curlOptions =  $response->request->curlOption;
$curlInfo =  $response->request->curlInfo;

//Вариант с использованием быстрого доступа
$curlOptions =  $response->curlOption;
$curlInfo =  $response->curlInfo;

asArray()

Преобразование в массив.

  • преобразование данных ответа на запрос asArray()
  • преобразование всего объекта в массив asArray(true)
$response = $operator->send($request)->asArray(); // $response
$array = $response->content; // Array$response
addError( string $error )

Добавление ошибки в лог ошибок

$request = $operator->constructRequest(Method::GET, 'info/me');

$response = $operator->send($request);

$response->addError('Ошибка!');

- - - - -

- - - - -

Дополнительные возможности

SSL

Функционал включения/отключения SSL верификации в объектах Operaotr & Request.

В curlOptions добавляется ключ CURLOPT_SSL_VERIFYPEER и CURLOPT_SSL_VERIFYHOST.

->disableSSL( bool $verifyPeer = false, int $verifyHost = 0 );
->enableSSL( bool $verifyPeer = true, int $verifyHost = 2 );

Operaotr - для всех запросов

$operator = new Operator( $_ENV['API_HOST'] );
$operator->disableSSL();

$request = $operator->constructRequest(Method::GET, 'info/me');

$response = $operator->setupRequest( $request )->send();

Request - для конкретного запроса

$operator = new Operator( $_ENV['API_HOST'] )->disableSSL();

$request = $operator->constructRequest(Method::GET, 'info/me');
$request->enableSSL();

$response = $operator->setupRequest( $request )->send();

Cookie

В объекте Operaotr имеется функционал использования cookie.
Operaotr - для всех запросов

$operator = new Operator( $_ENV['API_HOST'] );

$cookie = $_ENV['COOKIE'];
$jar = $_ENV['COOKIE_JAR'];

$operator->useCookie( $cookie, $jar );

$operator->useCookie( string $cookie, string $jar, ?string $file = null )
по умолчанию $file = null и $file приравнивается к $jar

Логирование

Добавление сообщений во внутренний массив logs

$operator = new Operator( $_ENV['API_HOST'] );

$operator->addLog( 'Какое то сообщение' );

$operator->addLog( string $message )

- - - - -

- - - - -

Расширения на основе базового класса

KnockKnockOctopus
KnockKnockOctopus php curl facade

Класс с функционалом простой реализации отправки запросов и минимальными настройками

Доступные методы.

Каждый метод принимает два аргумента:

Простой пример использования

$knockKnockOctopus = new KnockKnockOctopus($_ENV['API_URL']);

$knockKnockOctopus->get( '/profile', [ 'id' => 806034 ] ); //GET запрос

$knockKnockOctopus->post( '/new', [  //POST запрос
    'name' => 'Новая новость',
    'content' => 'Текст новости' 
]);

- - - - -

KnockKnockSecurity
KnockKnockSecurity php curl facade

Расширяет класс KnockKnockOctopus, предоставляя доступ к функционалу для простой и
быстрой реализации авторизации, и настройки запросов.

$knockKnockSecurity = new KnockKnockSecurity($_ENV['API_URL']);

// Настройка параметров запроса по умолчанию
$knockKnockSecurity
    ->disableSSL()
    ->setupAuthorization( KnockKnockSecurity::TOKEN_BEARER, 'token' )
    ->setupHeaders([ 'X-Api-Key' => $_ENV['X_API_KEY'] ])
    ->setupContentType( ContentType::JSON )
    ->on( Operator::EVENT_AFTER_SEND, function( Operator $operator, Response $response ) => 
    {
        $logFilePath = $_SERVER['DOCUMENT_ROOT'] . '/api_log.txt';

        file_put_contents( $logFilePath, $response->content, FILE_APPEND );
    });

// Получение ответа на запрос методом `patch`
$responsePatch = $knockKnockSecurity->patch( 'product', [
    'price' => 1000
]);

$product = $responsePatch->asArray();

$price = $product['price'];

// Изменение типа контента на `application/json`, для следующего запроса
$knockKnockSecurity->useContentType( ContentType::JSON );

// Отправка POST запроса и получение ответа
$responsePost = $knockKnockSecurity->post( 'category', [
    'name' => 'Фреймворки'
]);

$response = json_decode( $responsePost->content );

$category_id = $response->id;

- - - - -

- - - - -

Custom реализация

Custom реализация Базового класса, к примеру с добавлением логирования работающим "под капотом"

class KnockKnockYandex extends Operator
{
    private const LOGGER = 'logger';


    private string $host = 'https://api.yandex.ru/'

    private string $contentType = ContentType::JSON

    private YandexLogger $logger;



    /**
     * @return void
     */
    public function init(): void
    {
        $this->setupYandexLoggerEventHandlers();
    }
    
    /**
     * @param array $callbacks
     * 
     * @return self
     */
    private function setupYandexLoggerEventHandlers( array $callbacks ): self
    {
        $this->on( self::AFTER_CREATE_REQUEST, function( Request $request ) => 
        {
            $logData = $this->getLogDataByRequest( $request );

            $this->addYandexLog( $logData );
        };

        $this->on(self::EVENT_AFTER_SEND, function( Response $response ) => 
        {
            $logData = $this->getLogDataByRequest( $response->request );

            $this->addYandexLog( $logData );
        };
    }

    /**
      * @param Request $request
      * 
      * @return array
      */
    private function getLogDataByRequest( Request $request ): array
    {
        return $request->getParams();
    }

    /**
     * @param array $logData
     * 
     * @return void
     */
    private function addYandexLog( array $logData ): bool
    {
        return $logger->log( $logData );
    }
}

Пример использования custom реализации

$knockKnockYandex = KnockKnockYandex::getInstanсe( $_ENV['API_HOST'], [
    KnockKnockYandex::LOGGER => new YandexLogger(),
]);

$response = $knockKnockYandex->setupRequest( 'profile', [ 
    Request::METHOD => Method::PATCH,
    Request::DATA => [ 'city' => 'Moscow' ],
]); // Логирование `afterCreateRequest`

$response = $knockKnockYandex->send(); // Логирование `afterSend`

- - - - -

- - - - -

Тесты

  • tests: 100+
  • assertions: 350+

Запуск тестов:

Нативный

vendor/bin/phpunit

Информационный

vendor/bin/phpunit --testdox

С логированием

vendor/bin/phpunit --log-junit "tests/logs/phpunit.xml"

Лицензия

https://github.com/andy87/KnockKnock под лицензией CC BY-SA 4.0
Для получения дополнительной информации смотрите http://creativecommons.org/licenses/by-sa/4.0/
Свободно для не коммерческого использования
С указанием авторства для коммерческого использования

Packagist