Composer и автозагрузка
Управление зависимостями через Composer — lock-файл и ограничения semver, автозагрузка PSR-4 и пространства имён, require против require-dev, стандарты взаимодействия PSR от PHP-FIG и оптимизация автозагрузчика для продакшена.
13 вопросов
JuniorТеорияОчень частоКакую задачу решает Composer и что хранится в каталоге vendor/?
Какую задачу решает Composer и что хранится в каталоге vendor/?
Composer — менеджер зависимостей PHP уровня проекта. Нужные пакеты объявляются в composer.json, а composer install разрешает их версии, скачивает их в vendor/ и генерирует vendor/autoload.php. Каталог vendor/ — это результат сборки: его не правят руками и не коммитят.
Типичные ошибки
- ✗Считать
vendor/редактируемым исходником, а не сгенерированным результатом сборки - ✗Коммитить
vendor/вместоcomposer.jsonиcomposer.lock - ✗Думать, что Composer ставит пакеты глобально, как системный пакетный менеджер
Уточняющие вопросы
- →Что регистрирует
vendor/autoload.phpи откуда он подключается? - →Почему
composer installведёт себя иначе, чемcomposer update?
JuniorТеорияОчень частоПочему composer.lock коммитят в репозиторий и что именно он фиксирует?
Почему composer.lock коммитят в репозиторий и что именно он фиксирует?
composer.json задаёт диапазоны версий; composer.lock фиксирует точную версию, которую Composer реально выбрал для каждого пакета, включая транзитивные. Коммит делает composer install воспроизводимым — CI, коллеги и продакшен получают одинаковые зависимости. Пересчитывает и переписывает lock только composer update.
Типичные ошибки
- ✗Запускать
composer updateпри деплое — он пересчитывает версии и обесценивает lock - ✗Добавлять
composer.lockв.gitignore, делая сборки невоспроизводимыми - ✗Считать, что lock фиксирует только прямые зависимости, а не транзитивные
Уточняющие вопросы
- →Что произойдёт при
composer install, если lock рассинхронизирован сcomposer.json? - →Как обновить один пакет, не трогая остальную часть lock-файла?
JuniorТеорияОчень частоКак пространства имён предотвращают конфликт имён классов между двумя библиотеками?
Как пространства имён предотвращают конфликт имён классов между двумя библиотеками?
Пространство имён добавляет префикс каждому классу, интерфейсу и функции, объявленным в файле, поэтому Acme\Log\Logger и Vendor\Log\Logger — два разных полных имени, которые движок никогда не путает. Оба пакета могут поставлять класс с именем Logger: тождество задаёт полное имя с префиксом, а не короткое.
Типичные ошибки
- ✗Думать, что тождество класса движок определяет по короткому имени
- ✗Считать, что пространство имён само по себе что-то загружает или подключает
- ✗Верить, что два одинаковых полных имени могут сосуществовать в одном процессе
Уточняющие вопросы
- →Как разрешается неквалифицированное имя класса внутри файла с пространством имён?
- →Почему встроенный
Exceptionвнутри пространства имён пишут как\Exception?
JuniorТеорияЧастоЧем require отличается от require-dev в файле composer.json?
Чем require отличается от require-dev в файле composer.json?
require перечисляет пакеты, нужные приложению для работы; require-dev — инструменты, нужные только при разработке: PHPUnit, PHPStan, генераторы фикстур. Обычный composer install ставит оба блока, но composer install --no-dev при деплое пропускает dev-блок, давая меньший vendor/ и меньшую поверхность атаки.
Типичные ошибки
- ✗Тащить dev-инструменты в продакшен, забыв
--no-devпри деплое - ✗Класть в
require-devпакет, который реально нужен приложению в рантайме - ✗Считать, что
--no-devменяет автозагрузку, а не состав установленных пакетов
Уточняющие вопросы
- →Что сломается в рантайме, если продакшен-зависимость попала в
require-dev? - →Почему
--no-devменяет ещё и сгенерированную карту автозагрузчика?
JuniorТеорияЧастоЧто такое PHP-FIG и что именно стандартизирует PSR?
Что такое PHP-FIG и что именно стандартизирует PSR?
PHP-FIG — это группа мейнтейнеров фреймворков и библиотек, публикующая PSR — PHP Standard Recommendations. PSR не входит в язык, и движок его не проверяет; это согласованный контракт: набор интерфейсов (PSR-3 для логирования, PSR-7 для HTTP-сообщений) или соглашение (PSR-4 для автозагрузки, PSR-12 для стиля).
Типичные ошибки
- ✗Путать PHP-FIG с командой internals, голосующей по RFC языка
- ✗Думать, что движок проверяет PSR так же, как правило языка
- ✗Сводить любой PSR к правилу стиля, упуская стандарты-интерфейсы
Уточняющие вопросы
- →Какие PSR типичный фреймворк реализует сам, а какие лишь потребляет?
- →Что сломалось бы, если бы две библиотеки трактовали PSR-4 по-разному?
JuniorТеорияЧастоКак PSR-4 отображает полное имя класса на файл на диске?
Как PSR-4 отображает полное имя класса на файл на диске?
composer.json отображает префикс пространства имён на базовый каталог — например, App\ на src/. При первом обращении к неизвестному классу зарегистрированный автозагрузчик отрезает префикс, превращает оставшиеся разделители в разделители каталогов и добавляет .php, так что App\Http\Kernel превращается в src/Http/Kernel.php.
Типичные ошибки
- ✗Думать, что весь путь обязан повторять пространство имён без отрезания префикса
- ✗Ждать, что автозагрузчик сканирует каталоги, а не вычисляет один путь
- ✗Считать поиск нечувствительным к регистру, из-за чего файл с опечаткой якобы найдётся
Уточняющие вопросы
- →Что делает Composer, когда имени класса соответствуют сразу два префикса PSR-4?
- →Чем запись
classmapотличается от записиpsr-4вcomposer.json?
MiddleТеорияЧастоЧем ограничения версий ^ и ~ отличаются при семантическом версионировании?
Чем ограничения версий ^ и ~ отличаются при семантическом версионировании?
Оба разрешают обновления вплоть до следующего ломающего изменения, но проводят границу по-разному. ^1.2.3 допускает всё ниже 2.0.0 — минорные и патч-релизы. ~1.2.3 допускает только патчи, то есть всё ниже 1.3.0. На версии 0.x знак ^ ужесточается: ^0.2.3 остаётся ниже 0.3.0.
Типичные ошибки
- ✗Считать
~более свободным из двух ограничений - ✗Забывать, что
^на версии0.xтрактует смену минора как ломающую - ✗Думать, что
composer installпересчитывает ограничение вопреки lock-файлу
Уточняющие вопросы
- →Когда вы намеренно зафиксируете точную версию вместо диапазона?
- →Что пакет обещает, следуя семантическому версионированию, а чего не обещает?
JuniorТеорияИногдаЧто делает use в начале файла и чем он отличается от use в трейте или замыкании?
Что делает use в начале файла и чем он отличается от use в трейте или замыкании?
use в начале файла — это псевдоним времени компиляции: он позволяет писать Logger вместо Acme\Log\Logger в этом одном файле и при этом ничего не загружает. Две другие формы лишь переиспользуют тот же токен: use в теле класса подключает трейт, а use ($x) после сигнатуры замыкания захватывает внешнюю переменную.
Типичные ошибки
- ✗Думать, что
use-импорт загружает или подключает файл класса - ✗Ждать, что псевдоним, объявленный в одном файле, действует в других файлах
- ✗Путать
use-импорт с подключением трейта или захватом в замыкании
Уточняющие вопросы
- →Что меняет в разрешении имён запись
use Acme\Log\Logger as AppLogger? - →Почему
use ($x)захватывает по значению и чем от него отличаетсяuse (&$x)?
MiddleПроизводительностьИногдаЧего стоит автозагрузка PSR-4 на каждый класс и что меняет --optimize-autoloader?
Чего стоит автозагрузка PSR-4 на каждый класс и что меняет --optimize-autoloader?
Обычная PSR-4 превращает имя класса в путь и обращается к файловой системе — по одному stat на каждый подходящий префикс — при первом использовании класса, и так на каждый запрос. composer dump-autoload --optimize заранее строит статическую карту класс–файл, так что поиск сводится к чтению массива; --classmap-authoritative убирает и запасной путь PSR-4.
Типичные ошибки
- ✗Считать, что Opcache кэширует обращения автозагрузчика к диску (он кэширует только байткод)
- ✗Думать, что classmap пересобирается сам и при деплое
dump-autoloadне нужен - ✗Применять
--classmap-authoritativeк коду, порождающему классы в рантайме
Уточняющие вопросы
- →Почему
--classmap-authoritativeнебезопасен для кода, порождающего классы в рантайме? - →Как измерить долю автозагрузчика в запросе, прежде чем его оптимизировать?
MiddleТеорияИногдаКак стандарты совместимости PSR-7 и PSR-3 отвязывают библиотеку от фреймворка?
Как стандарты совместимости PSR-7 и PSR-3 отвязывают библиотеку от фреймворка?
Они описывают общие интерфейсы, а не реализации. Библиотека, объявляющая тип Psr\Log\LoggerInterface или ServerRequestInterface из PSR-7, одинаково работает под Monolog, Laravel и Symfony — фреймворку достаточно предоставить класс, реализующий контракт. Зависимость указывает на пакет с интерфейсами, а не на фреймворк.
Типичные ошибки
- ✗Думать, что PSR поставляет реализацию, а не набор интерфейсов
- ✗Объявлять тип конкретного класса фреймворка вместо интерфейса PSR
- ✗Считать, что совместимость достигается duck typing, а не общим контрактом
Уточняющие вопросы
- →Почему
psr/logпоставляет интерфейсы, но не поставляет собственную реализацию логгера? - →Что меняет неизменяемость объекта запроса PSR-7 для middleware, который его правит?
SeniorДизайнИногдаБольшое приложение на PHP разрослось так, что несколько его частей — биллинг, сервис уведомлений и общий HTTP-клиент — явно переиспользуемы и уже начали копироваться во второе приложение. Вас просят перестроить кодовую базу в приложение плюс набор внутренних Composer-пакетов. Опишите, как вы решите, что становится пакетом, а что остаётся кодом приложения, как эти пакеты будут автозагружаться и подключаться в повседневной разработке, как их публиковать и версионировать для второго приложения и на что смотреть после разделения кода.
Большое приложение на PHP разрослось так, что несколько его частей — биллинг, сервис уведомлений и общий HTTP-клиент — явно переиспользуемы и уже начали копироваться во второе приложение. Вас просят перестроить кодовую базу в приложение плюс набор внутренних Composer-пакетов. Опишите, как вы решите, что становится пакетом, а что остаётся кодом приложения, как эти пакеты будут автозагружаться и подключаться в повседневной разработке, как их публиковать и версионировать для второго приложения и на что смотреть после разделения кода.
Выносите то, что имеет устойчивый контракт и не зависит от приложения; всё, что знает про контроллеры и маршруты, остаётся в приложении. В разработке подключайте пакеты через репозиторий типа path, чтобы правки применялись сразу, а для второго приложения публикуйте тегированные релизы в приватный реестр. Каждый пакет владеет одним пространством имён и одним корнем PSR-4.
Типичные ошибки
- ✗Позволить пакету зависеть от приложения, создав циклическую зависимость
- ✗Делить по размеру каталогов, а не по устойчивому самодостаточному контракту
- ✗Забыть, что каждому пакету нужны своё пространство имён и свой корень PSR-4
Уточняющие вопросы
- →Чем репозиторий Composer типа
pathотличается от подключения опубликованной версии? - →Чего стоит версионировать все внутренние пакеты одним общим тегом?
SeniorПроизводительностьРедкоВаш CI тратит четыре минуты на composer install в каждом задании. Как это сократить?
Ваш CI тратит четыре минуты на composer install в каждом задании. Как это сократить?
Сначала убедитесь, что задание запускает именно composer install, а не update: при закоммиченном lock пересчитывать нечего. Затем кэшируйте глобальный каталог кэша Composer по ключу из хэша composer.lock, чтобы при неизменном lock переиспользовались прежние загрузки. В деплой-заданиях добавьте --no-dev --prefer-dist --no-progress.
Типичные ошибки
- ✗Запускать в CI
composer update, пересчитывая то, что уже решено lock-файлом - ✗Кэшировать
vendor/без ключа кэша по хэшу lock-файла - ✗Коммитить
vendor/в обход установки вместо кэширования загрузок
Уточняющие вопросы
- →Почему ключ кэша должен включать хэш lock-файла, а не имя ветки?
- →Когда кэширование
vendor/окажется ошибкой, хотя и выглядит быстрее?
SeniorДизайнРедкоВаша команда сопровождает внутренний Composer-пакет, которым пользуются шесть приложений. У широко используемого метода нужно изменить сигнатуру, и новое поведение несовместимо со старым. Пакет следует семантическому версионированию, а все потребители подключают его с ограничением через ^. Опишите, как вы выкатите это изменение: как версионируете, как потребители будут мигрировать, что сделаете, чтобы ни одно работающее приложение не сломалось на очередном composer update, и как поддержите приложение, которое действительно не сможет мигрировать ещё квартал.
Ваша команда сопровождает внутренний Composer-пакет, которым пользуются шесть приложений. У широко используемого метода нужно изменить сигнатуру, и новое поведение несовместимо со старым. Пакет следует семантическому версионированию, а все потребители подключают его с ограничением через ^. Опишите, как вы выкатите это изменение: как версионируете, как потребители будут мигрировать, что сделаете, чтобы ни одно работающее приложение не сломалось на очередном composer update, и как поддержите приложение, которое действительно не сможет мигрировать ещё квартал.
Поднимите мажорную версию: ограничение через ^ не даст потребителям подхватить её, поэтому composer update ничего не сломает. Перед этим выпустите минорную версию, добавляющую новый метод рядом со старым и помечающую старый как @deprecated, — приложения смигрируют по одному. Прежний мажор оставьте на ветке поддержки для отстающего.
Типичные ошибки
- ✗Выпускать несовместимое изменение как минор или патч — потребители получают его молча
- ✗Удалять старый метод в том же релизе, где появился новый
- ✗Прекращать поддержку прежнего мажора до того, как мигрировали все потребители
Уточняющие вопросы
- →Как вы обнаружите, что потребитель всё ещё вызывает устаревший метод?
- →Что изменится, если один из потребителей подключает пакет лишь транзитивно?