В данном посте речь пойдет о пакетах PHP и об алкогольных зависимостях. Вернее, о так называемых опциональных или предложенных зависимостях (optional dependencies, suggest/dev-dependencies), которые определяются в composer.json.
Что такое зависимость?
Для начала разберемся с тем, что такое зависимость и о чем вообще речь. Есть следующий код:
namespace GaufretteAdapter;
use GaufretteAdapter;
use MongoGridFS;
class GridFS implements Adapter
{
private $gridFS;
public function __construct(MongoGridFS $gridFS)
{
$this->gridFS = $gridFS;
}
public function read($key)
{
$file = $this->find($key);
return ($file) ? $file->getBytes() : false;
}
}
Класс GridFS — часть библиотеки абстрактной файловой системы Gaufrette, которую я в какой-то степени изменил. Для определения всех зависимостей этого кусочка кода мы должны задать себе следующие вопросы:
- Что нужно, чтобы данный код заработал?
Но нужно подумать еще вот о чём:
- Какая версия PHP потребуется для запуска, чтобы не получать ошибок?
- Возможно, потребуется еще и какую-то конкретную версию ставить?
- Какие расширения PHP понадобятся?
- Какие PEAR-библиотеки нужно установить?
- Каких пакетов не хватает?
Возвращаясь к классу GridFS, версия PHP должна быть не ниже 5.3, потому что используются неймспейсы. Также, необходим класс
, который является mongo-расширением к PHP и доступен лишь с версии 0.9.0 этого расширения. Кажется все, создаем composer.json:
MongoGridFS
{
...,
"require": {
"php": ">=5.3",
"ext-mongo": ">=0.9.0"
}
..
}
Этого списка достаточно и теперь вроде как ничто не останавливает нас от использования данного класса в наших приложениях… Увы, это не так.
Действительный список зависимостей knplabs/gaufrette
Как я уже говорил, GridFS — часть библиотеки Gaufrette, предоставляющей слой абстрактной файловой системы, для хранения файлов на различных типах файловых систем без заботы о деталях используемой файловой системы. Взглянем на composer.json этой библиотеки:
{
"name": "knplabs/gaufrette",
"require": {
"php": ">=5.3.2"
},
"require-dev": {
...
},
"suggest": {
...
"amazonwebservices/aws-sdk-for-php": "to use the legacy Amazon S3 adapters",
"phpseclib/phpseclib": "to use the SFTP",
"doctrine/dbal": "to use the Doctrine DBAL adapter",
"microsoft/windowsazure": "to use Microsoft Azure Blob Storage adapter",
"ext-zip": "to use the Zip adapter",
"ext-apc": "to use the APC adapter",
"ext-curl": "*",
"ext-mbstring": "*",
"ext-mongo": "*",
"ext-fileinfo": "*"
},
...
}
И вот он, первый сюрприз! Почти все то, что выяснилось о зависимостях ранее — никуда не годится, потому что библиотека говорит, что ей всего лишь нужен PHP версии не ниже 5.3.2, а все остальное — по вкусу, опционально или только для dev целей — называйте это как хотите.
Конечно, люди, давно использующие Composer или Packagist уже привыкли к таким вещам. Но это просто неправильный подход. Как мы выяснили ранее, ext-mongo это действительная зависимость (true dependency) класса GridFS, однако composer.json говорит нам об обратном.
Все это означает лишь то, что если мы захотим использовать этот класс в нашем проекте, то недостаточно просто воспользоваться пакетом knplabs/gaufrette
. Я также сделал необходимым ext-mongo в моем проекте, что является ошибкой: это не мой проект требует расширение mongo, а пакет knplabs/gaufrette
. Более того, как мне узнать какую версию ext-mongo мне выбирать? Те зависимости, которые указаны в блоке suggest не говорят об этом, заставляя выбирать именно меня.
Просто это другой пакет
knplabs/gaufrette
не единственный, кто поступает таким образом, выдавая реальные зависимости за предложенные. Владельцам пакетов это удобно — добавить разные классы, которые, возможно, понадобятся пользователям. Или не понадобятся. Поэтому, если использование этих классов опционально, то и их зависимости также опциональны. Однако владельцы пакетов забывают, что зависимости никогда не бывают опциональными. Они всегда обязательны, поскольку код просто не заработает без них.
Решение
Что должны в таком случае сделать разработчики пакетов? Разделить их. В случае с knplabs/gaufrette
это значит, что должен быть пакет knplabs/gaufrette
, содержащий весь общий код, необходимый для абстрагирования от файловой системы. И затем каждый отдельный адаптер, такой как GridFS, должен жить в своем пакете, например, knplabs/gaufrette-mongo-gridfs
. И у него уже будут свои зависимости:
{
...,
"require": {
"php": ">=5.3",
"knplabs/gaufrette": "~0.1"
"ext-mongo": ">=0.9.0"
}
}
И все, нигде нет скрытых зависимостей, все они необходимые.
Сам же knplabs/gaufrette
в свою очередь вообще больше не имеет зависимостй, а пакеты с адаптерами как раз являются предложенными:
{
"require": {
"php": ">=5.3.2"
},
"suggested": {
"knplabs/gaufrette-mongo-gridfs": "For storing files using Mongo GridFS",
...
}
}
Такой подход целый ряд преимуществ:
- Основной пакет становится более стабильным. Нет причин что-то менять, так как все изменяемые части внутри адаптеров.
- Разные пакеты могут иметь разных разработчиков. Например,
knplabs/gaufrette-mongo-gridfs
может дорабатывать кто-то, кто очень хорошо знает MongoDB. - Пользователям не нужно следить за обновлениями частей библиотеки, которые они не используют.
- Пользователям не придется вручную добавлять дополнительные зависимости в свои проекты.
В следующий раз, добавляя предложенные зависимости в свой пакет, подумайте о том, действительная ли это зависимость в этом пакете? Затем разделите пакет и укажите эту зависимость в нем как необходимую. Если весь код в основном пакете работает без этой предложенной зависимости, то в таком случае ее можно указать как предложенную.
Автор: yTko