Composer в PHP: разбор проекта от autoload до lock-файла
Composer не просто скачивает пакеты. Он строит карту автозагрузки, разрешает конфликты версий и фиксирует состояние в lock-файле. Разбираем, что происходит внутри на примере реального проекта.

Вы запускаете composer install на новом сервере, а получаете набор версий, отличных от тех, что были в разработке. Или composer update внезапно тянет мажорную версию библиотеки и ломает половину кода. Оба случая — не баги, а следствие того, как Composer разрешает зависимости и работает с двумя своими файлами: composer.json и composer.lock. Разберём на примере проекта, что происходит между командой и готовым vendor/, где это ломается и что смотреть, когда версии расходятся. Речь о Composer 2.x — первая версия давно снята с поддержки.
Смежные темы вроде публикации своего пакета в Packagist и написания кастомных установщиков здесь не рассматриваем — только жизненный цикл зависимостей в обычном проекте.
Два файла, которые всё решают
Разница между composer.json и composer.lock — источник большинства проблем у тех, кто пользуется Composer по наитию.
composer.json описывает, чего вы хотите: ограничения версий, а не конкретные номера. Запись "monolog/monolog": "^3.0" означает «любая версия от 3.0.0 до, но не включая, 4.0.0». Это диапазон, а не точка.
composer.lock фиксирует то, что получилось: точные версии всех пакетов, включая транзитивные (зависимости ваших зависимостей), с хешами. Именно lock-файл позволяет добиться того, чтобы на сервере и у коллеги установилось ровно то же, что у вас.
Правило, которое экономит часы отладки: composer.lock обязательно коммитится в репозиторий для приложений. Не коммитится он только для переиспользуемых библиотек, у которых нет «финального» окружения.
Что делает install, а что — update
Две команды путают чаще всего, и цена ошибки — сломанный прод.
| Команда | Читает | Пишет | Когда запускать |
|---|---|---|---|
composer install | composer.lock (если есть) | vendor/ | Разворачивание проекта, деплой на сервер |
composer update | composer.json | composer.lock и vendor/ | Осознанное обновление зависимостей |
install при наличии lock-файла игнорирует диапазоны из composer.json и ставит зафиксированные версии. Если lock-файла нет — ведёт себя как update и создаёт его. update заново решает граф зависимостей по диапазонам и переписывает lock. Отсюда правило: на сервере только install, обновление версий — отдельное осознанное действие в разработке.
Как читать ограничения версий
Синтаксис ограничений — это язык, на котором вы говорите Composer, насколько сильно готовы доверять авторам пакета.
^3.2— совместимо по SemVer: разрешает3.2,3.9, но не4.0. Самый частый и разумный выбор.~3.2— разрешает3.2до3.9, но не4.0; а~3.2.1— только патчи3.2.x. Тильда фиксирует последний указанный компонент.3.2.*— любая патч-версия внутри3.2.>=3.2 <3.5— явный диапазон.3.2.1— ровно эта версия. Жёстко, но иногда нужно, чтобы прибить проблемный пакет.
Разница между ^ и ~ кусается: ^0.3 НЕ разрешает 0.4, потому что для версий ниже 1.0 SemVer считает минорный номер потенциально ломающим. Это ловушка, если пакет ещё не дорос до стабильного релиза.
Разбор на примере
Соберём минимальный проект и посмотрим, что Composer делает на каждом шаге. Понадобится PHP 8.1 или новее и установленный Composer 2.x. Проверьте версию:
composer --version
# Composer version 2.x.x
Инициализируем проект без интерактивных вопросов и добавим зависимость:
# создаём composer.json с указанием минимальной версии PHP
composer init --name="acme/demo" --require="php:^8.1" --no-interaction
# добавляем логгер — Composer сам подберёт совместимую версию
composer require monolog/monolog:^3.0
После этого в composer.json появится примерно такое:
{
"name": "acme/demo",
"require": {
"php": "^8.1",
"monolog/monolog": "^3.0"
}
}
А в composer.lock — точная версия Monolog, которая была актуальной на момент установки, плюс её зависимость psr/log, которую вы явно не запрашивали. Это и есть транзитивная зависимость: Monolog реализует интерфейсы PSR-3, и они подтянулись автоматически.
Автозагрузка: где живёт магия require
После установки вы пишете use Monolog\Logger; и класс находится без единого require. Отвечает за это файл vendor/autoload.php, который генерирует Composer. Подключите его один раз в точке входа:
<?php
require __DIR__ . '/vendor/autoload.php';
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
$log = new Logger('demo');
$log->pushHandler(new StreamHandler(__DIR__ . '/app.log', Logger::WARNING));
$log->warning('Первое сообщение через автозагрузку');
Composer поддерживает несколько стратегий автозагрузки. Основная сегодня — PSR-4: соответствие между префиксом пространства имён и папкой. Свой код тоже стоит зарегистрировать в composer.json:
{
"autoload": {
"psr-4": {
"Acme\\": "src/"
}
}
}
Теперь класс Acme\Service\Mailer Composer будет искать в файле src/Service/Mailer.php. После правки секции autoload карту нужно перегенерировать, иначе новый класс не найдётся:
# перестроить карту автозагрузки
composer dump-autoload
Где это ломается
Три сценария, в которые упираются почти все.
Класс не найден после добавления файла
Вы создали src/Service/Mailer.php, а получаете Class "Acme\Service\Mailer" not found. Причина почти всегда одна из двух: имя файла или папки не совпадает с namespace по регистру (на Linux регистр важен, на macOS и Windows — нет, поэтому проблема всплывает только на сервере), либо забыли выполнить composer dump-autoload после изменения секции autoload. Проверьте точное соответствие Acme\Service → src/Service.
Не удаётся разрешить зависимости
Сообщение вида Your requirements could not be resolved to an installable set of packages означает конфликт: два пакета требуют несовместимые версии третьего, или запрошенная версия несовместима с вашей версией PHP. Composer 2.x обычно печатает цепочку — читайте её снизу вверх, там указан корень конфликта. Полезная команда для диагностики:
# показать, кто и почему требует конкретный пакет
composer why psr/log
# показать, что мешает установить версию
composer why-not monolog/monolog 3.5
На сервере встала не та версия
Если локально работает, а на деплое поведение другое, почти наверняка на сервере запустили composer update вместо install, либо lock-файл не попал в репозиторий. Для продового деплоя используйте:
composer install --no-dev --optimize-autoloader
Флаг --no-dev пропускает пакеты из секции require-dev (тесты, отладка), --optimize-autoloader строит статическую карту классов вместо сканирования папок в рантайме — заметно быстрее на боевом коде.
require-dev и почему это важно
Секция require-dev — для инструментов, которые нужны только разработчику: PHPUnit, статические анализаторы, дебаггеры. На проде их присутствие — это лишний код, потенциальные уязвимости и раздутый vendor/. Устанавливаются они так:
composer require --dev phpunit/phpunit
При обычном composer install они ставятся, при --no-dev — нет. Разделяйте зависимости сразу: перенести пакет из require в require-dev задним числом — значит выяснять, не тянет ли его продовый код.
Что делать дальше
Настройте пайплайн деплоя так, чтобы он всегда выполнял composer install --no-dev --optimize-autoloader и никогда — update. Добавьте composer.lock в репозиторий, если его там ещё нет. Для регулярной проверки уязвимостей в зависимостях запускайте composer audit — команда сверяет установленные версии с базой известных проблем безопасности. А перед крупным обновлением фиксируйте, что именно поменяется: composer update --dry-run покажет план без записи в lock-файл.
Логотип и фирменный стиль: Три концепции разного характера, палитра с обоснованием, проверка на читаемость в малом размере и в один цвет.
Спросить за 60 ₽Источники: Composer — Official Documentation, Composer — Versions and constraints
Частые вопросы
А если у меня Composer 1.x, эти команды сработают?
Нужно ли коммитить папку vendor в git?
Composer update сломал проект — как откатиться?
Почему composer install игнорирует мои изменения в composer.json?
Класс из моего src/ не находится даже после dump-autoload — что ещё проверить?
В чём разница между ^ и ~ в ограничениях версий?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.