Четыре столпа PSR
Мастер-класс по Symfony завершили, теперь пополним теперь рубрику ответов на вопросы.
Недавно пришло письмо с большим вопросом по мотивам нашей серии по PSR-7, на который родился ещё больший ответ. В качестве вопроса хотел привести цитаты, но для целостного понимания письмо с письменного разрешения автора привожу почти целиком:
Здравствуйте. Хотелось бы узнать ваше мнение о размышлении над PHP-FIG. Прошу прощения за сумбурность – трудно сформулировать мысль.
По сути FIG (Framework Interoperability Group) – группа взаимодействия framework'ов. Стандарты же называются PSR (PHP Standard Recommendations) – стандартные рекомендации PHP.
Но пол интернета кипит тем, что это именно стандарты и нужно следовать только им. Не следуешь – не нормальный программист.
То есть группа взаимодействия framework'ов пишет стандарты по работе с языком, что странно.
И меня беспокоит беспрекословное следование этим стандартам.
CodeStyle можно принять т.к у него нет побочного эффекта, и эти стандарты влияют только на форматирование самого кода.
Вопрос возникает к остальным стандартам:
PSR-11: Container Interface
Не понятна суть этого стандарта.
По сути – нам доступно два метода: has и get. Но прокидывать куда-то контейнер – плохая идея. Если его прокинуть, то он превращается в сервис-локатор, что само по себе имеет минусы.
Правильнее – настроить контейнер и получать из него определенный класс, в который в глубину будут автоматически инжектиться остальные зависимости. При этом применение даже в framework'ах всего этого вызывает сомнение – при замене контейнера все-равно придется переписывать все те места, в которых происходит заполнение контейнера.
В том-же symfony – это происходит внутри бандлов, в laravel в провайдерах. Что делает невозможным легкую замену контейнеру. Можно было бы написать interface для контейнера, но тут уже получится большой костыль – все контейнеры биндят зависимости по разному и имеют разный функционал. Разве нет?
PSR-15: HTTP Server Request Handlers
Тут мы имеем RequestHandlerInterface и MiddlewareInterface.
В каждом по одному методу:
– handle(ServerRequestInterface $request): ResponseInterface; – process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface;
Также – не ясно зачем это нужно?
RequestHandlerInterface – не вижу ни одной причины менять его. Если нужно будет менять его – придется менять весь framework, т.к по сути – это его каркас.
MiddlewareInterface – в стандарте даже не имеет связи с RequestHandlerInterface. И в чем сложность – написать свой middleware внутри которого вызывать метод какого-либо подключенной зависимости?
PSR-17: HTTP Factories
Также – не ясна причина, из-за которой будет необходимо поменять request|response. Прокидывать внутрь проекта request|respone – плохо. Разве нет? И даже если нужно будет поменять их – при правильной логике придется поправить только в middleware и в контроллерах. Тот-же symfony до сих пор использует свой http foundation.
Для каких-то вещей, вроде, роутера (у вас есть в уроках) – можно прокинуть интерфейс и написать adapter, необходимость замены в будущем остальных вещей – вызывает сомнение, но и это при отделении логики можно будет легко произвести. Есть паттерны, разделение проекта на части. Что еще нужно?
Если я захочу поменять кэш, например – напишу адаптер для интерфейса нужной мне библиотеки.
Для тех же, кто в ответ на "У всех свои проблемы" отвечает "У большинства проблемы одинаковые" – будет полностью писать на определенном framework'е – laravel, symfony, yii и.т.д., их библиотеки и будет завязан на те-же бандлы, контейнеры, конфиги, доктрину и т.д., и не будет заморачиваться работой в стиле: "сегодня заменю библиотеку для config, через месяц заменю http, еще через месяц роутер" и т.д.
Смущает стандартизация не только стиля программирования, но и способов реализации, от которой не видно настолько большой выгоды, о которой говорят.
Хотел бы услышать ваше мнение на это рассуждение.
Здравствуйте! Да, я часто говорю и пишу про PSR-ы. Раз непонятных моментов и претензий к ним возникает много, то стоит разделить рекомендации на смысловые группы с сайта PHP-FIG, но в более удобном порядке:
Coding Styles
- PSR-1: Basic Coding Standard
- PSR-12: Extended Coding Style Guide
Autoloading
- PSR-4: Improved Autoloading
Interfaces
- PSR-3: Logger Interface
- PSR-6: Caching Interface
- PSR-11: Container Interface
- PSR-13: Hypermedia Links
- PSR-14: Event Dispatcher
- PSR-16: Simple Cache
HTTP:
- PSR-7: HTTP Message Interfaces
- PSR-15: HTTP Handlers
- PSR-17: HTTP Factories
- PSR-18: HTTP Client
Они и представляют пока четыре столпа актуальных рекомендаций. Числа здесь не все, так как остальные либо уже устарели, либо до сих пор не приняты.
CodeStyle можно принять т.к у него нет побочного эффекта, и эти стандарты влияют только на форматирование самого кода.
Так что первая группа – это...
Всеобщий Code Style
Пока в других языках не утихают споры про «табы vs пробелы», пока каждая компания выпускает свои гайды:
и пока там до сих пор рушатся чьи-то судьбы
знайте, что в PHP с форматированием кода нам повезло.
В своих проектах у всех программистов вкусы и привычки разные. Самому себе можно оформлять код как угодно. Но как только программисты начинают использовать чужие компоненты или переходят из проекта в проект, то такое разнообразие начинает напрягать. В более распущенных случаях в одном файле проекта разные куски кода оказываются написанными разными программистами в разных стилях.
Для борьбы с бесполезным разнообразием стилей теперь для PHP есть конкретно описанные рекомендации PSR-1, PSR-2 и PSR-12.
Пусть рождённые до принятия этих рекомендаций Wordpress и Yii1 любят или не любят ставить или не ставить пробелы до/после скобок и называть поля в snake_case/camelCase, но сейчас с новыми фреймворками нам проще. Это даёт хорошие бонусы:
- Никому не нужно изобретать свои стили, как все это делают для Java. Можно всё делать по единым для всего мира PSR.
- Умные IDE содержат автоформатирование, настроенное по PSR. Стоит нажать Reformat Code в PhpStorm и код автоматически выровнится.
- Есть PHP CodeSniffer, который может в консоли проверить и автоматически исправить код по тем же PSR.
В итоге если все программисты привыкнут оформлять код одинаково по PSR, то будет проще всем.
А в языке Go с этим всё ещё строже, так как при неверном форматировании вылетит ошибка компиляции.
Так что PSR для Code Style – это как правила хорошего тона. Можете не придерживаться, но тогда постоянно вам будут об этом напоминать.
Автозагрузка
Идея PSR-4 проста: путь в namespace и имя класса совпадают с именами папок и файла.
Стоит где-то в одном месте указать автозагрузчику, что базовый неймспейс App
соответствует папке src
, так сразу класс App\Blog\Entity\Post
спокойно можно будет найти в файле src/Blog/Entity/Post.php
любому PSR-4-совместимому загрузчику.
Если по такому принципу будут работать все фреймворки и библиотеки, то можно будет пользоваться одним общим автозагрузчиком.
Делая свой мини-фреймворк или мини-CMS вам может быть лень подключать Composer. И может показаться, что проще сложить классы по-особому и сочинить свой загрузчик с spl_autoload_register
для импорта ваших классов из файлов. Если показалось, но вы решили выложить этот код на GitHub, то ущипните себя за что-нибудь.
Почему? Потому что как только я возьму вашу CMS и начну на ней что-то делать, то её кода мне скорее всего не хватит. Помимо него я захочу подключить готовый Markdown-парсер и почтовик вроде SwiftMailer. Или даже без них захочу подключить PHPUnit или Codeception для тестов. И для этого мне... всё равно придётся подключать Composer. И после этого ещё и скрещивать ваш автозагрузчик с имеющимся в Composer, чтобы они теперь не мешали друг-другу.
Так что в любой ситуации удобнее будет придерживаться именования файлов по PSR-4 и подключить Composer. Другим PSR-ам следуйте по вкусу, а автозагрузку не ломайте.
Итак, с рекомендациями по форматированию и автозагрузке разобрались. Их желательно придерживаться независимо от ваших вкусов. А теперь переходим к вещам менее важным опциональным.
Обобщённые интерфейсы
Для каких-то вещей, вроде, роутера (у вас есть в уроках) – можно прокинуть интерфейс и написать adapter, необходимость замены в будущем остальных вещей – вызывает сомнение, но и это при отделении логики можно будет легко произвести. Есть паттерны, разделение проекта на части. Что еще нужно?
Если я захочу поменять кэш, например – напишу адаптер для интерфейса нужной мне библиотеки.
Это вопрос к списку:
- PSR-3: Logger Interface
- PSR-6: Caching Interface
- PSR-16: Simple Cache
- PSR-14: Event Dispatcher
- PSR-18: HTTP Client
Адаптеры для своего кода – это понятно. Но что насчёт не своего?
Вася пишет библиотеку для определения города по IP-адресу. Сочинил класс Locator
:
src/ Locator.php
Теперь подумал, что неплохо бы вынести HTTP-клиент. Написал свой:
src/ Locator.php HttpClient.php
Ну и лучше интерфейс для клиента предусмотреть:
src/ Locator.php HttpClientInterface.php HttpClient.php
И неплохо бы добавить логер. Написал свой логер:
src/ Locator.php HttpClientInterface.php HttpClient.php FileLogger.php
Потом решил, что было бы неплохо не ограничиваться файловыми логами, а объявить интерфейс:
src/ Locator.php HttpClientInterface.php HttpClient.php LoggerInterface.php FileLogger.php
чтобы другие программисты могли вместо файлового свои логеры через адаптеры подсовывать.
Ну и чтобы слишком часто сторонний сервис локации не дёргать, решил добавить кэш:
src/ Locator.php HttpClientInterface.php HttpClient.php LoggerInterface.php FileLogger.php CacheInterface.php FileCache.php
Всё, библиотека готова. Можно выкладывать на GitHub.
Теперь чтобы подключить эту библиотеку к своему проекту я просто делаю:
composer require vasya/ip-locator
и пишу свои адаптеры для него
class YiiLoggerAdapter implements Vasya\LoggerInterface { public function error(string $message, string $ip): void { Yii::error($message . ': ' . $ip, 'ip-locator'); } }
class YiiCacheAdapter implements Vasya\CacheInterface { public function get(string $id) { return Yii::$app->cache->get($id); } ... }
class GuzzleHttpClientAdapter implements Vasya\HttpClientInterface { public function get(string $url) { return ...; } }
И всё работает.
Но проекты обычно большие. И так через Composer я установил себе восемь чужих библиотек. И вижу, что к каждой библиотеке её автор написал свои собственные Logger и Cache.
Итого я имею восемь библиотек со своими логерами и кэшами и мне для них нужно сочинить шестнадцать адаптеров. Грустно...
Но что за глобальная несправедливость такая? Почему тысячи программистов для своих тысяч библиотек тысячи раз пишут логеры и кэшеры? Вот бы было классно, если бы все программисты договорились и сделали один общий LoggerInterface и CacheInterface.
Вот они и договорились в PHP-FIG и сделали всё общее.
Если теперь Васе нужно в своём компоненте сделать логирование, то вместо своего самописа он спокойно принимает Psr\Log
:
namespace Vasya\IpLocator; use Psr\HttpClient\HttpClientInterface; use Psr\Log\LoggerInterface; class Locator { private $client; private $logger; public function __construct(HttpClientInterface $client, LoggerInterface $logger) { $this->client = $client; $this->logger = $logger; } ... }
Теперь я могу передать ему любой PSR-совместимый клиент и PSR-логер фреймворка напрямую без адаптеров:
$locator = new Locator($client, $logger);
Так что не только наш код может использовать чужие компоненты, но и чужим компонентам порой нужен наш код.
Если теперь я задумал через Composer установить себе восемь чужих библиотек, то если все они принимают PSR-овские логер и кэш, то я спокойно закину в них свои monolog/monolog и zendframework/zend-cache без всяких адаптеров.
В итоге имеем общепринятые интерфейсы LoggerInterface
, CacheInterface
, EventDispatcherInterface
и рассматриваемый нами далее HttpClientInterface
. Быстро и удобно.
Теперь Вася вместо кучи кода:
src/ Locator.php HttpClientInterface.php HttpClient.php LoggerInterface.php FileLogger.php CacheInterface.php FileCache.php
может сочинить всего один файл:
src/ Locator.php
и в нём завязаться на общепринятые интерфейсы.
Теперь клиент, логер и кэш к нему можно подключить любые готовые на усмотрение другого программиста.
Framework Interoperability сэкономил время на написание кода автору и на написание адаптеров пользователю библиотеки.
PSR-11: Container Interface
Не понятна суть этого стандарта.
По сути – нам доступно два метода: has и get. Но прокидывать куда-то контейнер – плохая идея. Если его прокинуть, то он превращается в сервис-локатор, что само по себе имеет минусы.
Правильнее – настроить контейнер и получать из него определенный класс, в который в глубину будут автоматически инжектиться остальные зависимости. При этом применение даже в framework'ах всего этого вызывает сомнение – при замене контейнера все-равно придется переписывать все те места, в которых происходит заполнение контейнера.
В том-же symfony – это происходит внутри бандлов, в laravel в провайдерах. Что делает невозможным легкую замену контейнеру. Можно было бы написать interface для контейнера, но тут уже получится большой костыль – все контейнеры биндят зависимости по разному и имеют разный функционал. Разве нет?
Да, прокидывать куда-то контейнер – плохая идея. И если его прокинуть, то он превращается в сервис-локатор, что само по себе имеет минусы.
Но всё-таки придётся в самом верху именно получить из него корневой сервис, в который в глубину будут автоматически инжектиться зависимости. И есть компоненты и фреймворки, которым для работы мы передаём именно сам контейнер.
Например, классу Slim\App или ControllerResolver
любого фреймворка или микрофреймворка он нужен, чтобы доставать оттуда контроллер через $container->get($controllerName)
.
Также к шине команд league/tactician
прилагается готовый резолвер ContainerLocator, который достаёт обработчик команды из PSR-контейнера как из локатора через $container->get($handlerName)
.
При замене контейнера все-равно придется переписывать все те места, в которых происходит заполнение контейнера.
Код Application
, ControllerResolver
и HandlerLocator
всех библиотек и микрофреймворков использует контейнер только на чтение методами has
и get
. Им не интересно, чем и как мы переданный контейнер заполняем.
И если мне при разработке проекта на микрофреймворке Slim 3 с удобным роутером не нравился его примитивный контейнер Slim\Container
без автовайринга, ленивой загрузки и фабрик, то я его могу заменить на zend/service-manager и передать в new Slim\App($container)
уже его.
И установив Tactician я могу ему спокойно передать любой контейнер из моего Yii, Symfony, Laravel, Slim или Zend, так как все они реализуют общий ContainerInterface
.
А так да, при переходе на другой контейнер мы переписываем только его заполнение в конфиге.
А дальше у нас...
Обобщение работы с HTTP
С общими интерфейсами компонентов разобрались. Они описывают простые конкретные библиотеки.
Из этой группы к ним можно отнести PSR-18 для описания компонента HTTP Client. А PSR-15 уже является обобщением для построения целых фреймворков.
Вся группа работает поверх PSR-7. С него и начнём.
PSR-7: HTTP Messages
Откуда всё это пошло и зачем понадобилось?
С данными запроса в PHP ввиду его процедурного прошлого мы можем работать напрямую через набор разрозненных глобальных массивов $_SERVER
, $_GET
, $_POST
, $_FILES
, $_COOKIES
. У $_SESSION
особый случай, так как сессии работают через файлы и Cookies.
Но передавать такую большую пачку было бы дико:
$controller->doAction($_SERVER, $_GET, $_POST, $_FILES, $_COOKIES);
В синхронных PHP-приложениях можно и не передавать, а дёргать напрямую из этих массивов. Но в асинхронном режиме это не сработает.
Для удобства можно их совместить в массив:
$request = [ 'server' => $_SERVER, 'query' => $_GET, 'data' => $_POST, 'files' => $_FILES, 'cookies' => $_COOKIES, ]; $response = $controller->doAction($request);
чтобы можно было отвязаться от суперглобальных массивов и даже работать вообще без них.
Но вместо процедурной работы с переменными:
echo $request['server']['HTTP_HOST'] . ' ' . $request['server']['REQUEST_URI'];
удобнее работать с объектом $request
:
echo $request->getHost() . ' ' . $request->getUri();
Для этого в PHP тоже можно написать свой класс:
$request = new Request($_SERVER, $_GET, $_POST, $_FILES, $_COOKIES); $response = $controller->doAction($request);
И всё бы работало в реале и легко эмулировалось в тестах.
В экосистеме NodeJS в наборе http.*
есть устоявшиеся классы ClientRequest
IncomingMessage
и ServerResponse
и многие фреймворки пишут на них.
В PHP с унификацией HTTP сложнее, так как в ядре нет своих собственных HTTP-объектов. И фреймворки появились раньше, чем стало модно распространять компоненты через Composer.
PSR-7 как раз и решил ввести общие RequestInterface
, ServerRequestInterface
и ResponseInterface
, заточенные на работу без оглядки на суперглобальные массивы. И сделал их иммутабельными для безопасной работы в асинхронных EventLoop-ах.
Зачем ещё это можно обобщать, если каждому фреймворку со своими классами запроса и ответа и так хорошо? В общем смысле с HTTP связано много интересных вещей. Например, атутентификация, HTTP-кеширование. Да и сами контроллеры.
Можно придти к мысли, что сделав обобщённые Request и Response можно будет не делать общие утилиты и контроллеры.
Каждый фреймворк это делал по-разному. Но после прихода PSR-7 на данный момент имеем:
- популярный набор symfony/http-foundation, на котором работают Symfony и Laravel;
- универсальный psr/http-message для написания фреймворконезависимого кода;
- свои классы Request и Response других фреймворков вроде Yii;
В Symfony используются свои классы, но имеется мост symfony/psr-http-message-bridge для конвертации Symfony\HttpFoundation
в Psr\Message
и обратно. В итоге в Symfony и Laravel в контроллер можно принимать хоть HttpFoundation\Request
, хоть универсальный Psr\Message\Request
.
PSR-18: HTTP Client
На сайте жены по бисероплетению я сделал виджет вывода фотографий из Instagram через Instagram PHP Scrapper, подключающийся через mashape/unirest-php.
Другие библиотеки для доступа к чужим API разных SMS-провайдеров, рассылок и других сервисов работают то напрямую через CURL, то через file_get_contents
, то через Guzzle.
В итоге наставив кучу библиотек тоже получаем такой же зоопарк способов подключения. И чтобы банально указать им всем один прокси-сервер или добавить кэш приходится ковыряться в настройках каждого клиента и переопределять методы, где жёстко вписан curl.
Раз мы определились с общим Request, то в идеале для той же унификации сейчас появился повод перевести все библиотеки на использование PSR-18, чтобы им всем можно было передать один и тот же Psr\HttpClient
, чтобы я мог подсунуть им логирующий, проксирующий или кеширующий декоратор для HttpClientInterface
.
PSR-15: HTTP Server Request Handlers
Также не ясно зачем это нужно?
RequestHandlerInterface – не вижу ни одной причины менять его. Если нужно будет менять его – придется менять весь framework, т.к по сути – это его каркас.
MiddlewareInterface – в стандарте даже не имеет связи с RequestHandlerInterface. И в чем сложность – написать свой middleware внутри которого вызывать метод какого-либо подключенной зависимости?
PSR-17: HTTP Factories
Также – не ясна причина, из-за которой будет необходимо поменять request|response. Прокидывать внутрь проекта request|respone – плохо. Разве нет? И даже если нужно будет поменять их – при правильной логике придется поправить только в middleware и в контроллерах. Тот-же symfony до сих пор использует свой http foundation.
Вот мы и дошли до контроллеров и посредников.
Имея общие Request и Response помимо обобщённой работы в синхронных или асинхронных фреймворках мы можем обобщить и сами контроллеры с посредниками.
API своего проекта я сделал на микрофреймворке Zend Expressive. И мне понадобилось определять IP-адрес комментатора. Что мне делать? Можно сочинить самому цикл по обходу заголовков. Но вместо этого я подключил готовый IpAddress Middleware.
С появлением PSR-15 теперь можно безболезненно делиться наборами middleware единого формата. Например, я могу сочинить и из проекта в проект копировать свой TrailingSlashMiddleware
для автообрезки слеша в конце адреса страницы:
namespace App\Gateway\Http\Middleware; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\MiddlewareInterface; use Psr\Http\Server\RequestHandlerInterface; use Zend\Diactoros\Response\RedirectResponse; class TrailingSlashMiddleware implements MiddlewareInterface { public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface { $path = $request->getUri()->getPath(); if ($path === '/') { return $handler->handle($request); } $normalized = rtrim($path, '/'); if ($path !== $normalized) { return new RedirectResponse($request->getUri()->withPath($normalized)); } return $handler->handle($request); } }
Или выложу на GitHub. Но пока есть проблема. В PSR есть только интерфейс ResponseInterface
, но нет самого класса Response
. Я использую Zend\Diactoros\Response
из пакета zend-diactoros:
use Zend\Diactoros\Response\RedirectResponse; ... return new RedirectResponse($request->getUri()->withPath($normalized));
Но в другом проекте у меня вместо него Slim\Http\Response
из пакета slim/http и пришлось бы переписать:
use Slim\Http\Response; ... return (new Response(301))->withRedirect($request->getUri()->withPath($normalized));
И чтобы внутри универсальных Middleware не вписывать жёстко new Response
там вместо этого можем использовать фабрику:
namespace App\Gateway\Http\Middleware; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\RequestFactoryInterface; use Psr\Http\Server\MiddlewareInterface; use Psr\Http\Server\RequestHandlerInterface; class TrailingSlashMiddleware implements MiddlewareInterface { private $factory; public function __construct(ResponseFactoryInterface $factory) { $this->factory = $factory; } public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface { $path = $request->getUri()->getPath(); if ($path === '/') { return $handler->handle($request); } $normalized = rtrim($path, '/'); if ($path !== $normalized) { return $this->factory->createResponse(301) ->withHeader('Location', $request->getUri()->withPath($normalized)); } return $handler->handle($request); } }
Здесь мы создаём Response через метод createResponse
фабрики:
use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\RequestFactoryInterface; use Psr\Http\Server\MiddlewareInterface; use Psr\Http\Server\RequestHandlerInterface; return $this->factory->createResponse(301) ->withHeader('Location', $request->getUri()->withPath($normalized)); }
Теперь никакие классы Zend и Slim мы здесь не используем, поэтому TrailingSlashMiddleware
можно выложить на GitHub и подключать к любому PSR-15-совместимому фреймворку. Достаточно только подсунуть ему фабрику из zend-diactoros или slim/http.
Также не ясна причина, из-за которой будет необходимо поменять request и response.
Из вышеизложенного можем сделать вывод, что внутри своего проекта Request и Response мы менять не собираемся. Они меняются лишь когда мы наш TrailingSlashMiddleware
переносим из фреймворка во фреймворк или подключаем к нескольким проектам на разных фреймворках.
MiddlewareInterface позволяют делать универсальные переносимые middleware, которые можно выкладывать на GitHub и использовать в проектах на разных синхронных или асинхронных фреймворках.
RequestHandlerInterface аналогично бонусом позволяет делать фреймворконезависимые контроллеры. Но не для выкладывания, а для своего проекта. Получаем бонус в том, что можно начать проект на Zend Expressive, а потом перейти на Slim3 или Laravel практически не переписывая контроллеры и посредники.
Следовать или нет
Главный вопрос. Нужно ли всё это?
CodeStyle можно принять т.к у него нет побочного эффекта, и эти стандарты влияют только на форматирование самого кода. Вопрос возникает к остальным стандартам. Смущает стандартизация не только стиля программирования, но и способов реализации, от которой не видно настолько большой выгоды, о которой говорят.
Выгоды от PSR-ов не видны в своём проекте со своими компонентами, так как свой код мы можем сочинять как хотим. Когда каждый программист велосипедит свой Logger и Cache проблем не возникает.
Но как только начинаем работать с кучей чужих фреймворков и компонентов, так сразу сталкиваемся с горой проблем несовместимости чужих компонентов друг с другом и с нашими классами и фреймворком. И приходится в каждом ковыряться и сочинять адаптеры.
Здесь-то и становится видна польза универсальности и стандартизации для общемировой совместимости компонентов и фреймворков друг с другом.
Да, разные фреймворки следуют им в разной степени. Кто-то модифицирует свой код. Кто то прикладывает адаптеры, как Symfony выложила psr-bridge и убрав PSR-16 из компонета symfony/cache добавила к нему переходник Psr16Cache.
И меня беспокоит беспрекословное следование этим стандартам.
Если теперь при выборе очередной библиотеки с Packagist я увижу, что там лежит свой Logger, Container, Event Dispatcher или Cache, то мне станет грустно и я напишу issue с просьбой заменить их на PSR-овские, чтобы я и тысячи программистов с написанием адаптеров не мучались. Это разумно.
Не уверен. Обычно, адаптеры не содержат никакой логики, а являются просто мостом. Время затраченное на написание issue может легко сравниться со временем написания адаптера... Для корректной же смены данных частей, в.т.ч и фреймворка можно просто разделять код на слои. Не использовать Request\Response, передавать данные через DTO,
Так Framework Interoperability как раз и нужен для упрощения interoperability. Чтобы код был совместим из коробки без десятков адаптеров и DTO.
Но пол интернета кипит тем, что это именно стандарты и нужно следовать только им. Не следуешь – не нормальный программист.
- Если пишешь только для себя, то пишешь как хочешь. Даже хоть без Composer.
- Если пишешь что-то для публики и уместно следуешь общепринятому, то тебя хвалят.
- Если пишешь публично, но ничему не следуешь, то тебя ругают.
Логично.
Сегодня принято одно, завтра другое. Тот-же Symfony – вышел из FIG. А сколько недовольства было по PSR Event Dispatcher...
То, что уже принято уже кардинально не изменится. А Symfony настолько крут, что может себе позволить выйти из FIG и сделать свой набор Contracts. При этом он не следует многим PSR нативно, но тут же сам предоставляет к ним адаптеры. Недовольство - недовольством, но теперь его Cache и Event Dispatcher начнут чаще использовать не для Symfony-проектов.
Существует множество библиотек и компонентов, которые не используют PSR, но при этом достаточно известнее PHP-FIG.
Да, есть монополисты, которые ещё держатся. Но если посмотреть на некоторых, то:
- Логер Monolog сразу внедрил PSR-3 и теперь де-факто все подключают его.
- Guzzle впрыгнула в тренд и внедрила PSR-7. PSR-18 в пути, но давно есть готовый адаптер.
Легаси-проекты могут также приложить к себе готовый опциональный адаптер.
Интервьюировался недавно, в несколько крупных компаний недавно, и от PSR там было только – стандарты оформления кода, да composer (не PSR). Никаких Http, Middleware, Container и.т.д. PSR интерфейсов.
Они не следуют общепринятому, но зная о том, что проект достаточно известный - нельзя сказать о том, что там сидят некомпетентные люди.
Крупные Bitrix-оиды не пользуются нэймспейсами и Composer. Крупные WordPress-ники не пользуются Git и ООП. Каждый пользуется только тем, что ему нужно и не пользуется тем, что ему неинтересно.
Framework Interoperability предоставляет обобщения для облегчения переносимости кода и компонентов между фреймворками.
Поэтому если работаете с несколькими фреймворками, пилите микросервисы на микрофреймворках, публикуете компоненты и не хотите завязываться на вендоров, то вам это нужно. Если же у вас только один проект на одном стеке на всю жизнь, то и думать ни о чём не нужно.
Сейчас стало популярно делать легковесные API для JS-фронтенда. И там вместо полноценных фреймворков проще собрать всё на микрофреймворке из компонентов. Так там PSR-ы очень кстати.
Напоследок: больше всего беспокоит то, что PHP-FIG продвигает стандарты, а не полное общество. Если бы данные моменты обсуждались на php.net гораздо большим сообществом, и реализовывались в виде SPL, то я имел бы другое мнение.
В данном же я вижу лишь продвижение интересов определенной группы людей.
В PHP уже лежат заброшенные HTTP Interface с 2014-го и Request and Response с 2016-го. Обсуждайте на здоровье всем сообществом :)
А в PHP-FIG участвуют именно авторы фреймворков и библиотек, знающие это изнутри.
Так что PSR-ы – вещь опциональная, но иногда полезная.
Дмитрий, Вы даете развернутые ответы на вопрос "зачем нужно ...". Насколько я смог понять, автор писем тоже спрашивает, *почему* именно эти люди (разработчики фреймворков, Framework Interoperability Group) придумывают общие стандарты для всех PHP-программистов (PHP Standard Recommendations)?
Разработчики фреймворков имеют собственные интересы и цели, а в итоге, получается, они называют решения для своих (никто не спорит, важных для всех) проблем стандартом для всего мирового PHP-сообщества. В этом видится несогласованность, неясность.
Яркий пример того, что участники Framework Interoperability Group больше заботятся о себе и своих продуктах, чем об общих интересах сообщества — недавний уход Фабьена.
PHP-FIG изначально был группой, которая придумывала рекомендации для того, чтобы разработчики фреймворков больше делали общего и меньше велосипедили. Цели сделать какие-то глобальные стандарты не было и никто из PHP-FIG не называл их таковыми.
Но по факту сообщество восприняло эти рекомендации именно как стандарты и пришлось группу немного реформировать чтобы она была более репрезентативной. В неё вошли ещё представители сообществ, которые фреймворки не разрабатывают. Сейчас же в рабочую группу может войти каждый. Так что PHP-FIG не закрыт, хотите что-то поменять там, возможности есть.
Уход Symfony (год назад) — это, конечно, плохо как для PSR, так и для Symfony. FIG лишился довольно сильных разработчиков, которые приносили хорошие технические аргументы. Symfony лишился возможности влиять на PSR и теперь просто их запиливает как есть. Тот же PSR-14, на который ругался Fabien, запилили некоторое время спустя.
То есть тут вопроса два:
1. Об интересах которого сообщества мы говорим. О Symfony или о PHP в целом?
2. PHP-FIG ли заботится об интересах своего продукта, а не PHP в целом?
В PSR-11 get() нужен не для того, чтобы его вызывать в userland-коде, а для того чтобы написать то, что будет как раз рассовывать объекты из контейнера в нужные места как, например, injector.
В PSR-7 главный профит в облегчении функционального тестирования без поднятия полного окружения и возможности запуска под нетрадиционными окружениями типа Swoole или RoadRunner (это как раз замена реализации).
Александр, про has/get в PSR-11 Вы, конечно, правы, и прокидывать контейнер тоже не дело (если это не ядро приложения), но автор письма говорит немного о другом и в том я его поддерживаю.
Он пишет о том, что нельзя так просто взять и заменить контейнер, ибо на стадии заполнения контейнера все контейнеры разные. Пробуя разные реализации контейнеров я сам с этим сталкивался. Хотя тот же контейнер Laravel даже PSR-11-му в полной мере не соответствует
Я вижу проблему PSR в том, что они не являются "необходимыми и достаточным" в полной мере.
На примере PSR-11: имеем один интерфейс ContainerInterface, в нём имеется has() и get() - необходимо и достаточно ли этого?
Если текущие реализации контейнеров в полной мере не взаимозаменяемы, то не достаточно. Возможно, как и в случае с PSR-7/17, проблему можно было бы решить с помощью ContainreFactoryInteface, который определил бы порядок заполнения. Но мне кажется почти нереальным подведение всех под одну гребёнку в этом вопросе
Вопрос необходимости has/get тоже остаётся открытым. Есть те, кто считает, что эти методы вообще не нужны, а я, например, думаю, что неплохо было бы ещё и call() добавить, ибо в разных реализациях он и так есть. Часто в фреймворках эти has/get и используются только для вызова методов и каждый пишет свой костыль-аналог call()
На примере PSR-3 (Logger): не предусмотрены ошибки самого логгера. От того Monolog вроде и соответствует PSR-3, но самого PSR-3 не достаточно в полной мере для логгера. И что, если логгеру запрещено бросать Exception, то теперь каждый логгер будет придумывать свой потенциально несовместимый exceptionHandler, не предусмотренный в интерфейсе?
Для контейнера PSR-11 вполне достаточен чтобы отвязать фреймворк от него. Это даст возможность конечному пользователю выбрать, какой контейнер использовать. И да, это не даст возможности заменить без боли контейнер вместе с его конфигом. Конфиг придётся переписывать.
Спасибо, интересная статья. Да, жаль что Symfony ушли из PSR.
Крупные WordPress-ники не пользуются Git и ООП. - да всё там есть при желании
Загонять вордпресс в гит — та ещё боль. Из-за плагинов, которые устанавливаются онлайн и при этом меняют структуру БД очень непросто синхронизировать прод с девом. Вряд ли там кто-то продумал нормальную систему миграций, а если и продумал, то вряд ли все производители плагинов ей следуют.
Дмитрий, а в других языках существует, что-то подобное как PSR-3, PSR-6 и т.д. ? В Java например?
Я не очень понимаю, зачем может потребоваться прокидывать свой код внутрь чужой библиотеки. Я считаю, что библиотеку необходимо рассматривать как black box. Тем более, что обычно мы не хотим напрямую вызывать Locator.php в своем коде и пишем для него адаптер и в этом адаптере можно добавить кеширование/логирование независимо от того есть это в библиотеке или нет. Непонятно зачем для этого лезть внутрь библиотеки. Ни в одном авторитетном источнике не встречал подобных практик. Есть такие?
В Java есть исторически сложившаяся экосистема Apache Commons.
> Пишем для него адаптер и в этом адаптере можно добавить кеширование/логирование независимо от того есть это в библиотеке или нет.
При подключении компонента Locator будет удобно, если к interface Locator будет в комплекте идти готовый прокси PsrCacheLocator и декоратор PsrLogLocator.
Как раз это я делаю сейчас в новой серии по IP Locator.
> Ни в одном авторитетном источнике не встречал подобных практик. Есть такие?
Например, к symfony/http-client в комплете есть прокси CachingHttpClient и адаптер Psr18Client.
Крупные Bitrix-оиды не пользуются нэймспейсами
Не правда! Работаю разработчиком в битриксе. Давно активно используем неймспейсы.
Из psr нам подходит максимум code style. У нас есть свои стандарты близкие к "псровскому". Psr нужен для опенс соурса.
Крупные Bitrix-оиды не пользуются нэймспейсами - познания автора о рынке слабы, и видимо он не сталкивался особо с крупными =)
У мелких да, подходы страдают. Как в целом и любых мелких.
Взяли свои особо крупные и на D7 переписали?
Прямо-таки объектную ORM в 2018-ом сделали и symfony/console через свой Composer для генерации к ней аннотаций подключили. Прогрессируют :)
PSR и весь подход и философия которые в нем - ошибочны.
composer - навязанный костыль, которого и быть-то не должно.
то же самое касается и все сферы PHP, все эти проприетарные npm.
разработка ПО скатилась в дно.
Всё, ребята, расходимся.
Здравствуйте!
Никак не врублюсь как работатет psr-15 middleware в slim4
Читаю описание:
1. Поясните пожалуйста, откуда взялся $handler ?
2. Кто и где назначает значение этой переменной? В коде к ней обращаются, но откуда взялся сам этот объект и что он из себя представляет?
А то магия какая то: используем в файле аргумент функции , а присваивание аргументу значения - неизвестно где.
Middleware - это посредники, из которых выстраивают цепь перед экшеном (контроллером):
В них вторым параметром сам фреймворк передаёт следующий элемент.
И как раз PSR-15 описывает интерфейсы для таких экшенов и посредников:
Подробно работу со всем этим мы рассматривали в видео про middleware
просто супер объяснение, спасибо