Опциональные зависимости не нужны

Original author: Matthias Noback
  • Translation
В данном посте речь пойдет о пакетах PHP и об алкогольных зависимостях. Вернее, о так называемых опциональных или предложенных зависимостях (optional dependencies, suggest/dev-dependencies), которые определяются в composer.json.

Что такое зависимость?


Для начала разберемся с тем, что такое зависимость и о чем вообще речь. Есть следующий код:

namespace Gaufrette\Adapter;
 
use Gaufrette\Adapter;
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, которую я в какой-то степени изменил. Для определения всех зависимостей этого кусочка кода мы должны задать себе следующие вопросы:

  • Что нужно, чтобы данный код заработал?


Но нужно подумать еще вот о чём:

  1. Какая версия PHP потребуется для запуска, чтобы не получать ошибок?
  2. Возможно, потребуется еще и какую-то конкретную версию ставить?
  3. Какие расширения PHP понадобятся?
  4. Какие PEAR-библиотеки нужно установить?
  5. Каких пакетов не хватает?

Возвращаясь к классу GridFS, версия PHP должна быть не ниже 5.3, потому что используются неймспейсы. Также, необходим класс \MongoGridFS, который является mongo-расширением к PHP и доступен лишь с версии 0.9.0 этого расширения. Кажется все, создаем composer.json:

{
    ...,
    "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.
  • Пользователям не нужно следить за обновлениями частей библиотеки, которые они не используют.
  • Пользователям не придется вручную добавлять дополнительные зависимости в свои проекты.

В следующий раз, добавляя предложенные зависимости в свой пакет, подумайте о том, действительная ли это зависимость в этом пакете? Затем разделите пакет и укажите эту зависимость в нем как необходимую. Если весь код в основном пакете работает без этой предложенной зависимости, то в таком случае ее можно указать как предложенную.

Similar posts

AdBlock has stolen the banner, but banners are not teeth — they will be back

More
Ads

Comments 6

    0
    Очень правильная статья о наболевшем. Периодически такие пакеты попадаются и «из коробки» вообще никак не работают, пока сам не поставишь зависимости вручную.
      0
      Этот посыл, кстати, просматривается и в оригинале.
      Там в обсуждении говорят, что это оверхэд, однако он (автор поста) говорит, что наверняка есть какие-то инструменты для всего этого и вообще занимает это все не больше часа, но экономит кучу времени другим.

      И что забавно — там же, в обсуждении оригинала, основной разработчик Gaufrette полностью поддержал пост и сказал, что такое должно быть с каждым их адаптером, но пока этого трудно добиться.

      P.S. Надеюсь, нигде не попутал в переводе, особенно в последнем абзаце.
      Кстати ссылка на оригинал на хабре появилась раньше, чем на phpweekly :)
      0
      Мне особо бесит то что среда сборки(выкачивания зависимостей) и деплоя вовсе не обязательно должна соответствовать среде исполнения(использующей зависимости). Но composer нам не дает норм.способа как-то убрать проблему, лишь целиком не качать вложенные зависимости.
      Убедительная просьба — правильно пишите документацию к пакетам, забудьте про все это мутные ext-…
        –3
        На мой взгляд автор не совсем прав. Есть множество подобных библиотек, которые создают обертки, которых нет в php. Например аналогом является doctrine/cache. Если для таких библиотек все провайдеры выносить в отдельные библиотеки — никакого времени на поддержку не хватит.
        Я согласен что для конкретного случая банально не хватает документации — вы всегда можете помочь, отправив PR на ее добавление, изменения описания в suggest у composer.json.

        Все что нужно сделать — добавить описание здесь github.com/KnpLabs/Gaufrette/blob/master/composer.json#L75, вместо *

        Ну и если человек использует GridFs, он должен понимать, что это FS на базе монги, и есть огромная вероятность, что понадобится ext-mongo
          0
          Тут вроде куда глубже проблема уходит — разделение подобного рода это вопрос скорее умения выделять самостоятельные части из огромной библиотеки, а composer просто косвенно страдает от этого
          0
          Справедливая мысль. По-моему, есть один случай, когда зависимость всё же может быть опциональной. Это когда есть несколько либ с одним интерфейсом и нужно выбрать одну обязательно. Т.е. не совсем опциональная зависимость, а, скорее выбор одной обязательно из нескольких предложенных. Приведу пример не на PHP, но тут подойдёт. В ноде я мог бы выбрать между частично совместимыми пакетами Q/bluebird/другая promise-либа, или LoDash-compat/underscore, jQuery/Zepto. Но, в npm такой возможности тоже не обнаружено (там есть просто «опциональные» зависимости).

          Only users with full accounts can post comments. Log in, please.