К сожалению, на собеседовании на вопрос “Чем отличаются команды composer install
и composer update
?” мы слишком часто слышим ответ “Не знаю” или, ещё хуже, “Ничем”.
В этой статье мы расскажем, чем отличается install
от update
и почему так важно не добавлять composer.lock
в .gitignore
.
Создаю проект.
Объявляю зависимости в
composer.json
.composer update
илиcomposer install
? Сделаю так: в первый раз запущуinstall
, потом будуupdate
, логично же? Вижу файлcomposer.lock
. Ну ок, наверное, это что-то нужное, залью его.После очередного запуска
composer update
опять изменился чёртовcomposer.lock
. А в прошлый раз так вообще конфликт из-за него при слиянии был. Да пошел он в.gitignore
!Какой же этот Composer долгий… И памяти жрёт при обновлении…
Время заливать проект на продакшн. Эм… какую команду прописать в скрипт обновления площадки? Ну пусть будет
composer install
. После очередного обновления продакшн фатально падает. Выясняется, что пакеты на сервере не обновляются. Так и знал, чтоinstall
не сработает. Меняю наupdate
.Через какое-то время после очередного деплоймента продакшн снова фатально падает. Какого..?
Фиксация зависимостей
При работе с зависимостями нам хотелось бы достичь следующих результатов:
- Согласованности. Все площадки (локальные проекты разработчиков, сервисы CI, продакшн-сервера) с проектом в состоянии коммита
X
должны иметь идентичный набор пакетов. - Контроля над процессом обновления. Мы хотим обновлять зависимости по желанию в ручном режиме.
- Стабильности при деплойменте. Отсутствие ошибок и падений из-за установки неверных зависимостей.
- Высокой скорости и низкого потребления памяти при развертывании.
Для достижения этих целей необходим механизм, который в момент фиксации изменений будет запоминать состояние зависимостей как часть состояния проекта. Назовем это фиксацией зависимостей.
Самая простая реализация такого механизма — lock-файл с информацией об установленных зависимостях. Файл должен храниться в репозитории и позволять однозначно воспроизводить на любой другой площадке набор пакетов, использованных программистом при разработке.
А что там у Composer
В Composer lock-файл называется composer.lock
и располагается в корне проекта. Чтобы понять, как он генерируется и используется, разберем механизм работы двух команд менеджера.
composer update
Задача команды composer update
— обновить зависимости проекта до актуальных версий в соответствии с правилами, записанными в composer.json
.
Для каждого заявленного в проекте пакета менеджер находит его источник, считывает информацию о версиях и зависимостях, а затем рекурсивно повторяет то же самое для зависимостей пакета, чтобы получить полное дерево. После этого Composer разрешает зависимости с учетом версионных правил, объявленных во всех собранных composer.json
файлах.
Информация о пакете, рекомендованном к установке, имеет следующий вид:
{ // название пакета "name": "doctrine/collections", // версия "version": "v1.5.0", // адрес исходника для установки с флагом --prefer-source "source": { "type": "git", "url": "https://github.com/doctrine/collections.git", "reference": "a01ee38fcd999f34d9bfbcee59dbda5105449cbf" }, // адрес архива для установки с флагом --prefer-dist "dist": { "type": "zip", "url": "https://api.github.com/repos/doctrine/collections/zipball/a01ee38fcd999f34d9bfbcee59dbda5105449cbf", "reference": "a01ee38fcd999f34d9bfbcee59dbda5105449cbf", "shasum": "" } // ... копия других мета-данных из 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 следующего содержания:
{ "name": "student/project", "require": { "vendor/package": "^2.0.2" } }
Прописанное для 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.
- Первое правило lock-клуба: по умолчанию всегда и везде используем
composer install
. - Коммитим
composer.lock
в репозиторий. - Используем
composer update
только для намеренных обновлений. - При конфликте в
composer.lock
во времяmerge
/rebase
принимаем любую версию файла. Затем выполняемcomposer update
, чтобы гарантированно получитьcomposer.lock
, соответствующийcomposer.json
.
p.s.
Текст этой записи является копией статьи с заблокированного в России (на момент написания) сайта https://medium.com