Что делает Composer
Composer — стандартный менеджер зависимостей для PHP-проектов. Он отвечает за три вещи: описывает, какие пакеты нужны проекту; вычисляет совместимый набор версий; генерирует autoload-файл, через который код из vendor/ и ваш src/ подключаются без ручных require по всему приложению.
Важно: Composer не является частью языка PHP. Это внешний CLI-инструмент и формат проекта вокруг composer.json, composer.lock и директории vendor/. Но в современной PHP-разработке он почти такой же базовый, как php.ini, Autoloading и PSR-4 и соглашения PSR-1, PSR-12 и стиль кода.
Типичный проект выглядит так:
my-app/
composer.json
composer.lock
public/
index.php
src/
App.php
tests/
AppTest.php
vendor/
autoload.php
...сторонние пакеты...composer.json коммитят всегда. composer.lock для приложений тоже обычно коммитят, потому что он фиксирует точные версии зависимостей. vendor/ в Git обычно не кладут: его восстанавливают командой Composer.
flowchart TD
A[composer.json] --> B[Composer resolve]
B --> C[composer.lock]
B --> D[vendor/ packages]
D --> E[vendor/autoload.php]
E --> F[Application code]
A --> G[require / require-dev]
A --> H[autoload / autoload-dev]
A --> I[scripts]
J[Packagist or custom repositories] --> Bcomposer.json как контракт проекта
Минимальный composer.json может быть очень коротким:
{
"require": {
"php": "^8.3",
"ext-mbstring": "*",
"monolog/monolog": "^3.0"
}
}Секция require описывает то, без чего проект не запустится в normal runtime. Сюда относятся PHP-версия, обязательные расширения (ext-mbstring, ext-pdo, ext-json) и библиотеки, которые используются в production-коде. Если забыть указать расширение, Composer может успешно установить зависимости на машине, где расширение есть, а потом приложение упадёт в другом окружении. Это рядом с темой Версии PHP и режимы выполнения: Composer проверяет platform requirements, но только если они честно описаны.
Название пакета имеет форму vendor/package: например, monolog/monolog, symfony/console, phpunit/phpunit. Левая часть не обязательно компания; это namespace владельца в экосистеме Composer, чтобы не было конфликта между двумя пакетами с одинаковой короткой идеей вроде json или logger.
Ограничение версии — это не «поставь ровно вот это». Запись ^3.0 означает совместимые релизы в рамках правила Composer для caret constraints: обычно от 3.0.0 до, но не включая, 4.0.0. Для приложений это удобно: можно получать patch/minor-обновления при явном composer update, но не перескочить на следующий major случайно.
require-dev: инструменты разработки отдельно
Зависимости, нужные только разработчикам и CI, кладут в require-dev:
{
"require": {
"php": "^8.3",
"ext-pdo": "*"
},
"require-dev": {
"phpunit/phpunit": "^11.0",
"phpstan/phpstan": "^2.0",
"friendsofphp/php-cs-fixer": "^3.0"
}
}PHPUnit не нужен пользователю сайта, но нужен команде; статический анализатор не нужен request-response коду, но нужен перед merge. Поэтому composer install --no-dev на production не должен ставить эти пакеты. Это напрямую связано с PHPUnit, моки и стабы, PHPDoc, generics и статический анализ и CI, линтеры и автоматические проверки.
Частая ошибка — положить production-библиотеку в require-dev, потому что «сейчас она нужна только в консольной команде». Если эта команда запускается в production, зависимость должна быть в require. И наоборот: не надо тянуть phpunit/phpunit в production-runtime только потому, что тесты живут в том же репозитории.
Packagist и repositories
Packagist — основной публичный репозиторий Composer-пакетов. Если в composer.json нет секции repositories, Composer ищет пакеты в Packagist. Поэтому команда:
composer require monolog/monologдобавляет пакет в composer.json, пересчитывает зависимости и скачивает код в vendor/ без отдельного указания URL.
Секция repositories нужна, когда пакет лежит не в Packagist: приватный Composer-репозиторий, VCS-репозиторий, временный fork, корпоративный mirror.
{
"repositories": [
{
"type": "vcs",
"url": "https://github.com/acme/private-tools"
}
],
"require": {
"acme/private-tools": "dev-main"
}
}С этим нужно быть аккуратным. Порядок репозиториев значим: Composer смотрит сверху вниз, и кастомный источник может переопределить пакет из Packagist. Для легаси-фикса это удобно; для supply-chain безопасности — место, которое стоит ревьюить так же внимательно, как composer.lock.
Autoload: связь Composer и кода
После установки Composer генерирует vendor/autoload.php. Входной файл приложения обычно подключает именно его:
<?php
require __DIR__ . '/../vendor/autoload.php';
$app = new App\App();
$app->run();Для собственного кода чаще всего используют PSR-4:
{
"autoload": {
"psr-4": {
"App\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"App\\Tests\\": "tests/"
}
}
}Теперь класс App\Service\Mailer ожидается в файле src/Service/Mailer.php. Тестовый namespace App\Tests\ живёт отдельно и не загрязняет production autoload. После изменения autoload или autoload-dev запускают:
composer dump-autoloadВ production часто используют оптимизацию autoload-карты:
composer install --no-dev --optimize-autoloaderЭто не заменяет понимания Неймспейсы и use: Composer не «угадывает» неправильный namespace. Он следует правилам autoload-маппинга.
Scripts: короткие команды проекта
scripts — место для команд, которые команда проекта запускает одинаково у всех:
{
"scripts": {
"test": "phpunit",
"analyse": "phpstan analyse src tests",
"cs": "php-cs-fixer fix --dry-run --diff",
"check": [
"@cs",
"@analyse",
"@test"
]
}
}Теперь вместо длинного набора команд можно выполнить:
composer checkХороший script не прячет магию, а фиксирует договорённость. Если в CI запускается composer check, локально должна запускаться та же команда. Тогда меньше ситуаций «у меня всё прошло, а на merge request упало».
Есть и event scripts: например, post-install-cmd или post-update-cmd. Они могут быть полезны, но их нельзя добавлять бездумно. Composer-скрипты выполняют код во время установки зависимостей, поэтому незнакомый composer.json и незнакомые плагины — это не просто текстовый конфиг, а часть доверенной supply-chain поверхности.
Практичный пример composer.json
Для небольшого веб-приложения без фреймворка разумный старт может выглядеть так:
{
"name": "acme/mini-app",
"description": "Small PHP application",
"type": "project",
"require": {
"php": "^8.3",
"ext-json": "*",
"ext-pdo": "*",
"monolog/monolog": "^3.0"
},
"require-dev": {
"phpunit/phpunit": "^11.0",
"phpstan/phpstan": "^2.0"
},
"autoload": {
"psr-4": {
"App\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"App\\Tests\\": "tests/"
}
},
"scripts": {
"test": "phpunit",
"analyse": "phpstan analyse src tests",
"check": ["@analyse", "@test"]
}
}Здесь видно главное разделение: runtime-зависимости отдельно, dev-инструменты отдельно, namespace-маппинг отдельно, повторяемые проверки отдельно. Уже следующий слой — как Composer выбирает точные версии, чем install отличается от update, почему composer.lock нужен в CI и как работает composer audit; это отдельная тема composer.lock, install/update и audit.
См. также
- Autoloading и PSR-4 — как namespace превращается в путь к PHP-файлу.
- composer.lock, install/update и audit — точные версии, reproducible installs и проверка уязвимостей.
- PSR-1, PSR-12 и стиль кода — соглашения, которые часто идут рядом с Composer-проектами.
- PHPDoc, generics и статический анализ — типизация над native types и инструменты вроде PHPStan/Psalm.
- PHPUnit, моки и стабы — зачем тестовый фреймворк обычно живёт в
require-dev. - CI, линтеры и автоматические проверки — как Composer scripts становятся частью pipeline.
- Версии PHP и режимы выполнения — почему
phpиext-*constraints должны соответствовать реальному окружению.