Зачем нужен composer.lock
composer.json описывает намерение: «проекту нужен monolog/monolog версии ^3.0, PHP ^8.3, расширение ext-pdo». composer.lock фиксирует результат вычисления: какие именно версии пакетов были выбраны, с какими зависимостями, из каких источников и с какими metadata.
Это главное различие. composer.json — диапазоны и правила. composer.lock — снимок конкретного dependency graph. Поэтому для приложений lock-файл обычно коммитят. CI, staging, production и машина разработчика должны ставить один и тот же набор пакетов, а не «самые свежие совместимые на утро деплоя».
Для библиотек правило мягче: lock-файл может быть полезен для локальных тестов, но потребитель библиотеки всё равно решает зависимости в своём приложении. В приложении же отсутствие lock-файла почти всегда делает сборку менее воспроизводимой.
flowchart TD
A[composer.json: constraints] --> B{Команда}
B -->|composer update| C[Решение dependency graph]
C --> D[Новый composer.lock]
D --> E[vendor/]
B -->|composer install + lock есть| F[Чтение точных версий из composer.lock]
F --> E
E --> G[autoload.php]
G --> H[Приложение / CI / production]
H --> I[composer audit]
H --> J[check-platform-reqs]install: поставить зафиксированное
composer install — команда для восстановления зависимостей проекта. Если рядом есть composer.lock, Composer берёт точные версии из него и устанавливает их в vendor/.
composer installТипичный сценарий:
git pull
composer installПосле git pull lock-файл мог измениться: кто-то добавил пакет, обновил Symfony-компонент, убрал устаревшую зависимость. composer install приводит локальный vendor/ к состоянию, которое описано в lock-файле.
Если composer.lock отсутствует, install вынужден решить зависимости заново и создать lock-файл. Это нормально для первого локального запуска нового проекта, но плохо как production-паттерн: на сервере вы хотите не «решить зависимости сейчас», а поставить уже проверенный набор.
Для production обычно используют более жёсткую форму:
composer install --no-dev --optimize-autoloader --no-interaction--no-dev не ставит пакеты из require-dev и не включает autoload-dev. То есть phpunit/phpunit, phpstan/phpstan, тестовые namespace и dev-only утилиты не попадают в runtime. Это продолжает разделение из Composer, Packagist и composer.json: если код нужен на production, он должен быть в require, а не в require-dev.
--optimize-autoloader строит более быструю classmap для автозагрузчика. Это особенно уместно на production, где файлы не меняются при каждом запросе. В dev-среде оно часто не нужно: важнее быстрый цикл правки кода.
update: пересчитать версии
composer update делает другое: он заново решает dependency graph по constraints из composer.json, записывает новые конкретные версии в composer.lock и затем ставит их.
composer updateЭто команда изменения зависимостей, а не обычная команда деплоя. Если запустить её без аргументов, Composer может обновить много пакетов сразу: прямые зависимости, транзитивные зависимости, иногда целый пласт экосистемы. Такой diff сложнее ревьюить и сложнее откатывать.
Для аккуратного обновления чаще указывают пакет явно:
composer update monolog/monologТак проще понять, почему изменился lock-файл. В merge request обычно смотрят не только composer.json, но и composer.lock: какие пакеты реально поменялись, не приехал ли неожиданный major, не заменился ли source URL, не появилась ли новая цепочка зависимостей.
Короткое практическое правило:
composer install = довериться composer.lock
composer update = изменить composer.lockЕсли на production запускают composer update, production становится местом принятия dependency-решений. Это почти всегда неверная граница ответственности. Обновления должны проходить локальные проверки, CI, линтеры и автоматические проверки, ревью lock-файла и только потом деплой.
Reproducible installs и vendor/
vendor/ обычно не коммитят. Его можно восстановить из composer.json + composer.lock, поэтому Git-репозиторий остаётся компактным, а сторонний код не смешивается с вашим.
Воспроизводимость здесь не абсолютная магия, но сильная гарантия: при одинаковом lock-файле Composer должен поставить одинаковые версии пакетов. Это снижает класс ошибок «у меня работает, а на сервере другой patch-релиз». Особенно это важно перед миграциями PHP-версии, где одна транзитивная зависимость может поддерживать PHP 8.4, а другая ещё нет. Поэтому тема напрямую связана с Миграции между версиями PHP.
Плохой diff:
modified: composer.json
modified: composer.lock
modified: vendor/symfony/console/Application.php
modified: vendor/psr/log/LoggerInterface.phpНормальный diff для приложения:
modified: composer.json
modified: composer.lockvendor/ пересоберёт CI или deploy pipeline.
Platform requirements: PHP, extensions, system libs
Composer проверяет не только пакеты, но и platform packages: php, ext-mbstring, ext-pdo, ext-json, lib-*. Эти требования не скачиваются Composer’ом; они должны быть установлены в окружении.
{
"require": {
"php": "^8.3",
"ext-pdo": "*",
"ext-mbstring": "*"
}
}Если расширение используется кодом, его стоит явно указать. Иначе локально всё может работать просто потому, что расширение уже установлено, а контейнер или production-сервер упадёт позже.
Есть настройка config.platform, которая позволяет зафиксировать целевую платформу для dependency resolution:
{
"config": {
"platform": {
"php": "8.3.0"
}
}
}Это полезно, если локально у разработчика PHP 8.5, а production ещё на 8.3: Composer не выберет пакет, которому нужен PHP выше целевой версии. Но это не заменяет проверку реального сервера. Для неё есть отдельная команда:
composer check-platform-reqsОна сверяет требования установленных пакетов с настоящим PHP и расширениями текущего окружения, а не только с тем, что указано в config.platform.
Флаги вроде --ignore-platform-reqs или --ignore-platform-req=ext-foo стоит считать временным обходом, а не нормой. Они могут помочь собрать проект в неполном локальном окружении, но если так скрыть отсутствие расширения на production, ошибка просто переедет из Composer в runtime.
composer audit: проверка известных проблем
composer audit проверяет установленные или зафиксированные пакеты по security advisories и dependency policies. По умолчанию Composer использует Packagist API, если проект не настроил другие репозитории.
composer audit
composer audit --locked
composer audit --no-dev
composer audit --format=json--locked полезен в CI: проверяется то, что записано в lock-файле, независимо от текущего состояния vendor/. --no-dev исключает dev-зависимости, если вы хотите отдельно оценить production surface. JSON-формат удобен для CI-отчётов и security dashboards.
Важно не путать audit с полным security review. Он находит известные advisory по зависимостям, но не доказывает, что приложение безопасно. Он не заменяет темы Prepared statements и SQL injection, XSS, экранирование вывода и шаблоны, CSRF и state-changing запросы и Конфигурация безопасности PHP. Audit отвечает на более узкий вопрос: «есть ли в нашем dependency graph известные проблемные версии?»
Если advisory действительно не применим, его можно игнорировать с причиной в конфиге. Но игнор без причины быстро превращается в мусорный список. Хорошая запись объясняет, почему риск принят, когда вернуться к вопросу и какой mitigation уже есть.
Практичный pipeline команд
Для локальной разработки:
composer install
composer validate
composer audit
composer checkДля обновления одной зависимости:
composer update vendor/package
composer audit --locked
composer checkДля production-сборки:
composer install --no-dev --optimize-autoloader --no-interaction
composer check-platform-reqsСмысл не в том, чтобы запомнить набор флагов как заклинание. Граница простая: install воспроизводит уже принятое решение, update принимает новое dependency-решение, audit подсвечивает известные риски, platform checks сверяют ожидания Composer с реальным PHP-окружением.
См. также
- Composer, Packagist и composer.json — зависимости,
require,require-dev, scripts и autoload. - Autoloading и PSR-4 — почему production autoload optimization связан с namespace-маппингом.
- CI, линтеры и автоматические проверки — где запускать
composer install,audit, статический анализ и тесты. - PHPDoc, generics и статический анализ — почему анализаторы обычно живут в
require-dev. - PHPUnit, моки и стабы — типичный dev-пакет, который не нужен в production runtime.
- Миграции между версиями PHP — как lock-файл, platform requirements и dependency updates влияют на переход между PHP 8.x.
- Конфигурация безопасности PHP — границы Composer audit и runtime-настроек безопасности.