Кэшинг пакетов для 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
    • 11,6k
    • 9
    Поделиться публикацией
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама
    Комментарии 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 прописаны расплывчатые версии, это повлечёт за собой обновление установленных пакетов. Но это уже зависит от вас самих, точнее, ваших настроек.

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