Наконец, нашлось время разобраться, что собой представляет PSR-7. Как известно, на сегодняшний день существует публичный интерфейс https://github.com/php-fig/http-message и две его реализации:
Итак, что можно сказать о реализациях?
Метод Psr\Http\Message\UriInterface::setScheme() предусматривает, что реализация должна поддерживать схемы «http» и «https» и может также поддерживать и другие схемы. То есть, если мне нужно быстро создать ссылку на ftp — ресусрс, mailto или file я мог бы воспользоваться уже существующим классом Uri.
К сожалению, ни одна из реализаций подобных возможностей не предоставляет. Массив в теле класса с двумя методами «http» и «https» тоскливо смотрится, учитывая какие титаны это писали.
Кроме того, ни в интерфейсе, ни в реализациях ничего не сказано о том, что метод UriInterface::withUserInfo для протокола http, по сути, является @depricated, о чем говорится в здесь.
Классы, реализующие Psr\Http\Message\StreamInterface являются, по сути, обертками для стандартных потоков PHP. Вот как аргументирует использование потоков автор Guzzle в своей статье «Несколько слов о более высоком уровне (абстракции) потоков PHP в PSR-7»:
Тем не менее, из нереализованных на сегодняшний день в Guzzle интерфейсов остается Psr\Http\Message\UploadedFileInterface.
То есть, непонятно, в чем же здесь преимущество потоков, если загрузка файлов, о которой так много говорили, осталась нереализованной. В реализации Matthew O'Phinney метод ::moveTo() реализовывается с помощью старой доброй нативной PHP фунции move_uploaded_file().
Все уже, наверное, знают, что особенностью HTTP прослойки от PSR-7 является неизменность объектов. Вот только в двух существующих реализациях это выполняется различным образом.
Guzzle внутри методов с приставкой with проверяет, изменилось ли состояние объекта. Если оно изменилось — возвращает новый объект. Если не изменилось — возвращает экземпляр текущего. Реализация Matthew O'Phinney всегда возвращает новый объект, не проверяя при этом состояние.
Нужно сказать, что здесь во многом виноваты сами интерфейсы Psr, поскольку в описании, например UriInterface::withFragment:
А вот в return того же метода:
Конечно, можно сказать, что я придираюсь, но нужно же как-то обьяснить разницу в реализации.
Честно говоря, реализации этого интерфейса озадачили больше всего. Есть некоторые огрехи в самих интерфейсах, но это ерунда по сравнению с реализациями.
Так, например, метод RequestInterface::withUri описывает использование параметра $preserveHost, приведу в оригинале:
Может быть я чего-то не понял, но Matthew O'Phinney в реализации этого метода дает следующие комментарии:
То есть логика изменена на полностью противоположную. И самое интересное, Guzzle делает то же самое, но без всяких комментариев.
В общем, если кратко по итогам — то на сегодняшний день весь стандарт, что интерфейсы, что реализация оставляет некоторое ощущение «сырости», хотя, возможно, это просто мое субьективное мнение.
Чтобы быть совсем уж точным, сейчас проект от Matthew O'Phinney https://github.com/phly/http приостановлен и начат ребрендинг. Хотя, по сути — это не ребрендинг, а очень серьезный рефакторинг. И, нужно сказать, что явные ошибки залатываются качественно и оперативно. Проект развивается под крылом Zend Framework, посмотреть можно здесь. Хотя, это не перекрывает некоторые архитектурные недоработки самого стандарта. Поэтому я думаю, что в самом стандарте еще предстоят изменения.
- одна от автора Guzzle: https://github.com/guzzle/psr7
- и вторая от Matthew O'Phinney: https://github.com/phly/http
Итак, что можно сказать о реализациях?
Uri
Метод Psr\Http\Message\UriInterface::setScheme() предусматривает, что реализация должна поддерживать схемы «http» и «https» и может также поддерживать и другие схемы. То есть, если мне нужно быстро создать ссылку на ftp — ресусрс, mailto или file я мог бы воспользоваться уже существующим классом Uri.
К сожалению, ни одна из реализаций подобных возможностей не предоставляет. Массив в теле класса с двумя методами «http» и «https» тоскливо смотрится, учитывая какие титаны это писали.
Кроме того, ни в интерфейсе, ни в реализациях ничего не сказано о том, что метод UriInterface::withUserInfo для протокола http, по сути, является @depricated, о чем говорится в здесь.
Streams
Классы, реализующие Psr\Http\Message\StreamInterface являются, по сути, обертками для стандартных потоков PHP. Вот как аргументирует использование потоков автор Guzzle в своей статье «Несколько слов о более высоком уровне (абстракции) потоков PHP в PSR-7»:
Самые горячие споры до сих пор были вокруг решения о том, как будет представлено тело HTTP-сообщения…
… Есть несколько других вариантов, которые могли бы быть использованы для представления HTTP-сообщения:
— строка
— итераторы
— потоки PHP
Использование строки потребовало бы, чтобы все содержимое сообщения загружалось бы в память. Это не годится, если вы взаимодействуете с веб-сервисами вроде Amazon S3, где является обыденной загрузка гигабайт данных в качестве объектов хранения.
Могло бы сработать использование итераторов, но это может привести к значительному снижению производительности из-за того, что каждый вызов next() будет возвращать только один байт, что приведет к огромному количеству вызовов метода при загрузке больших файлов. Кроме того, это предоставляло бы представление тела сообщения в режиме «только для чтения».
Тем не менее, из нереализованных на сегодняшний день в Guzzle интерфейсов остается Psr\Http\Message\UploadedFileInterface.
То есть, непонятно, в чем же здесь преимущество потоков, если загрузка файлов, о которой так много говорили, осталась нереализованной. В реализации Matthew O'Phinney метод ::moveTo() реализовывается с помощью старой доброй нативной PHP фунции move_uploaded_file().
Immutable
Все уже, наверное, знают, что особенностью HTTP прослойки от PSR-7 является неизменность объектов. Вот только в двух существующих реализациях это выполняется различным образом.
Guzzle внутри методов с приставкой with проверяет, изменилось ли состояние объекта. Если оно изменилось — возвращает новый объект. Если не изменилось — возвращает экземпляр текущего. Реализация Matthew O'Phinney всегда возвращает новый объект, не проверяя при этом состояние.
Нужно сказать, что здесь во многом виноваты сами интерфейсы Psr, поскольку в описании, например UriInterface::withFragment:
Return an instance with the specified URI fragment
А вот в return того же метода:
A new instance with the specified fragment
Конечно, можно сказать, что я придираюсь, но нужно же как-то обьяснить разницу в реализации.
RequestInterface
Честно говоря, реализации этого интерфейса озадачили больше всего. Есть некоторые огрехи в самих интерфейсах, но это ерунда по сравнению с реализациями.
Так, например, метод RequestInterface::withUri описывает использование параметра $preserveHost, приведу в оригинале:
When `$preserveHost` is set to `true`, this method interacts with the Host header in the following ways:
— If the Host header is missing or empty, and the new URI contains a host component, this method MUST update the Host header in the returned request.
— If the Host header is missing or empty, and the new URI does not contain a host component, this method MUST NOT update the Host header in the returned request.
— If a Host header is present and non-empty, this method MUST NOT update the Host header in the returned request.
Может быть я чего-то не понял, но Matthew O'Phinney в реализации этого метода дает следующие комментарии:
When `$preserveHost` is set to `true`, the returned request will not update the Host header of the returned message — even if the message contains no Host header.
То есть логика изменена на полностью противоположную. И самое интересное, Guzzle делает то же самое, но без всяких комментариев.
В общем, если кратко по итогам — то на сегодняшний день весь стандарт, что интерфейсы, что реализация оставляет некоторое ощущение «сырости», хотя, возможно, это просто мое субьективное мнение.
Чтобы быть совсем уж точным, сейчас проект от Matthew O'Phinney https://github.com/phly/http приостановлен и начат ребрендинг. Хотя, по сути — это не ребрендинг, а очень серьезный рефакторинг. И, нужно сказать, что явные ошибки залатываются качественно и оперативно. Проект развивается под крылом Zend Framework, посмотреть можно здесь. Хотя, это не перекрывает некоторые архитектурные недоработки самого стандарта. Поэтому я думаю, что в самом стандарте еще предстоят изменения.