Composer, автозагрузка PSR-4 и пространства имён
Вопрос про инструмент, которым пользуются каждый день, но редко задумываются, как он устроен.
PSR-4 — стандарт автозагрузки, который задаёт соответствие между префиксом пространства имён и каталогом на диске:
App\Service\Mailerищется вsrc/Service/Mailer.php.
Вопрос 1: «Что происходит, когда вы пишете new App\Service\Mailer(), а файл не подключён?»
Что проверяет интервьюер
Понимаете ли вы, что автозагрузка — не магия Composer, а механизм самого языка. Когда PHP встречает неизвестный класс, он по очереди вызывает функции, зарегистрированные через spl_autoload_register(), передавая им полное имя класса. Задача автозагрузчика — подключить нужный файл; если ни один этого не сделал, будет Error: Class not found.
<?php
// упрощённая версия того, что делает Composer
spl_autoload_register(function (string $class): void {
$prefix = 'App\\';
$baseDir = __DIR__ . '/src/';
if (!str_starts_with($class, $prefix)) {
return; // не наш префикс — пусть попробует следующий загрузчик
}
$relative = substr($class, strlen($prefix));
$file = $baseDir . str_replace('\\', '/', $relative) . '.php';
if (is_file($file)) {
require $file;
}
});
Composer генерирует такой загрузчик автоматически по секции autoload в composer.json:
{
"require": {
"php": ">=8.2",
"monolog/monolog": "^3.5"
},
"require-dev": {
"phpunit/phpunit": "^11.0"
},
"autoload": {
"psr-4": { "App\\": "src/" },
"files": ["src/helpers.php"]
},
"autoload-dev": {
"psr-4": { "App\\Tests\\": "tests/" }
}
}
После этого достаточно одной строки require __DIR__ . '/vendor/autoload.php'; во входном файле — дальше классы подтягиваются сами. На проде обязательно собирают оптимизированную карту классов: composer install --no-dev --optimize-autoloader превращает поиск по файловой системе в готовый массив «класс → путь».
Вопрос 2: «Чем composer.json отличается от composer.lock?»
| composer.json | что хочет проект: диапазоны версий (^3.5), скрипты, автозагрузка |
| composer.lock | что реально установлено: точные версии и хэши всех пакетов, включая транзитивные |
Правила, которые ждут в ответе: lock-файл коммитится в репозиторий; на сервере запускается composer install (ставит ровно то, что в lock), а composer update — только осознанно и локально, потому что он двигает версии и переписывает lock. Оператор ^3.5 разрешает обновления до 4.0 (не включая), ~3.5 — только до 3.6 (не включая).
Вопрос 3: «Как работают пространства имён?»
<?php
namespace App\Mail {
class Mailer {
public function send(string $to): void {
echo "письмо для $to отправлено\n";
}
}
}
namespace App {
use App\Mail\Mailer;
$mailer = new Mailer();
$mailer->send('user@example.com');
echo Mailer::class, "\n";
echo \strlen('abc'), "\n";
echo __NAMESPACE__, "\n";
}
Вывод:
письмо для user@example.com отправлено App\Mail\Mailer 3 App
Что здесь стоит проговорить:
use— это псевдоним времени компиляции, а не подключение файла. Он лишь говорит «в этом файлеMailerозначаетApp\Mail\Mailer».- Имя, начинающееся с обратного слэша (
\strlen), — абсолютное. Без слэша PHP сначала ищетApp\strlenи только потом, для функций и констант, откатывается к глобальному пространству. Для классов такого отката нет:new Exception()внутри namespace безuseвызовет ошибку. Mailer::classвозвращает полное имя строкой — удобно и безопасно при рефакторинге.- Два блока namespace в одном файле разрешены синтаксически, но в реальном коде так не пишут: PSR-4 требует один класс на файл.
Про PSR в целом
PSR (PHP Standard Recommendation) — рекомендации группы PHP-FIG, благодаря которым библиотеки разных авторов совместимы между собой. Достаточно назвать основные:
| PSR-1 / PSR-12 | стиль кода и оформление файлов |
| PSR-4 | автозагрузка классов по namespace |
| PSR-3 | интерфейс логгера (LoggerInterface) |
| PSR-6 / PSR-16 | кэш: полный и упрощённый интерфейсы |
| PSR-7 / PSR-15 / PSR-17 | HTTP-сообщения, middleware и фабрики |
| PSR-11 | контейнер внедрения зависимостей |
Типичные ошибки кандидатов
- Думают, что
useподключает файл. Файл подключает автозагрузчик,use— только сокращает имя. - Не коммитят
composer.lockили запускают на продеcomposer update— и получают неожиданные версии пакетов. - Путают
requireиrequire-dev, из-за чего PHPUnit уезжает на прод. - Не знают про
--optimize-autoloaderи про то, что PSR-4 чувствителен к регистру:App\serviceиApp\Service— разные вещи на Linux. - Кладут в один файл несколько классов и удивляются, что автозагрузка их не находит.
Как ответить кратко
«PHP при встрече с неизвестным классом зовёт функции из
spl_autoload_register. Composer по секцииautoload.psr-4генерирует такой загрузчик: префикс namespace отображается в каталог, имя класса — в имя файла.composer.jsonописывает желаемые диапазоны версий,composer.lockфиксирует точные — его коммитим, на сервере делаемcomposer install --no-dev --optimize-autoloader.use— это псевдоним на этапе компиляции, а не подключение файла».