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

Composer стал фактическим стандартом управления зависимостями в PHP, и большинство разработчиков использует его поверхностно: добавить пакет, запустить install, забыть про файл конфигурации до следующего конфликта версий. Этого достаточно для небольшого личного проекта, но на продакшене и в команде поверхностное использование быстро превращается в источник непредсказуемых сборок, разных версий зависимостей у разных разработчиков и уязвимостей, которые никто не заметил вовремя.
Констрейнты версий: что на самом деле означает каждый знак
Запись версии в composer.json это не точная версия, а правило, какие версии допустимы при обновлении. Разница между знаками меняет поведение проекта сильнее, чем кажется на первый взгляд, и путаница между ними одна из самых частых причин неожиданных поломок после composer update.
| Запись | Что означает | Риск |
|---|---|---|
^8.1 | Любая версия до следующей старшей, совместимая по semver | Низкий для пакетов, строго следующих semver |
~8.1.0 | Патч-версии в пределах 8.1.x, без перехода на 8.2 | Минимальный, но требует ручного обновления минорных версий |
8.1.* | Любая версия внутри 8.1, аналогично тильде на этом уровне | Похож на тильду, менее явный для чтения |
| Точная версия без символов | Ровно одна версия, без автоматических обновлений | Нулевой риск неожиданного обновления, но нет патчей безопасности без ручного действия |
Каретка ^ остаётся разумным выбором по умолчанию для большинства библиотек: она разрешает получать исправления ошибок и совместимые новые возможности, но блокирует переход через границу несовместимых изменений. Жёсткая фиксация точной версии оправдана для критичных production-зависимостей, где команда сознательно хочет контролировать момент каждого обновления руками, а не автоматически при следующей установке.
Зачем composer.lock обязателен в репозитории
Файл composer.json описывает допустимый диапазон версий, а файл composer.lock фиксирует конкретные версии, которые реально были выбраны при последней установке, включая версии зависимостей зависимостей. Без lock-файла два разработчика, запустившие composer install в разные дни, могут получить разные версии одного и того же пакета, если за это время вышло совместимое по semver обновление.
Частая ошибка новичков: добавить composer.lock в .gitignore, решив, что это временный файл сборки. На практике это главный источник детерминированной установки: команда composer install при наличии lock-файла ставит ровно те версии, которые в нём записаны, игнорируя диапазоны из composer.json, тогда как composer update пересчитывает диапазоны заново и обновляет lock-файл.
# Установка строго по composer.lock, предсказуемо для CI и продакшена
composer install --no-dev --optimize-autoloader
# Обновление диапазонов версий и пересчёт lock-файла, делать осознанно
composer update
# Обновить только один пакет, не трогая остальные версии
composer update vendor/package --with-dependenciesАудит зависимостей на уязвимости
Composer начиная с версии 2.4 включает встроенную команду audit, которая сверяет установленные версии пакетов с публичной базой известных уязвимостей и сообщает, какие зависимости требуют обновления по соображениям безопасности, а не только по новым возможностям. Это особенно важно для PHP-проектов, которые годами держат зависимости на фиксированных старых версиях без проверки.
composer audit
# Включить аудит в CI, провалить сборку при найденных уязвимостях
composer audit --locked --format=plainРегулярный запуск аудита, например раз в неделю в CI-пайплайне, превращает обновление зависимостей из редкого стрессового события в маленькую рутинную задачу. Проект, который обновляет зависимости раз в год одним большим коммитом, почти гарантированно столкнётся с несовместимыми изменениями сразу в нескольких пакетах, тогда как регулярные небольшие обновления разносят эту сложность во времени.
Platform config для предсказуемой установки
Composer по умолчанию определяет доступную версию PHP и установленные расширения на той машине, где выполняется команда, и подбирает совместимые версии зависимостей под неё. Проблема возникает, когда разработка идёт на одной версии PHP, а продакшен работает на другой: composer может выбрать версию пакета, совместимую с окружением разработчика, но несовместимую с сервером.
{
"config": {
"platform": {
"php": "8.3.12"
},
"sort-packages": true,
"optimize-autoloader": true
}
}Явное указание версии PHP в секции platform заставляет Composer считать зависимости совместимыми именно с этой версией независимо от того, что реально установлено локально, и это снимает целый класс ошибок «у меня работает, на сервере не работает», вызванных разницей версий интерпретатора между окружениями.
Автозагрузка и флаги оптимизации
Флаг optimize-autoloader переключает автозагрузку классов с динамического поиска по правилам PSR-4 на статическую карту классов, собранную один раз при установке. На продакшене это заметно ускоряет первый запрос, потому что не требует поиска файла по неймспейсу на каждое подключение класса. На этапе разработки эту оптимизацию обычно не включают, потому что она требует пересборки карты после каждого нового класса.
composer install --no-devна продакшене, чтобы не тянуть пакеты для тестов и разработки в рабочее окружениеcomposer.lockв репозитории для приложений, обязательно обновлять осознанно, а не случайно при любой правкеcomposer auditрегулярно, лучше автоматически в CI, чем вручную по памяти- Явный
platform.phpв конфиге, если версия интерпретатора на сервере отличается от версии у разработчиков
Composer не требует глубокого изучения, чтобы пользоваться им правильно, достаточно понимать разницу между диапазоном версий и зафиксированной версией, не исключать lock-файл из репозитория приложения и не забывать про аудит уязвимостей хотя бы раз в несколько недель. Эти несколько привычек снимают большую часть проблем, с которыми обычно сталкиваются команды уже на зрелом проекте, когда зависимостей накопилось несколько десятков.
Разрешение конфликтов версий между пакетами
Чем больше зависимостей в проекте, тем выше шанс, что два разных пакета потребуют несовместимые версии общей транзитивной зависимости. Composer в такой ситуации не ставит произвольную версию, а честно сообщает о конфликте и перечисляет, какой пакет какую версию требует. Разбирать такие сообщения проще, если читать их снизу вверх: обычно в самом конце указана конкретная пара пакетов, из-за которой диапазоны не пересекаются.
Команда composer why vendor/package показывает, какие пакеты проекта зависят от указанного, а composer why-not vendor/package 3.0 объясняет, что мешает установить конкретную версию. Эти две команды обычно решают конфликт быстрее, чем попытка угадать причину по тексту ошибки установки, особенно в проектах с несколькими десятками зависимостей, где цепочка транзитивных требований может быть длинной.
Приватные репозитории и зеркала пакетов
Composer по умолчанию обращается к публичному Packagist, но для внутренних пакетов компании или для приватных форков открытых библиотек используется собственный репозиторий, подключаемый в секции repositories файла composer.json с типом vcs и адресом git-репозитория форка. Это особенно полезно, когда команда держит собственный форк публичного пакета с точечным патчем, который пока не принят в оригинальный проект, и хочет использовать именно свою версию через обычный composer require без изменения остального кода.
Второй практический случай: некоторые команды держат собственное зеркало Packagist внутри корпоративной сети, чтобы установка зависимостей не зависела от доступности внешнего интернета и ускорялась за счёт близкого расположения зеркала к серверам сборки. Настройка такого зеркала выходит за рамки повседневной работы с Composer, но для крупных команд с частыми CI-сборками экономия времени на установке зависимостей становится заметной уже на нескольких сотнях сборок в месяц.
Кеширование зависимостей в CI
Установка зависимостей с нуля на каждой сборке CI занимает заметное время, особенно для проектов с несколькими десятками пакетов. Composer хранит скачанные архивы пакетов в локальном кеше, и если этот кеш сохраняется между запусками сборки, повторная установка тех же версий происходит в разы быстрее, потому что сетевые запросы к Packagist заменяются чтением с локального диска.
Большинство CI-систем позволяют явно указать путь к кешу Composer как директорию для сохранения между запусками по ключу, который меняется только при изменении composer.lock. Это простая настройка, но она часто остаётся незамеченной в проектах, где пайплайн собирали один раз и больше не трогали: время установки зависимостей на каждом запуске тихо накапливается в общее время ожидания результата сборки для всей команды.
Частые вопросы
Нужно ли коммитить composer.lock в репозиторий?
Для приложений, сайтов и сервисов, да. Это гарантирует, что все разработчики и продакшен получают одни и те же версии зависимостей. Для библиотек, которые сами устанавливаются как чужая зависимость, lock-файл обычно не публикуют.
Чем отличается composer install от composer update?
Install ставит версии строго из composer.lock, если он есть, и ничего не пересчитывает. Update пересчитывает допустимые версии по правилам из composer.json и обновляет lock-файл, это осознанное действие, а не рутинная установка.
Как проверить зависимости проекта на известные уязвимости?
Команда composer audit сверяет установленные версии с публичной базой уязвимостей. Разумно запускать её регулярно в CI, а не только вручную при подозрении на проблему.



