Документация


Предыстория

Next2 является переработкой более ранней библиотеки psr15-nextopen in new window. Первая версия была написана для простой, гибкой и быстрой работы с PSR-15 middlewareopen in new window в стиле приложений на фреймворке KoaJsopen in new window, который в свою очередь очень похож на ExpressJsopen in new window.

Hello World

Минимальное приложение с поддержкой PSR-7 на Next2 выглядит так:


use PTS\Next2\Context\ContextInterface;
use PTS\Next2\MicroApp;
use PTS\Psr7\Response\JsonResponse;
use PTS\Psr7\ServerRequest;
use PTS\Psr7\Uri;

require_once '../../../vendor/autoload.php';

$psr7Request = new ServerRequest('GET', new Uri('/'));

$app = new MicroApp;

$app->store->get('/', function(ContextInterface $ctx) {
    $ctx->setResponse(new JsonResponse(['message' => 'Hello World!']));
});

$psr7Response = $app->handle($psr7Request); // psr-15 runner











 
 
 
 
 


Глоссарий

Middleware (обработчик промежуточного уровня)

Middleware - это часть приложения, которая отвечает за конкретную единицу логики в обработке request/response. (PSR-15 middlewareopen in new window)

Отсылка к PSR-15

Первой версия фреймворка использовала PSR-15 middlewareopen in new window. Next2 используеют похожую на PSR-15 по смыслу и сигнатуре модель выстраивания обработчиков.

Отличия от PSR-15 обработчиков:

  • Обработчиком промежуточного уровня выступает любой обработчик с типом callable вместо Psr\Http\Server\MiddlewareInterface.
  • Layer может запускать не 1 обработчик, а группу обработчиков callable[], позволяя делить код на более мелкие части сохраняя логическую целостность.

Layer / Слой

Это абстракция над обработчиком, которая содержит метаинформцию (для простоты это можно считатать алиасом middleware). Layer может иметь как 1 обработчик, так и несколько, чтобы декомпозировать на более мелкие части код. Приложение это набор Layers, которые определяются для каждого http запроса индивидуально.

Приложение

Приложение является самым высоким уровнем и реалзиует полноую обработку запроса. Задача приложения получить на входе http request и создать http response.

Приложение осознано сводит число сущностей к минимуму, чтобы оставаться действительно простым, понятным и надежным. Основное понятие с которым придется постоянно работать это Layer (Слой).

Приложеине получает на вход стандартный объект запроса PSR-7 и прогоняет через все обработчики слоев. Какие именно слои примут частие в обработке запроса определяется метаифнормацией слоя. Семантически можно выделить следующие типы слоев:

  • Активация на любом запросе
  • Активация по http методу
  • Активация по соответствию uri в регулярном выражении
Собственные стратегии

Из коробки идет минимальный набор стратений (по http методу и поиск по uri). Можно дополнительно реализовать любые кастомные правила, по которым будет принято решеине активируетя слой для обработки request или нет. Для э того нужно добавить свою реализацию PTS\Next2\Layer\Resolver\LayerResolverInterface

Строительные блоки

Handler / Обработчик

Обработчиком может выступать любая форма callable PHP типа. Функция обработчик в общем случае принмает 1 аргумент. Первый аргумент это контекст, который должнен реализовывать PTS\Next2\Context\ContextInterface либо его наследников в случае расширения базового контекста под себя.

Обработчик это самая минимальная единица.

Context

Контекст - это связующая часть между всеми слоями. Базовый контекст хранит request, response и позволяет получить доступ к текущему слою (Layer) в runtime. В контексте можно хранить все что угодно, расширяя его дочерним классом и не ограничивая себя. Данный подход хорошо себя зарекомендовал в таких фреймворках как koaJsopen in new window и goFiberopen in new window.

Слои

Приложение MicroApp хранит в себе в свойстве $app->store хранилище слоев LayersStore. Слой инкапсулирует 1 обработчик или группу обработчиков. Хранилище слоев позволяет добавлять обрабочтики к приложению посредством метода use:

    /**
     * @param callable[] | callable $handler
     */
    public function use(callable|array $handler, array $options = []): static
    {
        $layer = $this->layerFactory->create($handler, $options);
        return $this->addLayer($layer);
    }

Чтобы дерелировать управление следующему обработчику, нужно вызвать метод next на объекте контекста:

    public function someHandler(\PTS\Next2\Context\ContextInterface $ctx)
    {
        // request phase
        $ctx->next();
        // response phase
    }

Слоенный подход

Каждый слой имеет 1 определенную зону отвественности, это позволяет оставаться вашему коду простым и чистым. Слой может самостоятельно создать http response и не передавать управление следующему слою. Например слой, который проверяет аунтификацию, может сам создать http response с статус кодом 401. С таким подходом, мы можем очень быстро обрабатывать некоторые запросы, даже не поднимая тяжелый фреймворк, если он используется на последующих слоях.

Другой пример, это обработчик, который может кешировать запрос и повторно очень быстро отвечать на запрос из кеша.

Options

Опции объекта Layer

ОпцияОписаниеПо умолчаниюПример значения
pathописывает uri путь, на который должен активироваться слой, можро использловать регулярки, без указария этого параметра слой активируется на любой uri-/users/{id}
nameопределяем уникальное имя слоя, через это имя можно будет найти слой (по умолчанию все слои получают имена вида l-0, l-1 и т.д.)-usersAction
methodsпринимает массив из http методов, слой активируется только на http запросы с указанными http методами. Если методы не переданы, то слой активируется на любой http метод[]['GET']
priorityпринимает int число с приоритетом, чем ни меньше число, тем раньше выполнится слой. Это позволяет конфигурировать слои декларативно, не в порядке добавления50100
restrictionsпринимает массив вида ['id' => \id+], позволяя накладывать дополнительные ограничения на переменны в uri запроса[]['id'=>'\d+']
contextпозволяет прикрепить к слою любые произволные данные[]['foo'=>'bar']

Пример использования options в коде:


use PTS\Next2\Context\ContextInterface;
use PTS\Next2\MicroApp;
use PTS\Psr7\Response\JsonResponse;
use PTS\Psr7\ServerRequest;
use PTS\Psr7\Uri;

require_once '../../../vendor/autoload.php';

$psr7Request = new ServerRequest('GET', new Uri('/hello/alex/'));

$app = new MicroApp;

$app->store->use(function(ContextInterface $ctx) {
    $name = $ctx->getUriParams()['name'] ?? null;
    $ctx->response = new JsonResponse(['message' => "Hello {$name}!"]);
}, [
    'path' => '/hello/{name}/',
    'name' => 'helloWorldAction',
    'priority' => 50,
    'methods' => 'GET|HEAD',
    'restrictions' => [
        'name' => '[a-zA-Z0-9]+'
    ]
]);

$psr7Response = $app->handle($psr7Request);

















 
 
 
 
 
 
 
 


Приложение

Приложение это самый высокий слой. Приложение инкапсулирует группу слоев. Приложения могут объединяться через CompositionMicroApp в общее приложение.

Прочее

Быстрые http методы (сахар)

Для упрощения также доступны методы, которые зеркалируют основные http методы, например:

$app->store
    ->get('/users/{id}/', fn($ctx) => $ctx->response = new JsonResponse)
    ->post('/users/{id}/', fn($ctx) => ...)
    ->patch('/users/{id}/', fn($ctx) => ...)
    ->put('/users/{id}/', fn($ctx) => ...)
    ->delete('/users/{id}/', fn($ctx) => ...);

Быстрые методы

Полный список методов можно найти в трейте PTS\Next2\Layer\Store\FastMethodsTrait. Он реализует самые популярные методы. Вы можете добавить свои методы для сахара

Декларативная кофнигурация

Очень удобно конфигурировать маршруты и порядок декларативно, например посредством yml файлов, простой пример такой конфигурации выглядит так:

# middlewares
ThrowableToResponse:
  controller: Site\Middleware\ThrowableToResponse:process

# 404 page
otherwise:
  handler: Site\Controller\Action\Page404:__invoke
  name: otherwise
  priority: 10000

# actions
main:
  path: /
  methods: 'GET|HEAD'
  handler: Site\Controller\OtherwiseController:mainPage

posts:
  path: /post/
  methods: 'GET'
  handler: Site\Controller\PostController:getList

cat:
  path: /cat/{id}/
  restrictions:
    id: \d+
  methods: 'GET'
  handler: Site\Controller\CategoryController:getCat

Можно расширять декларативную конфигурацию посредством расширения класса PTS\Next2\LayerLayerFactory или реалзиации PTS\Next2\LayerFactoryInterface интерфейса.

Inline ограничения параметров в uri

Ограничения на параметр в path можно описать дополнительно прямо в path помимо конфигурации опции restrictions у слоя. Inline формат записи проще для простых регулярок. Для сложных и длинных регулярок лучше использовать явно опцию restrictions, чтобы конфигурация слоя оставалась простой для чтения и интерпритации. В общем виде такая форма записи выглядит так {name:restrictRegExp}. Следующие конфигурации будут эквивалентны:

cat:
  path: /cat/{id}/
  restrictions:
    id: \d+

cat2:
  path: /cat/{id:\d+}/

Приоритет

Inline ограничения будут проигнорированы, если для параметра по имени параметра сконфигурировано в конфиге слоя ограничение

Flow

Каждый слой может что-то делать до вызова $ctx->next() на этапе запроса, также может что-то делать после вызова $ctx->next() на этапе ответа. Пример полного выполнения запроса, который проходит сквозь все слои:

200 flow

Пример flow, кога запрос досрочно может быть обработан одним из слоев, без делегирования обработки через $ctx->next():

401 flow

Каждый слой решает сам, создать http response и пректатить обработку, либо вызвать следующий слой и делегировать ему тем самым обработку.

PSR-15 Middleware

Интеграция

Приложение реализует интерфейс Psr\Http\Server\RequestHandlerInterface из PSR-15. За счет этого оно может интегрировться в любые приложения как middleware, принимая на вход PSR7 Request и возвращая PSR7 Response.

Схематично это выглядит как psr15 -> psr15 -> next2.


Если микро приложение должно делегировать обработку запроса далее в классическую PSR-15 Middleware, то можно обернуть в такую middleware:


use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use PTS\Next2\Adapter\Psr7RunnerAdapter;
use PTS\Next2\Context\ContextInterface;
use PTS\Next2\Layer\Layer;
use PTS\Next2\MicroApp;

class NextMiddleware implements MiddlewareInterface
{
    /** @var Layer[] */
    protected array $layers;
    protected Psr7RunnerAdapter $runner;

    public function __construct(MicroApp $app)
    {
        $this->layers = $app->store->getLayers();
        $this->runner = new Psr7RunnerAdapter;
    }

    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
    {
        $callable = function(ContextInterface $ctx) use ($handler) {
            $response = $handler->handle($ctx->getRequest());
            $ctx->setResponse($response);
        };

        $adapterLayer = new Layer([$callable]);

        $layers = [...$this->layers, $adapterLayer];
        $this->runner->run($layers, $request);

        return $handler->handle($request);
    }
}

Схематично это выглядит как psr15 -> psr15 -> next2 -> psr15.

Такие интеграции позволяют точечно заменять части старого приложения или переносить небольшими кусочками часть функционала.

TODO

  • подумать о передачи в $ctx->next() аргументов, которые придут экстра аргументами в следующий handler помимо контекста.
  • подумать об ограничении uri параметров до регулядки [A-Za-z0-9_-]) по дефолту
  • подумать о автозаполнителях и валидаторах параметров из запроса как в koaJs router paramopen in new window (сейчас легко реализуются обработчиком или слоем с сохранением в конеткст, без ввода новой абстракции или сахара)