Pion is a lightweight PHP Framework

2.1.0 2022-10-31 20:17 UTC

This package is auto-updated.

Last update: 2024-11-20 14:30:14 UTC


README

Pion - легковесный PHP Framework.

Pion Components

Examples

Pion principles

1. Minimalism
Pion предоставляет только базовый набор инструментов. Его задача - получить пользовательский request, обработать его и вернуть ответ.
Такой подход проектирования использован умышленно, чтобы защитить ядро фреймворка от тесной интеграции c "лишними" компонентами. Проще говоря - вы без проблем можете использовать ваш любимый templating/ORM/IoC-container/etc. 😄

Ремарка 1: ➡️ Pion Example, тут вы можете найти "plug and play" пример приложения построенного на связке Pion + Doctrine + Symfony Console + еще пара Symfony компонентов

Ремарка 2: Для Pion разработан нативный templating компонент - Peony. Он супер простой и вряд ли подойдет для сложных задач. Но у него есть ряд преимуществ, например PhpStorm плагин для комплишна переданных во вьюшку переменных. 😉

Ремарка 3: Возможно в дальнейшем будут разработаны "нативные" ORM/IoC-container/Console/etc. компоненты. В любом случае они будут поставляться в виде отдельных пакетов.

2. Customizability
Pion спроектирован с расчетом на то, что практически любой из его внутренних компонентов может быть заменен кастомным.

3. Muggle principle
Архитектура фреймворка стремится к уменьшению количества "магии".

ИМХО: вы должны иметь возможность в любой момент заглянуть "под капот" фреймворка и быстро понять, что там происходит. Без борьбы с "адом обратных вызов"/свалки из абстрактных классов и yaml/array конфигураций.

Pion продвигает идею очевидного flow и контролируемости.

4. IoC/DI
Также Pion поддерживает принципы инверсии управления. Это видно на примере использования подкомпонента Arguments Resolver (подробнее о нем ниже). Вкратце - если какой-то экшн требует соединения с базой данных - приложение обязано передать ему экземпляр EntityManager в качестве аргумента.

5. Foolproof
Да, Pion предполагает, что вы каждый раз будете наследовать \Pion\Http\Uri\PionUri для определения ссылки на каждый экшн. Например. Но благодаря этому вы будете защищены от случайной опечатки в имени GET параметра. Да и переименовывать GET параметры будет значительно легче. 😄

6. Easy configuration
Процесс конфигурации Pion очень прост (это же lightweight framework 🙂). При конфигурации не используется yaml/json/ассоциативные массивы. Я глубоко убежден, что это плохая практика, так IDE не резолвит ключи в таких конфигурациях и если вам надо заглянуть "под капот" - приходится использовать текстовый поиск по названию этих ключей. 🤦‍♂️

Вы должны иметь возможность в любой момент использовать "go to declaration" в любом месте конфигурации.

Hello World

Пока в качестве Hello World - примера выступает репозиторий Pion Example. Можете скачать его себе и запустить в Docker, там есть инструкция. На его примере достаточно легко понять принцип работы фреймворка.

А если вас интересуют детали - продолжайте прокручивать эту документацию 😃

Main Components

Application

Application - сердце фреймворка. Его задачи:

  • Определить action для текущего request-а
  • Выполнить action и вернуть его результат

В качестве настроек Application принимает \Pion\Routing\RoutingInterface и \Pion\Actions\Resolver\ActionArgumentsResolverInterface.

Чтобы обработать request и получить ответ - необходимо вызвать метод \Pion\Application\ApplicationInterface::dispatch и передать ему \Pion\Http\Request\RequestInterface. Про эти компоненты ниже. ⤵️

Request

Любая реализация \Pion\Http\Request\RequestInterface. Стандартная реализация так и называется - \Pion\Http\Request\Request. Предоставляет приложению OOP-style доступ к суперглобальным переменным $_GET/$_POST/$_SERVER/$_COOKIES.

Routing

Любая реализация \Pion\Routing\RoutingInterface. Стандартная реализация - \Pion\Routing\Routing. В качестве конфигурации принимает в конструкторе массив объектов, реализующих \Pion\Routing\Route\RouteInterface.

RouteInterface
У \Pion\Routing\Route\RouteInterface есть 3 метода:

  • \Pion\Routing\Route\RouteInterface::path(): string - возвращает URL path роута. Нужен, чтобы построить URL для этого роута.
  • \Pion\Routing\Route\RouteInterface::isSupported(RequestInterface $request): bool - определяет, может ли текущий роут обработать переданный request.
  • \Pion\Routing\Route\RouteInterface::action(): \Pion\Actions\ActionInterface - возвращает объект экшена-обработчика этого роута.

Actions

Согласно MVC в приложении должны быть контроллеры, которые получают пользовательский request, выполняют какую-то бизнес логику и возвращают ответ. Как правило в одном контроллере может находиться несколько экшенов, каждый из которых обрабатывает свой Route. Этот подход плох по следующим причинам:

  • Классы контроллеров неконтролируемо разрастаются
  • Экшены начинают использовать какую-то общую логику, инкапсулированную внутри контроллера. сли такой экшн понадобится вынести в отдельный контроллер - это может стать проблемой.
  • Если экшены хранят данные внутри свойств объекта контроллера - это может привести к неочевидным глюкам. Например экшн A помещает в $this->user объект авторизованного пользователя. А потом кто-то пытается обратиться к $this->user в экшене B и получает NPE. Пример утрирован, но суть, думаю, ясна.

С оглядкой на все это в Pion нет контроллеров - есть Actions. Action реализует \Pion\Actions\ActionInterface.

Каждый экшн обязан реализовать 2 метода:

  • \Pion\Actions\ActionInterface::route(): \Pion\Routing\Route\RouteInterface - возвращает Route настроенный для этого экшена.
  • \Pion\Actions\ActionInterface::__invoke(???): \Pion\Http\Response\ResponseInterface. Метод __invoke не описан в \Pion\Actions\ActionInterface, так как может принимать произвольный набор параметров (об этом в блоке Argument resolving). По сути в этом методе мы обрабатываем запрос, выполняем бизнес-логику и возвращаем ответ, который будет отправлен пользователю.

Argument resolving

\Pion\Actions\ActionInterface::__invoke(???) - аргументы этого метода могут быть произвольными. В Pion используется механизм называемый Argument Resolving. По сути он реализует один из принципов IoC - инъекцию зависимостей. Если ваш экшн зависит, например, от соединения с базой данных - его сигнатура должна выглядеть следующим образом:

# ...
public function __invoke(DbConnection $dbConnection): \Pion\Http\Response\ResponseInterface {
  # ...
}

При инициализации этого экшена Application обязан передать ему DbConnection. Откуда Application получает эту зависимость?
Выше, в блоке Application, я писал, что один из элементов его конфигурации - это \Pion\Actions\Resolver\ActionArgumentsResolverInterface.
Контракт этого интерфейса содержит всего 1 метод - ActionArgumentsResolverInterface::resolve(ActionInterface $action): array. Его задача проста - определить, какие аргументы нужно передать в метод __invoke и вернуть массив этих аргументов, если он может их определить.

Естественно логику определения аргументов не нужно реализовывать самому, для этого есть стандартная реализация - \Pion\Actions\Resolver\ActionArgumentsResolver.
В качестве настроек необходимо передать массив \Pion\Actions\Resolver\Argument\Value\ValueResolverInterface.
Не буду вдаваться в подробности, скажу вкратце - задача ValueResolver-а вернуть значение для какого-то одного конкретного аргумента в __invoke.

У \Pion\Actions\Resolver\Argument\Value\ValueResolverInterface конечно есть стандартные реализации, их две.

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

RequestValueResolver
В качестве аргумента в конструкторе принимает RequestInterface. Умеет резолвить значения из $_GET и $_POST.

Example:

Допустим в вашем приложении есть экшн удаления пользователя. У этого экшена две зависимости - соединение с базой данных и ID пользователя, которого необходимо удалить.

$request = new \Pion\Http\Request\Request();
$dbConnection = new DbConnection(...);
$response = (new \Pion\Application\Application(
  new \Pion\Routing\Routing(
    DeleteUserAction::route()
  ),
  new \Pion\Actions\Resolver\ActionArgumentsResolver(
    new \Pion\Actions\Resolver\Argument\Value\RequestValueResolver($request),
    new \Pion\Actions\Resolver\Argument\Value\ObjectValueResolver($dbConnection)
  )
))->dispatch($request);

# ...

class DeleteUserAction implements \Pion\Actions\ActionInterface {
  public function __invoke(DbConnection $dbConnection, string $userId) : \Pion\Http\Response\ResponseInterface {
    ...
  }
}

P.S. Работать с $_GET/$_POST параметрами только через ArgumentsResolver не обязательно. Вы можете зарезолвить сам Request new \Pion\Actions\Resolver\Argument\Value\ObjectValueResolver\ObjectValueResolver($request) 😄

Response

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

Любая реализация \Pion\Http\Response\ResponseInterface. В качестве стандартной можно использовать \Pion\Http\Response\PlainTextResponse или \Peony\Response\TemplatedResponse, если вы используете Peony. Если вы используете другой темплейтинг - советую реализовать для него TemplatedResponse по примеру.

URL's

Для построения URL на экшн предполагается наследовать \Pion\Http\Uri\PionUri.
Например так.