Четыре столпа 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-ы – вещь опциональная, но иногда полезная.

Комментарии

 

Andrey B.

Дмитрий, Вы даете развернутые ответы на вопрос "зачем нужно ...". Насколько я смог понять, автор писем тоже спрашивает, *почему* именно эти люди (разработчики фреймворков, 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 (это как раз замена реализации).

Ответить

 

Sergei Maslovskiy

Спасибо, интересная статья. Да, жаль что Symfony ушли из PSR.

Ответить

 

Vladimir Kamuz

Крупные WordPress-ники не пользуются Git и ООП. - да всё там есть при желании

Ответить

Оставить комментарий

Войти | Завести аккаунт


(никто не увидит)



Можно использовать теги <p> <ul> <li> <b> <i> <a> <pre>