Кэшинг пакетов для Composer

    Используя современный подход к разработке проектов начинаешь пользоваться прелестями менеджера пакетов, в случаe с разработкой на PHP это Composer. В данной статье мы кратко рассмотрим Composer и далее речь пойдёт о настройке локального кэша пакетов.

    Данное описание ни в коем случае не претендует на полноту, а лишь даёт краткое представление о данном инструменте.
    Описание Composer и пример с Silex.
    Возьмём описания из официальной документации:
    Composer — является инструментом для управления зависимостями в PHP. Позволяет объявлять зависимые библиотеки необходимые для проекта, и установить их в ваш проект. Composer работает в связке с Packagist.
    Packagist — хранилище Composer пакетов. Позволяет вам находить нужные пакеты, а Composer узнавать откуда взять исходники пакетов.

    В прочем Composer не ограничен Packagist хранилищем и вполне позволяет настроить зависимые пакеты из svn, git, pear. Пакеты должны содержать правильно настроенный конфигурационный файл (composer.json). Хотя и это не обязательно, если конфигурационный файл отсутствует в нужном вам пакете, то вам самим придётся его прописать уже в конфигурационном файле зависимостей вашего проекта. Пример

    Конфигурация зависимостей проекта происходит через Json конфигурационный файл (composer.json). Суть сводится к нахождению нужного пакета на Packagist, добавления соответствующей записи в composer.json и запуска Composer, который сам скачает и настроит пакет (настроит по мере прописанных действий разработчиком пакета). Пример.

    Посмотрим пример минимальной установки фреймворка Silex на (debian/ubuntu):
    mkdir /path/to/your/webroot/silex; cd /path/to/your/webroot/silex
    sudo apt-get install git php5 curl; curl -sS https://getcomposer.org/installer | php
    


    Этой командой вы скачаете Composer в формате PHP archive — composer.phar. Далее создаём минимальный composer.json:
    echo '{"require": {"silex/silex": "~1.1"} }' > composer.json
    php composer.phar install
    


    Далее создаём web/index.php:
    <?php
    // web/index.php
    require_once __DIR__.'/../vendor/autoload.php';
    
    $app = new Silex\Application();
    // definitions
    $app->run();
    


    Всё, фреймворк Silex готов, установку и запуск веб сервера опустим, в интернете полно готовых описаний.


    Проблема с менеджером пакетов заключается в том, что слишком часто приходится устанавливать одни и те же пакеты. Каждая новая установка проекта, это обращение к packagist.org для нахождения источников зависимостей вашего пакета, зависимостей зависимостей и т.д. и дальнейшее скачивание необходимых пакетов. Справедливости ради стоит отметить, что у Composer есть встроенный кэш, но он хранится под ~/.composer/cache, и соответственно индивидуален для каждого пользователя, не говоря уже об отдельных средах для разработчиков, тестеров, QA, продакшн. И везде скачиваются одни и те же пакеты.

    Большинство пакетов находятся на гитхабе, откуда скачиваются достаточно быстро чтоб не заворачиваться на локальные кэш. Но когда github в очередной раз недоступен/тормозит из-за DDos, да ещё и размер зависимостей достигает сотен мегабайт — это становится проблемой. Github недоступен — работа стоит. Я предлагаю и в данный момент использую утилиту Satis.

    Satis — статическое хранилище Composer пакетов, ультра-легкая, статическая версии Packagist, может быть использована для размещения приватных пакетов вашей компании, или своих собственных.

    Как видно из официального описания — главной целью Satis является возможность подключения приватных пакетов. Но так же Satis, имеет возможность скачивать пакеты из того же Packagist, хранить скаченные пакеты в zip или tar, а также раздавать их. Это нам и нужно. И так приступим к установке:
    cd /path/to/your/webroot
    sudo apt-get install php5 git curl; curl -sS https://getcomposer.org/installer | php
    php composer.phar create-project composer/satis --stability=dev; cd satis
    


    Далее создаём конфиг satis.json:
    {
       "name":"Project name",
       "homepage":"http://packagist.example.com",
       "archive":{
          "directory":"dist",
          "skip-dev":false
       },
       "repositories":[
          {
             "type":"composer",
             "url":"https://packagist.org"
          }
       ],
       "require-dependencies":true,
       "require":{
          "silex/silex":">1"
       }
    }

    • name: имя вашего хранилища,
    • homepage: линк по которому будет доступна "/path/to/your/webroot/satis/web" директория;
    • archive.directory: путь где будут храниться кэш пакетов;
    • archive.skip-dev: пропускать ли дев пакеты. Дев часто меняются, но если мы хотим полностью избавиться от зависимости от packagist.org, github, bitbucket и т.д. то ставим false;
    • repositories: список хранилищ, их может быть несколько. Можно указывать прямые линки пакетов на github и т.д;
    • { «type»: «composer», «url»: «packagist.org» }: указываем оригинальное для Composer хранилище пакетов — Packagist;
    • require-dependencies: нужно ли скачивать зависимости зависимостей. Опять же, для полной независимости ставим true;
    • require: список пакетов кэш которых мы хотим использовать, ;
    • {«silex/silex»: ">1"}: для данного примера возьмём «require» из примера с установкой Silex фреймворка, с указанием версий выше первой;


    И запускаем сборку:
    php bin/satis build satis.json web/
    


    У меня получился такой output:
    Scanning packages
    Creating local downloads in 'web//dist'
    Dumping 'doctrine/annotations-1.0.0.0'.
    Dumping 'doctrine/annotations-1.1.0.0'.
    Dumping 'doctrine/annotations-1.1.1.0'.
    Dumping 'doctrine/annotations-1.1.2.0'.
    Dumping 'doctrine/annotations-9999999-dev'.
    Dumping 'doctrine/cache-1.0.0.0'.
    Dumping 'doctrine/cache-1.1.0.0'.
    Dumping 'doctrine/cache-1.2.0.0'.
    Dumping 'doctrine/cache-9999999-dev'.
    Dumping 'doctrine/collections-1.0.0.0'.
    Dumping 'doctrine/collections-1.1.0.0'.
    Dumping 'doctrine/collections-9999999-dev'.
    Dumping 'doctrine/common-2.2.0.0'.
    Dumping 'doctrine/common-2.2.0.0-RC1'.
    Dumping 'doctrine/common-2.2.0.0-RC3'.
    Dumping 'doctrine/common-2.2.0.0-RC4'.
    Dumping 'doctrine/common-2.2.0.0-RC5'.
    Dumping 'doctrine/common-2.2.0.0-beta1'.
    Dumping 'doctrine/common-2.2.0.0-beta2'.
    Dumping 'doctrine/common-2.2.1.0'.
    Dumping 'doctrine/common-2.2.2.0'.
    Dumping 'doctrine/common-2.2.3.0'.
    Dumping 'doctrine/common-2.2.9999999.9999999-dev'.
    Dumping 'doctrine/common-2.3.0.0'.
    Dumping 'doctrine/common-2.3.0.0-RC1'.
    Dumping 'doctrine/common-2.3.0.0-RC2'.
    Dumping 'doctrine/common-2.3.0.0-RC3'.
    Dumping 'doctrine/common-2.3.0.0-beta1'.
    Dumping 'doctrine/common-2.3.9999999.9999999-dev'.
    Dumping 'doctrine/common-2.4.0.0'.
    Dumping 'doctrine/common-2.4.0.0-RC1'.
    Dumping 'doctrine/common-2.4.0.0-RC2'.
    Dumping 'doctrine/common-2.4.0.0-RC3'.
    Dumping 'doctrine/common-2.4.0.0-RC4'.
    Dumping 'doctrine/common-2.4.1.0'.
    Dumping 'doctrine/common-2.4.9999999.9999999-dev'.
    Dumping 'doctrine/common-9999999-dev'.
    Dumping 'doctrine/inflector-1.0.0.0'.
    Dumping 'doctrine/inflector-9999999-dev'.
    Dumping 'doctrine/lexer-1.0.0.0'.
    Dumping 'doctrine/lexer-9999999-dev'.
    Dumping 'pimple/pimple-1.0.0.0'.
    Dumping 'pimple/pimple-1.0.1.0'.
    Dumping 'pimple/pimple-1.0.2.0'.
    Dumping 'pimple/pimple-9999999-dev'.
    Dumping 'psr/log-1.0.0.0'.
    Dumping 'silex/silex-1.0.1.0'.
    Dumping 'silex/silex-1.0.9999999.9999999-dev'.
    Dumping 'silex/silex-1.1.0.0'.
    Dumping 'silex/silex-1.1.1.0'.
    Dumping 'silex/silex-9999999-dev'.
    Dumping 'symfony/debug-2.3.0.0'.
    Dumping 'symfony/debug-2.3.1.0'.
    Dumping 'symfony/debug-2.3.2.0'.
    Dumping 'symfony/debug-2.3.3.0'.
    Dumping 'symfony/debug-2.3.4.0'.
    Dumping 'symfony/debug-2.3.5.0'.
    Dumping 'symfony/debug-2.3.6.0'.
    Dumping 'symfony/debug-2.3.9999999.9999999-dev'.
    Dumping 'symfony/debug-2.4.0.0-beta1'.
    Dumping 'symfony/debug-9999999-dev'.
    Dumping 'symfony/event-dispatcher-2.1.0.0'.
    Dumping 'symfony/event-dispatcher-2.1.1.0'.
    Dumping 'symfony/event-dispatcher-2.1.10.0'.
    Dumping 'symfony/event-dispatcher-2.1.11.0'.
    Dumping 'symfony/event-dispatcher-2.1.12.0'.
    Dumping 'symfony/event-dispatcher-2.1.13.0'.
    Dumping 'symfony/event-dispatcher-2.1.2.0'.
    Dumping 'symfony/event-dispatcher-2.1.3.0'.
    Dumping 'symfony/event-dispatcher-2.1.4.0'.
    Dumping 'symfony/event-dispatcher-2.1.5.0'.
    Dumping 'symfony/event-dispatcher-2.1.6.0'.
    Dumping 'symfony/event-dispatcher-2.1.7.0'.
    Dumping 'symfony/event-dispatcher-2.1.8.0'.
    Dumping 'symfony/event-dispatcher-2.1.9.0'.
    Dumping 'symfony/event-dispatcher-2.1.9999999.9999999-dev'.
    Dumping 'symfony/event-dispatcher-2.2.0.0'.
    Dumping 'symfony/event-dispatcher-2.2.1.0'.
    Dumping 'symfony/event-dispatcher-2.2.2.0'.
    Dumping 'symfony/event-dispatcher-2.2.3.0'.
    Dumping 'symfony/event-dispatcher-2.2.4.0'.
    Dumping 'symfony/event-dispatcher-2.2.5.0'.
    Dumping 'symfony/event-dispatcher-2.2.6.0'.
    Dumping 'symfony/event-dispatcher-2.2.7.0'.
    Dumping 'symfony/event-dispatcher-2.2.8.0'.
    Dumping 'symfony/event-dispatcher-2.2.9.0'.
    Dumping 'symfony/event-dispatcher-2.2.9999999.9999999-dev'.
    Dumping 'symfony/event-dispatcher-2.3.0.0'.
    Dumping 'symfony/event-dispatcher-2.3.1.0'.
    Dumping 'symfony/event-dispatcher-2.3.2.0'.
    Dumping 'symfony/event-dispatcher-2.3.3.0'.
    Dumping 'symfony/event-dispatcher-2.3.4.0'.
    Dumping 'symfony/event-dispatcher-2.3.5.0'.
    Dumping 'symfony/event-dispatcher-2.3.6.0'.
    Dumping 'symfony/event-dispatcher-2.3.9999999.9999999-dev'.
    Dumping 'symfony/event-dispatcher-2.4.0.0-beta1'.
    Dumping 'symfony/event-dispatcher-9999999-dev'.
    Dumping 'symfony/http-foundation-2.1.0.0'.
    Dumping 'symfony/http-foundation-2.1.1.0'.
    Dumping 'symfony/http-foundation-2.1.10.0'.
    Dumping 'symfony/http-foundation-2.1.11.0'.
    Dumping 'symfony/http-foundation-2.1.12.0'.
    Dumping 'symfony/http-foundation-2.1.13.0'.
    Dumping 'symfony/http-foundation-2.1.2.0'.
    Dumping 'symfony/http-foundation-2.1.3.0'.
    Dumping 'symfony/http-foundation-2.1.4.0'.
    Dumping 'symfony/http-foundation-2.1.5.0'.
    Dumping 'symfony/http-foundation-2.1.6.0'.
    Dumping 'symfony/http-foundation-2.1.7.0'.
    Dumping 'symfony/http-foundation-2.1.8.0'.
    Dumping 'symfony/http-foundation-2.1.9.0'.


    Теперь нужно подправить конфиг пакетов в проекте который будет использовать наш Satis кэш, опять же воспользуемся примером Silex:
    cd /path/to/your/webroot/silex
    echo '{"repositories": [{ "type": "composer", "url": "http://packagist.example.com" },{ "packagist": false } ], "require": {"silex/silex": "~1.1"}}' > composer.json
    


    Отформатированная версия composer.json
    {
       "repositories":[
          {
             "type":"composer",
             "url":"http://packagist.example.com"
          },
          {
             "packagist":false
          }
       ],
       "require":{
          "silex/silex":"~1.1"
       }
    }
    


    • repositories: список хранилищ пакетов;
    • { «type»: «composer», «url»: «packagist.example.com» }: линк на наше, только что, созданное Satis хранилище с типом composer.
    • { «packagist»: false }: по умолчанию, если пакет не будет найден в списке указанных хранилищ, Composer полезет искать пакеты на Packagist. Это хорошо и удобно. Но, если мы разрэшим такое поведение, то мы не можем быть уверены, что действительно все пакеты есть у нас на локальном хранилище. Добавлю своё наблюдение: если у вас много зависимостей, то Composer ест много памяти, некоторые рапортуют размеры в 3Гб… что печалит. Так вот, похожую ситуацию наблюдали и мы, до тех пор пока не оставили лишь одно хранилище. То есть, или packagist.org или своё Satis;
    • require: список зависимостей;
    • {«silex/silex»: "~1.1"}: пока нам нужен только Silex;


    Теперь, чтоб убедиться в работоспособности нашей структуры, нам нужно очистить кэш Composer, иначе Composer возьмёт пакет из своего кэша:
    cd /path/to/your/webroot/silex; rm composer.lock; rm -fr vendor; rm -fr ~/.composer/cache
    php composer.phar install
    


    Всё. Если настройки правильные, теперь Composer будет брать пакеты из нашего локального кэша (http://packagist.example.com).

    Минус предложенного решения в том, что кэшируемые пакеты приходится прописывать в конфигурационном файле Satis (satis.json) руками, то есть Satis не будет работать как прокси с авто-кэшингом, что на мой взгляд, является упущением. Так же нужно настроить крон скрипт который будет дёргать Satis build для обновления дев-пакетов и скачивания новых версий пакетов:
    0 */12 * * * cd /path/to/your/webroot/satis/; php bin/satis build satis.json ./web/

    P.S. неточности и ошибки прошу в личку.
    P.S.S. другие решения кэшинга Composer пакетов, предлагаю обсудить в коментариях.
    • +15
    • 12,8k
    • 9
    Поделиться публикацией
    Похожие публикации
    Ой, у вас баннер убежал!

    Ну. И что?
    Реклама
    Комментарии 9
    • –1
      Мне казалось Satis — вариант легкого packagist. Просто статическое хранилище репозиториев.
      Как вариант — хранить один отдельный репозиторий с vendor + composer.lock, и подтягивать его через git modules
      • 0
        Так они есть лёгкий, и статический. Если заново не билдить, то ни чего и не меняется.
        Если я Вас правильно понял, вы предлагаете создать свой пакет со встроенными зависимостями в папку «vendor»? Думаю так не получится, зависимости определяются динамически и каждый пакет будет отдельно тащить пакеты.
        • 0
          Нет, я предлагаю хранить в отдельном репозитории зависимости. Например в разработке под ноду вообще считается правильным. Но если мы не хотим засорять историю апдейтами зависимостей — хранить их в отдельном репозитории уже скачанные
          • 0
            Так вы будете засорять историю не апдейтами зависимостей, а апдейтами сабмодуля, внутри которого будет история, засоренная апдейтами заваисимостей :)

            (we need to go deeper)
      • 0
        Не совсем понимаю в чём проблема. Я у себя в проекты добавляю не только composer.json, но и composer.lock. А при деплое делается install, который полностью происходит из кэша composer. Только для этого должна быть указана версия зависимостей, хотя бы в формате 1.*. А если берётся из какой-то ветки, то тут, конечно будет тормозить.
        • 0
          более того, composer.lock рекомендуется добавлять, что бы у всех разработчиков были одинаковые версии зависимостей.
        • 0
          Проблема с менеджером пакетов заключается в том, что слишком часто приходится устанавливать одни и те же пакеты. Каждая новая установка проекта, это обращение к packagist.org для нахождения источников зависимостей вашего пакета, зависимостей зависимостей и т.д. и дальнейшее скачивание необходимых пакетов. Справедливости ради стоит отметить, что у Composer есть встроенный кэш, но он хранится под ~/.composer/cache, и соответственно индивидуален для каждого пользователя, не говоря уже об отдельных средах для разработчиков, тестеров, QA, продакшн. И везде скачиваются одни и те же пакеты.
          • +1
            rm composer.lock;

            В последнем абзаце. Я бы не рекомендовал такое делать. Достаточно кильнують вендоров и кеш компоузера. В файле composer.lock как раз фиксируются версии пакетов, никак не хранятся.
            • 0
              А так же в composer.lock хранятся пути от куда брать исходники, что ни как нас не устраивает при переходе на локальный кэш.

              Но вы правы, при условии, что в composer.json прописаны расплывчатые версии, это повлечёт за собой обновление установленных пакетов. Но это уже зависит от вас самих, точнее, ваших настроек.

            Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

            Самое читаемое