<? phpukraine СПІВБЕСІДИ
Пошук по платформі
DEVOPS · JUNIOR ЧАСТО ПИТАЮТЬ

Навіщо composer.lock, як працює semver і чим update відрізняється від install?

composer.json описує допустимі діапазони версій, composer.lock фіксує точні версії, які реально встановлені; install ставить строго з lock і дає однакові залежності всюди, update перераховує діапазони й переписує lock.

Локально працює, а на сервері фатальна помилка про неіснуючий метод — чому?
Чи треба комітити composer.lock, якщо версії й так є в composer.json?
Що станеться, якщо запустити composer update на продакшені?
Чим `^1.2.3` відрізняється від `~1.2.3` і що з них дозволить версію 2.0.0?
Composer semver залежності деплой

У Composer два файли з різними ролями. composer.json — це декларація намірів: «мені підійде будь-який Laravel 12.x». composer.lock — протокол результату: точні версії всіх пакетів, включно з транзитивними, які ви ніколи не згадували, плюс хеш коміту кожного з них, посилання на дистрибутив і content-hash — відбиток тих секцій composer.json, що впливають на резолюцію. Саме тому lock зазвичай на кілька тисяч рядків: у ньому не десяток ваших залежностей, а все дерево цілком.

composer install за наявності lock не виконує резолву взагалі — просто розпаковує перелічені версії. Це швидко, детерміновано і дає однаковий vendor/ на ноутбуці, в CI і на сервері. composer update навпаки: ігнорує зафіксовані версії, звіряється з composer.json, витягує з packagist найновіше, що вкладається в діапазони, розв'язує конфлікти між залежностями залежностей і переписує lock. Якщо composer.json змінили, а lock ні, install виведе попередження про невідповідність content-hash і все одно поставить старе; у CI цю розбіжність ловлять командою composer validate --strict, яка на ній падає.

Діапазони записують семантичним версіонуванням: MAJOR.MINOR.PATCH, де мажор означає несумісні зміни, мінор — сумісні доповнення, патч — виправлення. Практично використовують два оператори. Каретка ^1.2.3 дозволяє все до наступного мажора: >=1.2.3 <2.0.0. Тильда ~1.2.3 піднімає лише останню вказану позицію: >=1.2.3 <1.3.0, а от ~1.2 — це вже >=1.2 <2.0. Окремо треба знати виняток для нульових мажорів: у версіях 0.x Composer вважає ламким лівий ненульовий сегмент, тому ^0.3.1 означає >=0.3.1 <0.4.0, а не «до 1.0». Composer не читає код пакета — він довіряє номеру, тож захист від автора, який зламав API в мінорному релізі, лише один: зафіксований lock і тести після кожного оновлення.

Звідси робочий процес. Lock комітять у репозиторій (для застосунку — обов'язково; у бібліотеці він ігнорується споживачами, але корисний для власного CI). Оновлюють локально й точково: composer update vendor/package -W, потім тести, потім коміт зміненого lock окремою гілкою — так у код-рев'ю видно, що саме змінилося у версіях. composer require робить те саме автоматично, тобто частковий update лише для нового пакета. На деплої запускають виключно composer install --no-dev --optimize-autoloader: --no-dev викидає PHPUnit і решту інструментів розробки, --optimize-autoloader будує статичну мапу класів замість пошуку файлів на диску.

Межа цього підходу в тому, що lock фіксує версії, але не гарантує їхньої безпеки: зафіксований на пів року пакет може мати опубліковану вразливість. Тому в пайплайні тримають composer audit (доступний з Composer 2.4), який звіряє lock з базою відомих CVE, а оновлення роблять регулярними невеликими порціями, а не одним стрибком через два мажори раз на рік. Другий нюанс — платформа: якщо на ноутбуці PHP 8.4, а на сервері 8.2, резолюція локально дозволить те, що на сервері не встановиться. Лікується це config.platform.php у composer.json, а не прапорцем --ignore-platform-reqs, який просто вимикає перевірку й переносить падіння на продакшен.

# composer.json — діапазони, тобто наші наміри
#   "laravel/framework": "^12.0"   → >=12.0.0  <13.0.0  (мажор фіксуємо)
#   "nesbot/carbon":     "~3.8.0"  → >=3.8.0   <3.9.0   (патчі, без мінорів)
#   "league/csv":        "^0.9.2"  → >=0.9.2   <0.10.0  (нульовий мажор: ламає мінор)

# 1. Розробник: ставимо строго те, що у lock. Резолву немає, тому швидко.
composer install

# 2. Оновлення — тільки локально, у гілці, точково.
composer outdated --direct          # що взагалі має новіші версії
composer update nesbot/carbon -W    # -W = разом із залежностями цього пакета
vendor/bin/pest                     # тести на новому наборі версій
git add composer.json composer.lock && git commit -m "Bump carbon"

# 3. Конфлікт у composer.lock після мержу: файл не редагують руками.
git checkout --ours composer.json
git checkout --ours composer.lock
composer update --lock              # перерахувати content-hash без зміни версій

# 4. Деплой: install і нічого більше. update тут = нетестований код у релізі.
composer install --no-dev --optimize-autoloader --no-interaction --prefer-dist
composer audit                      # відомі CVE у зафіксованих версіях
Розділення ролей: composer.json — це наміри (діапазони), composer.lock — це факт (точні версії, посилання на коміт і content-hash).
Що `composer install` за наявності lock не робить резолву взагалі й ставить те саме на ноутбуці, в CI і в продакшені; `composer update` ігнорує зафіксовані версії й перезбирає дерево залежностей.
Правило семантичного версіонування MAJOR.MINOR.PATCH і що `^1.2.3` означає `>=1.2.3 <2.0.0`, `~1.2.3` — `>=1.2.3 <1.3.0`, а для нульових мажорів `^0.3.1` це `>=0.3.1 <0.4.0`.
Що lock комітять для застосунків, а на деплої запускають `composer install --no-dev --optimize-autoloader`, ніколи не `update`.
Що оновлюють локально й точково: `composer update vendor/package -W`, потім тести, потім коміт зміненого lock у гілці.
Додати composer.lock у .gitignore «щоб не було конфліктів у мержі» — після цього кожна машина отримує свій набір версій.
Запускати `composer update` на сервері під час деплою й підтягувати щойно випущений мінорний реліз без жодного тесту.
Розв'язувати конфлікт у composer.lock руками в редакторі замість `git checkout --theirs composer.lock && composer update --lock` чи повторного `composer require`.
Вважати, що `^1.2.3` не пустить 1.9.0, або що `~1.2` і `~1.2.0` — це те саме (перше дозволяє 1.9, друге лише 1.2.x).
Ставити `"*"` або `dev-master` у composer.json і потім дивуватися, що збірка місячної давності не відтворюється.
Забути `--no-dev` на продакшені й тягнути PHPUnit разом із його залежностями в реліз.
ПОРАДА

Скажіть одним реченням: «composer.json — це те, що ми дозволяємо, composer.lock — це те, що ми перевірили». Далі додайте, що update робиться локально в окремій гілці з тестами, а на сервері живе тільки install — це відповідь рівня людини, яка деплоїла, а не читала документацію.

оновлено 5 вересня 2026 · ліцензія CC-BY-SA-4.0 Знайшли неточність? Напишіть →
ПЕРЕВІРТЕ СЕБЕ

install відтворює зафіксований стан без резолву, update шукає нові версії в межах діапазонів і оновлює lock — тому на сервері запускають саме install.