Composer управляет зависимостями на уровне конкретного проекта, изолируя сторонние библиотеки в каталоге vendor/. Надежность деплоя и предсказуемость поведения приложения в продакшене зависят от понимания того, как менеджер пакетов разрешает граф зависимостей, фиксирует версии и генерирует карту автозагрузки.
composer.json vs composer.lock: фундаментальная разница
Два ключевых файла в корне проекта выполняют принципиально разные задачи: один декларирует правила, второй фиксирует точное состояние окружения.
Что хранит composer.json
composer.json — это манифест проекта. В нем описываются:
- Прямые зависимости (
requireиrequire-dev) с допустимыми диапазонами версий. - Правила автозагрузки классов (PSR-4, PSR-0, classmap, files).
- Системные требования (версия PHP, расширения).
- Пользовательские скрипты и конфигурация поведения Composer.
Этот файл задает намерения разработчика, но не гарантирует точный состав установленных пакетов при повторной установке на другой машине.
Зачем нужен composer.lock
composer.lock — это снимок (snapshot) полностью разрешенного графа зависимостей. Когда Composer вычисляет дерево пакетов, он записывает в lock-файл:
- Точные версии каждого установленного пакета (включая транзитивные зависимости).
- Хеши коммитов (commit hash) для пакетов, загруженных из VCS.
- URL-адреса дистрибутивов и контрольные суммы архивов.
- Хеш содержимого
composer.json(content-hash), гарантирующий, что lock-файл соответствует текущему манифесту.
{
"_readme": [
"This file locks the dependencies of your project to a known state"
],
"content-hash": "a1b2c3d4e5f6...",
"packages": [
{
"name": "monolog/monolog",
"version": "3.5.0",
"source": {
"type": "git",
"url": "https://github.com/Seldaek/monolog.git",
"reference": "4118123e1b81a51c64a18ae52e79259de870b87b"
}
}
]
}
Почему composer.lock коммитят в приложениях, но игнорируют в библиотеках
- В конечных приложениях (проектах):
composer.lockобязательно фиксируется в Git. Это единственный способ гарантировать, что все разработчики, CI-раннеры и production-серверы запустят идентичный набор кода вплоть до последнего патча сторонней библиотеки. - В библиотеках (reusable packages):
composer.lockдобавляется в.gitignore. Если библиотека закоммитит свой lock-файл, Composer проигнорирует его при подключении этой библиотеки в родительский проект. Библиотека должна тестироваться на различных комбинациях версий зависимостей в пределах заданных диапазонов.
composer install против composer update: как не сломать прод
Непонимание разницы между этими двумя командами — основная причина внезапных сбоев при релизах.
┌────────────────────────┐
│ composer install │
└───────────┬────────────┘
│
composer.lock существует?
├─── ДА ───► Устанавливает ТОЧНЫЕ версии из lock
└─── НЕТ ──► Разрешает зависимости -> Создает lock -> Устанавливает
┌────────────────────────┐
│ composer update │
└───────────┬────────────┘
│
Игнорирует composer.lock
│
Вычисляет новые версии по composer.json
│
Перезаписывает composer.lock -> Устанавливает
Логика работы composer install
Команда предназначена для развертывания проекта:
- Если
composer.lockприсутствует, Composer читает только его и скачивает указанные там версии. Диапазоны изcomposer.jsonигнорируются. - Если
composer.lockотсутствует (например, при инициализации нового проекта), Composer вычисляет граф зависимостей поcomposer.json, устанавливает пакеты и создает новыйcomposer.lock.
В CI/CD и на production-серверах используется исключительно composer install.
Что делает composer update
Команда предназначена для обновления зависимостей:
- Игнорирует существующий
composer.lock. - Заново обращается к Packagist/репозиториям и рассчитывает максимально свежие версии пакетов, удовлетворяющие ограничениям из
composer.json. - Перезаписывает
composer.lock. - Скачивает и устанавливает новые пакеты в
vendor/.
Запуск composer update на рабочем сервере приводит к неконтролируемому обновлению сотен транзитивных пакетов прямо во время деплоя.
Безопасное обновление отдельного пакета
Если требуется обновить только одну конкретную библиотеку, не затрагивая остальной стек, команду ограничивают именем пакета:
# Обновить только symfony/yaml
composer update symfony/yaml
# Обновить symfony/yaml и разрешить обновление его прямых зависимостей
composer update symfony/yaml --with-dependencies
# Обновить symfony/yaml и все связанные транзитивные зависимости
composer update symfony/yaml --with-all-dependencies
Ограничения версий (Version Constraints) и SemVer
Composer опирается на принципы семантического версионирования (SemVer: MAJOR.MINOR.PATCH):
MAJOR— обратно несовместимые изменения API;MINOR— новая функциональность с сохранением обратной совместимости;PATCH— исправление ошибок с сохранением обратной совместимости.
Синтаксис диапазонов
| Ограничение | Пример | Допустимый диапазон | Пояснение |
|---|---|---|---|
| Точная версия | 1.2.3 |
1.2.3 |
Только указанный релиз. |
| Wildcard (маска) | 1.2.* |
>=1.2.0 <1.3.0 |
Любые патчи в ветке 1.2. |
| Диапазон | >=1.2 <2.0 |
>=1.2.0 <2.0.0 |
Явное указание границ. |
Тильда (~) |
~1.2.3<br>~1.2 |
>=1.2.3 <1.3.0<br>>=1.2.0 <2.0.0 |
Фиксирует последний указанный сегмент. |
Карет (^) |
^1.2.3<br>^0.3.2 |
>=1.2.3 <2.0.0<br>>=0.3.2 <0.4.0 |
Разрешает обновления, не ломающие обратную совместимость по SemVer. |
Особенность карета (^) для версий до 1.0
По спецификации SemVer релизы 0.x.x считаются нестабильными, и любое минорное изменение может содержать ломающие правки. Поэтому:
^0.3.2эквивалентно>=0.3.2 <0.4.0;^0.0.4эквивалентно0.0.4.
Минимальная стабильность
По умолчанию Composer устанавливает только стабильные релизы (stable). Если проекту требуется альфа-, бета- или dev-версия:
{
"minimum-stability": "stable",
"prefer-stable": true,
"require": {
"vendor/package": "^2.1@RC"
}
}
Флаг стабильности @RC или @dev рядом с конкретным пакетом позволяет точечно подключить нестабильный релиз, сохраняя общее требование стабильности для остальных библиотек.
Автозагрузка PSR-4: от composer.json до исполнения
Стандарт PSR-4 сопоставляет пространства имен (namespaces) PHP с физической структурой каталогов файловой системы.
Настройка секций autoload и autoload-dev
{
"autoload": {
"psr-4": {
"App\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"App\\Tests\\": "tests/"
}
}
}
- Префикс пространства имен обязательно оканчивается двойным обратным слешем
\\. Это исключает ошибочное сопоставление похожих пространств имен (например,AppHelper\не совпадет с правилом дляApp\). - Класс
App\Services\PaymentServiceбудет искаться по путиsrc/Services/PaymentService.php. autoload-devзагружается только при обычной установке и полностью исключается при сборке с флагом--no-dev.
Оптимизация автозагрузчика для продакшена
В режиме разработки Composer проверяет файловую систему на наличие файлов при каждом обращении к новому классу. На продакшене это создает избыточные системные вызовы stat().
Для генерации статической карты классов (classmap) выполняется:
composer dump-autoload --optimize --no-dev --classmap-authoritative
--optimize(-o): конвертирует правила PSR-4/PSR-0 в плоский массив соответствийкласс => файлв файлеvendor/composer/autoload_classmap.php.--classmap-authoritative(-a): запрещает автозагрузчику искать файлы на диске, если класс не найден в сгенерированной карте классов. Если класса нет в карте — сразу выбрасывается ошибка, минуя обращение к файловой системе.
Platform Requirements: синхронизация сред разработки и продакшена
Composer учитывает не только PHP-пакеты, но и саму среду исполнения: версию интерпретатора PHP и установленные расширения (ext-*).
Декларация требований среды в require
Фиксация расширений предотвращает установку проекта на сервер, где отсутствуют необходимые модули:
{
"require": {
"php": "^8.2",
"ext-pdo": "*",
"ext-mbstring": "*",
"ext-intl": "*"
}
}
Секция config.platform
Если разработчик локально использует PHP 8.3, а production-сервер работает на PHP 8.2, стандартный composer update может подтянуть версии пакетов, несовместимые с PHP 8.2.
Чтобы привязать расчет зависимостей к целевой среде, используется секция platform:
{
"config": {
"platform": {
"php": "8.2.18",
"ext-intl": "1.0.0"
}
}
}
Composer будет строить дерево зависимостей так, будто проект всегда запускается на PHP 8.2.18, независимо от реальной версии интерпретатора на машине сборки.
Воспроизводимая сборка в CI/CD: практический сценарий
Production-команда установки
В пайплайнах деплоя используется строго определенный набор флагов:
composer install \
--no-dev \
--no-interaction \
--prefer-dist \
--optimize-autoloader \
--classmap-authoritative \
--no-progress \
--no-scripts
Разбор флагов:
--no-dev: исключает пакеты из секцииrequire-dev(тестовые фреймворки, статические анализаторы).--no-interaction: отключает любые интерактивные вопросы CLI.--prefer-dist: скачивает архивы (zip), а не клонирует git-репозитории, что ускоряет сборку.--optimize-autoloaderи--classmap-authoritative: генерируют максимально производительную карту классов.--no-progress: отключает анимацию прогресс-бара, снижая объем логов в CI.--no-scripts: предотвращает автоматический запуск сторонних хуков во время сборки контейнера (скрипты миграций или прогрева кэша запускаются отдельными шагами).
Проверка консистентности файлов
Перед установкой зависимостей в CI рекомендуется валидировать файлы проекта:
# Проверка синтаксиса и синхронизации composer.json и composer.lock
composer validate --strict --no-check-all
Если разработчик изменил composer.json, но забыл обновить composer.lock, флаг --strict вернет ненулевой код возврата и остановит пайплайн.
Пример конфигурации GitHub Actions с кэшированием
name: Build and Test
on: [push, pull_request]
jobs:
composer:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.2'
extensions: mbstring, pdo, intl
coverage: none
- name: Get Composer Cache Directory
id: composer-cache
run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
- name: Cache dependencies
uses: actions/cache@v4
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
restore-keys: ${{ runner.os }}-composer-
- name: Validate composer files
run: composer validate --strict
- name: Install dependencies
run: |
composer install --no-interaction --prefer-dist --optimize-autoloader
Чек-лист: диагностика и устранение конфликтов зависимостей
Когда composer update или добавление нового пакета завершается ошибкой Your requirements could not be resolved to an installable set of packages, используйте следующий алгоритм:
- Определите инициатора конфликта. Выполните проверку причин установки конкретной версии:
# Почему установлен данный пакет composer why symfony/console # Почему нельзя установить желаемую версию composer why-not symfony/console 6.4.0 - Проверьте ограничения платформы. Убедитесь, что требуемый пакет не конфликтует с локальной версией PHP или расширениями:
composer check-platform-reqs - Проанализируйте транзитивные зависимости. Если пакет
AтребуетC: ^1.0, а пакетBтребуетC: ^2.0, они не могут сосуществовать. Проверьте репозитории библиотек на предмет наличия обновлений, поддерживающих более новые версии общего пакетаC. - Устраните устаревшие блокировки. Не удаляйте
composer.lockцеликом. Попробуйте обновить конфликтующие ветки точечно:composer update vendor/package-a vendor/package-b --with-all-dependencies
Частые вопросы (FAQ)
Что делать, если composer.lock вызывает конфликт при git merge?
Не разрешайте конфликт в composer.lock вручную в текстовом редакторе. Правильный порядок действий:
- Разрешите конфликт в
composer.jsonвручную, выбрав нужные диапазоны зависимостей. - Зафиксируйте
composer.json. - Запустите
composer update --lock— эта команда пересчитаетcomposer.lockна основе разрешенногоcomposer.json, обновляя только метаданные без неконтролируемого обновления остальных пакетов. - Добавьте обновленный
composer.lockв коммит.
Почему нельзя запускать composer update на продакшене?
composer update рассчитывает дерево зависимостей заново. Если один из авторов ваших транзитивных зависимостей выпустил минорный релиз с ошибкой, composer update скачает его прямо на сервер. Кроме того, процесс разрешения зависимостей требует значительного объема оперативной памяти и процессорного времени, что может вызвать сбой рабочего сервера из-за Out Of Memory (OOM).
В чём разница между autoload и autoload-dev?
Секция autoload содержит правила загрузки для боевого кода приложения (src/). Секция autoload-dev предназначена для тестового окружения, фикстур и инструментов разработки (tests/). При запуске composer install --no-dev правила из autoload-dev не попадают в итоговую карту классов в vendor/autoload.php.
Зачем нужен флаг --optimize-autoloader (-o)?
Он создает единый массив соответствий всех классов их физическим путям (autoload_classmap.php). Интерпретатору не приходится проверять наличие файла на диске через правила PSR-4 — файл подключается по готовому пути из памяти, что снижает время отклика приложения.
Как обойти несовпадение локальной версии PHP без поломки сборки?
Используйте секцию config.platform.php в composer.json. Не используйте флаг --ignore-platform-reqs при повседневной разработке, так как он полностью отключает проверки и позволяет установить пакеты, код которых физически не сможет выполниться на целевом интерпретаторе.
Заключение
Стабильность инфраструктуры на базе PHP строится на разделении манифеста composer.json и фиксатора состояния composer.lock. Использование предсказуемых ограничений версий, настройка config.platform под целевой сервер и вызов composer install с оптимизацией автозагрузки в CI/CD обеспечивают детерминированную сборку, исключая расхождения между средами разработки и продакшена.



.svg.webp)




