Выпуск №16 — виртуальные пакеты Composer

Виртуальные пакеты в Composer — способ указать зависимость не от конкретного пакета, а от любого пакета «предоставляющего» этот виртуальный:
Composer & virtual packages
Composer «provide» and dependency inversion
Breaking Free from Guzzle5 with PHP-HTTP and HTTPlug

Всем привет!

Вы слушаете «Пятиминутку PHP», выпуск номер 16 — еженедельный подкаст о новостях из мира PHP, интересных постах в блогах и современных подходах к разработке.

Начну с опровержений! В прошлом выпуске подкаста я сказал, что WebStorm стоит дороже, чем PhpStorm — это не так, всё наоборот, я запутался в ценах на сайте JetBrains, посыпаю голову пеплом. Впрочем, моя рекомендация пользователям WebStorm: «забейте на NodeJs, переходите на PHP» — остаётся в силе!

Теперь немного интересненького. Знали ли вы про виртуальные пакеты в Composer? В документации на сайте getcomposer.org в разделе Librares в подразделе «Platform packages» упоминается этот термин, но там речь идёт о возможности указать зависимость от конкретной версии PHP или HHVM или каких-либо расширений. И это не то, о чём я сейчас хочу вам рассказать. Речь пойдёт о других виртуальных пакетах. Попробуйте на сайте packagist.org ввести в поисковую строку слово «implementation» и вы увидите много пакетов с зелёным комментарием «Virtual Package».

Концептуально: виртуальный пакет по сравнению с «обычным» представляет собой что-то похожее на PHP интерфейс по отношению к классу. Когда мы используем интерфейсы в PHP коде? Когда хотим указать зависимости, но не от конкретной реализации, а более тонко — от некоего интерфейса, который может иметь различные реализации. Это особенно полезно при разработке библиотек.

Виртуальные пакеты в Composer имеют ту же цель — указание зависимостей, но не от конкретного пакета, а от целого множества пакетов объединённых неким обобщением, некоей меткой — виртуальным пакетом. Если реальный пакет является реализацией некоего виртуального, то в composer.json в секции «provide» автор указывает имя виртуального пакета.

Попробую пояснить на примере. Допустим, я разрабатываю некоторую библиотеку и мне нужен логгер. И, поскольку сейчас самые модные логгеры, это PSR-3 совместимые, буду ориентироваться на PSR-3 интерфейс. С PHP кодом всё просто: в конструкторе класса я указываю зависимость от LoggerInterface (это тот самый PSR-3 интерфейс) и теперь пользователи библиотеки обязаны будут предоставить мне какой-нибудь логгер, реализующий этот интерфейс. Переходим к Composer. Раньше (до виртуальных пакетов) я бы написал в composer.json, что моя библиотека зависит от пакета psr/log, который содержит в себе LoggerInterface. И эта строчка в composer.json файле даст намёк пользователям моей библиотеки, что от них потребуется какой-то PSR-3 совместимый логгер. Но мы пойдём дальше — от намёков, к прямым указаниям! Я могу написать в composer.json в секции require пакет под названием psr/log-implementation — это виртуальный пакет. Когда разработчик реального приложения добавит мою библиотеку к себе в зависимости в composer.json и выполнит команду composer install, то composer увидит, что моя библиотека зависит от некоего виртуального пакета psr/log-implementation, и пойдёт искать по всем другим пакетам в дереве зависимостей текущего проекта, а нет ли среди них такого, в котором присутствует секция provide с указанием psr/log-implementation? В частности, если разработчик приложения добавит к себе в зависимости знаменитый monolog или любой другой из 20 пакетов, у которых в composer.json написано «provide»: {«psr/log-implementation»: «1.0.0»}, то установка завершится успешно. В противном случае, composer выдаст ошибку, что зависимости не удовлетворены, т.к. не удалось найти пакет psr/log-implementation.

В своём рассказе я привёл классический пример с логгером, в частности со стандартном PSR-3 который нам дал и PHP интерфейс и виртуальный пакет для composer и это на самом деле могло вас запутать. Виртуальные пакеты в composer никак не привязаны и не требуют наличия каких-либо PHP интерфейсов — это совершенно ортогональные системы для управления зависимостями. Виртуальные пакеты помогут более жестко указать зависимости на уровне composer файлов, чтобы всё упало ещё на стадии composer install.

Вот вам другой пример, который я придумал только что. Я — Ричард Столман, апологет свободного ПО! Внезапно, я решил написать библиотеку на PHP и моя библиотека требует какую-то реализацию кеша. Причём только такую, которая под лицензией GPL, естественно. Поэтому я добавляю в свой composer.json зависимость от виртуального пакета gnu/cache, а потом напишу в блоге статью с заголовком «правильный кэш — это свободный кеш!», где попрошу авторов всех библиотек кеширования, которые под лицензией GPL, добавить в их composer.json строчку «provide»: {«gnu/cache»: «1.0.0»}. При установке моей библиотеки через composer, последний потребует от пользователя установить любой пакет, который предоставляет (provide) виртуальный пакет gnu/cache, и, если в дереве зависимостей проекта уже есть какой-нибудь из gnu/cache совместимых, то зависимости будут разрешены успешно!

Как видите, в данном примере я ни слова не сказал о PHP интерфейсах и реальной совместимости на уровне кода. Виртуальные пакеты — это совершенно ортогональная система, которая лишь добавляет ещё один слой проверок на совместимость на уровне composer пакетов.

Дам ссылки на статьи по теме.

Первая статья так и называется: «Composer & virtual packages» — объяснение виртуальных пакетов с наглядными примерами composer.json файлов.

Вторая статья: «Composer «provide» and dependency inversion» — примечательна тем, что автор раскрывает некоторые недостатки и проблемы виртуальных пакетов.

Третья статья: «Breaking Free from Guzzle5 with PHP-HTTP and HTTPlug» — реальный пример из жизни, когда разработчик некоего SDK имел жесткую зависимость от библиотеки Guzzle версии 5 (напомню, это популярный HTTP клиент).

С появлением PSR-7 (HTTP message interfaces) и Guzzle 6, он захотел дать своим пользователям возможность использовать любой PSR-7 совместимый HTTP клиент, а не только Guzzle 6. Для этого автор статьи описал в своём composer.json зависимость от виртуального пакета php-http/client-implementation. Теперь пользователь SDK может использовать любой HTTP клиент, который предоставляет этот виртуальный пакет. На деле же, если же выбор пользователя SDK всё-таки пал на Guzzle 6, то обнаруживается, что в библиотеке Guzzle 6 в composer.json нет секции provide, т.е. Guzzle 6 не предоставляет этот виртуальный пакет, поэтому пользователю SDK придётся установить ещё специальный guzzle6-adapter, который одним концом совместим с Guzzle 6, а другим предоставляет требуемый виртуальный пакет php-http/client-implementation. Вот такая цепочка зависимостей получилась, зато гибко и одновременно строго на уровне описания зависимостей для composer!

Вы уже скучаете по временам PHP4, когда запросы к базе делались из глобального синглтона прямо в шаблоне? То ли ещё будет!

Ну что же, надеюсь, мне удалось объяснить суть виртуальных пакетов в аудио-формате и этот выпуск оказался полезным. Предлагайте свои темы и до встречи на следующей неделе!

  • Евгений Кудесник Сергеевич

    Почему бросать НодЖс? в чем смысл?