Pion - легковесный PHP Framework.
- Pion Support plugin for PhpStorm
- Peony Templating
- Muon Forms
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 - примера выступает репозиторий Pion Example. Можете скачать его себе и запустить в Docker, там есть инструкция. На его примере достаточно легко понять принцип работы фреймворка.
А если вас интересуют детали - продолжайте прокручивать эту документацию 😃
Application - сердце фреймворка. Его задачи:
- Определить action для текущего request-а
- Выполнить action и вернуть его результат
В качестве настроек Application принимает \Pion\Routing\RoutingInterface
и \Pion\Actions\Resolver\ActionArgumentsResolverInterface
.
Чтобы обработать request и получить ответ - необходимо вызвать метод \Pion\Application\ApplicationInterface::dispatch
и передать ему \Pion\Http\Request\RequestInterface
. Про эти компоненты ниже.
Любая реализация \Pion\Http\Request\RequestInterface
. Стандартная реализация так и называется - \Pion\Http\Request\Request
. Предоставляет приложению OOP-style доступ к суперглобальным переменным $_GET
/$_POST
/$_SERVER
/$_COOKIES
.
Любая реализация \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
- возвращает объект экшена-обработчика этого роута.
Согласно 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). По сути в этом методе мы обрабатываем запрос, выполняем бизнес-логику и возвращаем ответ, который будет отправлен пользователю.
\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 не обязательно. Вы можете зарезолвить сам Requestnew \Pion\Actions\Resolver\Argument\Value\ObjectValueResolver\ObjectValueResolver($request)
😄
данный интерфейс находится в разработке и пока предоставляет базовый функционал.
Любая реализация \Pion\Http\Response\ResponseInterface
. В качестве стандартной можно использовать \Pion\Http\Response\PlainTextResponse
или \Peony\Response\TemplatedResponse
, если вы используете Peony.
Если вы используете другой темплейтинг - советую реализовать для него TemplatedResponse
по примеру.
Для построения URL на экшн предполагается наследовать \Pion\Http\Uri\PionUri
.
Например так.