Не игнорьте composer.lock

install vs update

Удальцов Валентин
Пых / PHPYH
4 min readAug 6, 2018

--

Создаю проект. Объявляю зависимости в composer.json. composer update или composer install? Сделаю так: в первый раз запущу install, потом буду update, логично же? Вижу файл composer.lock. Ну ок, наверное, это что-то нужное, залью его.

После очередного запуска composer update опять изменился чёртов composer.lock. А в прошлый раз так вообще кофликт из-за него при слиянии был. Да пошел он в .gitignore!

Какой же этот Composer долгий… И памяти жрёт при обновлении…

Время заливать проект на продакшн. Эм… какую команду прописать в скрипт обновления площадки? Ну пусть будет composer install. После очередного обновления продакшн фатально падает. Выясняется, что пакеты на сервере не обновляются. Так и знал, что install не сработает. Меняю на update.

Через какое-то время после очередного деплоймента продакшн снова фатально падает. Какого..?

Composer — одна из причин, по которой PHP сегодня именно такой. Популярный, прогрессивный и удобный. Именно поэтому мы в RUVENTS считаем понимание принципов работы менеджера зависимостей одним из ключевых навыков PHP-программиста.

К сожалению, на собеседовании на вопрос “Чем отличаются команды composer install и composer update?” мы слишком часто слышим ответ “Не знаю” или, ещё хуже, “Ничем”.

В этой статье в надежде на то, что её прочтет наш будущий соискатель, мы в очередной раз расскажем, чем отличается install от update и почему так важно не добавлять composer.lock в .gitignore.

Фиксация зависимостей

При работе с зависимостями нам хотелось бы достичь следующих результатов:

  1. Согласованности. Все площадки (локальные проекты разработчиков, сервисы CI, продакшн-сервера) с проектом в состоянии коммита X должны иметь идентичный набор пакетов.
  2. Контроля над процессом обновления. Мы хотим обновлять зависимости по желанию в ручном режиме.
  3. Стабильности при деплойменте. Отсутствие ошибок и падений из-за установки неверных зависимостей.
  4. Высокой скорости и низкого потребления памяти при развертывании.

Для достижения этих целей необходим механизм, который в момент фиксации изменений будет запоминать состояние зависимостей как часть состояния проекта. Назовем это фиксацией зависимостей.

Самая простая реализация такого механизма — lock-файл с информацией об установленных зависимостях. Файл должен храниться в репозитории и позволять однозначно воспроизводить на любой другой площадке набор пакетов, использованных программистом при разработке.

А что там у Composer

В Composer lock-файл называется composer.lock и располагается в корне проекта. Чтобы понять, как он генерируется и используется, разберем механизм работы двух команд менеджера.

composer update

Задача команды composer updateобновить зависимости проекта до актуальных версий в соответствии с правилами, записанными в composer.json.

Для каждого заявленного в проекте пакета менеджер находит его источник, считывает информацию о версиях и зависимостях, а затем рекурсивно повторяет то же самое для зависимостей пакета, чтобы получить полное дерево. После этого Composer разрешает зависимости с учетом версионных правил, объявленных во всех собранных composer.json файлах.

Информация о пакете, рекомендованном к установке, имеет следующий вид:

Плоский массив таких объектов будет записан в формате JSON в злополучный файл composer.lock по завершении команды. Это и есть фиксация зависимостей в Composer.

После всех этих ресурсоёмких операций менеджер сопоставляет разрешенные зависимости с уже имеющимися пакетами в папке vendor/ и устраняет несоответствия: удаляет устаревшие и ненужные пакеты, скачивает (или подтягивает из кэша) новые и добавленные.

composer install

Задача команды composer installустановить зафиксированные зависимости проекта.

При запуске команды Composer первым делом ищет файл composer.lock. В случае успеха, минуя вышеописанные сложные процедуры, менеджер мгновенно получает массив разрешенных зависимостей из lock-файла и сразу приступает к обновлению папки vendor/.

Если composer.lock отсутствует, команда выполняет те же действия, что и composer update (включая создание lock-файла).

Итак, composer install использует зафиксированные в composer.lock зависимости как готовую инструкцию для установки/обновления, а composer update игнорирует этот файл и производит установку/обновление с нуля.

Также команда composer install, очевидно, выполняется гораздо быстрее composer update и требует совсем немного памяти.

Может быть всё-таки заигнорить?

Нет. Игнорировать можно девушку или парня, чтобы подогреть интерес к себе (только не переборщите!). composer.lock же, напротив, сразу готов с вами сотрудничать.

Не храня lock-файл в репозитории, вы отказываетесь от механизма фиксации зависимостей. Несмотря на то, что composer.lock будет в любом случае сгенерирован, Composer не сможет гарантировать, что установленные пакеты будут иметь те же версии, что при разработке.

Почему упал продакшн?

Как вы теперь поняли, история из предисловия (основанная на реальных событиях) демонстрирует ошибочную цепочку рассуждений. И вот каким образом она может привести к беде.

Допустим, есть composer.json следующего содержания:

Прописанное для vendor/package правило позволяет обновлять пакет в пределах версий >=2.0.2, <3.0.0.

Допустим, вышел релиз 2.0.3, в который закралась ошибка. Так как на продакшн-сервере обновления накатываются бесконтрольно при каждом деплойменте, vendor/package успешно обновился, хотя горе-разработчик об этом даже не догадывался. FAIL!

Если бы программист использовал composer install и заливал composer.lock в репозиторий, он имел бы полный контроль на обновлением пакетов. Прогнав после планового composer update тесты (обновив страницу в браузере), он бы заметил ошибку и добавил бы бажную версию в раздел conflict файла composer.json.

Как с этим жить

Теперь, когда вы узнали о фиксации зависимостей, разобрались с командами и-таки убрали строку из .gitignore, самое время подвести черту и обсудить workflow.

  1. Первое правило lock-клуба: по умолчанию всегда и везде используем composer install.
  2. Коммитим composer.lock в репозиторий.
  3. Используем composer update только для намеренных обновлений.
  4. При конфликте в composer.lock во время merge/rebase принимаем любую версию файла. Затем выполняем composer update, чтобы гарантированно получить composer.lock, соответствующий composer.json.

Источники вдохновения

--

--