Namespaces, Composer и autoload (PSR-4)
Namespaces, Composer и autoload
Пока ты пишешь один файл - require_once спасает. Как только в проекте 50+ классов, ты упираешься в две проблемы: коллизии имён (class User в админке и class User в API - конфликт) и ручные require (каждый новый класс требует подключения). Лекарство - namespaces + автозагрузка + Composer.
Namespace: пространство имён
<?php
// файл: src/Auth/User.php
namespace App\Auth;
final class User
{
public function __construct(public readonly string $login) {}
}
<?php
// файл: src/Blog/User.php
namespace App\Blog;
final class User
{
public function __construct(public readonly string $nickname) {}
}
Это два разных класса: App\Auth\User и App\Blog\User. Они могут спокойно сосуществовать.
Полностью квалифицированное имя (FQCN - fully qualified class name) - это \App\Auth\User. Ведущий \ - корень.
use - импорт имён
<?php
// файл: src/Controller/HomeController.php
namespace App\Controller;
use App\Auth\User;
use App\Blog\User as BlogUser;
final class HomeController
{
public function index(): void
{
$auth = new User('admin'); // App\Auth\User
$blog = new BlogUser('reader'); // App\Blog\User
}
}
Без use пришлось бы каждый раз писать FQCN: new \App\Auth\User('admin'). as - алиас, нужен только при конфликтах.
use импортирует только в этом файле. В соседнем файле нужно писать свои use.
Импорт функций и констант
По умолчанию use импортирует классы. Для функций/констант - use function / use const:
<?php
namespace App\Helpers;
function slugify(string $s): string { return strtolower(str_replace(' ', '-', $s)); }
const MAX_LENGTH = 255;
<?php
namespace App\Controller;
use function App\Helpers\slugify;
use const App\Helpers\MAX_LENGTH;
echo slugify('Привет мир');
echo MAX_LENGTH;
Composer: пакетный менеджер
В Go есть go mod. В PHP - Composer. Это де-факто стандарт: установка зависимостей, автозагрузка, скрипты.
Установка (один раз на машину):
# macOS / Linux
curl -sS https://getcomposer.org/installer | php
sudo mv composer.phar /usr/local/bin/composer
composer --version
Инициализация проекта:
composer init
Появится composer.json примерно такого вида:
{
"name": "myapp/site",
"type": "project",
"require": {
"php": ">=8.2"
},
"autoload": {
"psr-4": {
"App\\": "src/"
}
}
}
И composer dump-autoload создаёт vendor/autoload.php - единый файл, подключение которого закрывает все require сразу:
<?php
// public/index.php
require __DIR__ . '/../vendor/autoload.php';
use App\Controller\HomeController;
(new HomeController())->index();
Любой класс из src/ подгрузится автоматически по мере обращения.
PSR-4: автозагрузка по конвенции
PSR-4 - это стандарт того, как namespace отображается на путь. Правило: префикс namespace → корень директории. Подкаталоги соответствуют поднеймспейсам, имя файла равно имени класса.
composer.json:
"autoload": { "psr-4": { "App\\": "src/" } }
src/Auth/User.php → App\Auth\User
src/Auth/Service/Login.php → App\Auth\Service\Login
src/Blog/User.php → App\Blog\User
Регистр имени файла должен совпадать с регистром класса - иначе на Linux (case-sensitive) код не загрузится в продакшне, хотя на macOS (case-insensitive) работал.
Установка зависимостей
composer require monolog/monolog
composer require --dev phpunit/phpunit
require - для прод-зависимостей, --dev - только для разработки (PHPUnit на сервере не нужен). Composer создаст vendor/ и composer.lock (фиксирует точные версии - коммить в репозиторий).
Использование установленного пакета:
<?php
require __DIR__ . '/vendor/autoload.php';
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
$log = new Logger('app');
$log->pushHandler(new StreamHandler('php://stdout'));
$log->info('Готово!');
Никаких require для Monolog - autoloader сам нашёл файлы.
Semver и операторы версий
{
"require": {
"monolog/monolog": "^3.5"
}
}
^3.5->=3.5.0 <4.0.0(любые минорные/патч, без мажорного бамба) - дефолт~3.5->=3.5.0 <3.6.0(только патчи)3.5.*->=3.5.0 <3.6.0(то же)3.5.0- точно эта версия>=3.5,<4.0- диапазон вручную
^ подходит почти всегда: фиксирует мажор (где ломающие изменения), позволяет получать минорные обновления и патчи.
composer update - обновляет до новых версий в рамках указанных диапазонов. composer install - ставит ровно то, что в composer.lock (так делает CI и прод).
composer.lock - единственный источник правды
composer.lock фиксирует точные версии всех зависимостей (включая транзитивные) и их хеши. Это гарантирует, что у тебя на ноуте и у коллеги (и в проде) - идентичные версии.
composer install # ставит из lock-файла
composer update # обновляет всё в рамках constraints + перезаписывает lock
composer update monolog/* # обновляет только Monolog
Никогда не коммить vendor/ - она генерируется. Всегда коммить composer.lock (исключение - библиотеки, которые сами кому-то ставятся).
Дополнительные блоки composer.json
{
"autoload": {
"psr-4": { "App\\": "src/" },
"files": ["src/helpers.php"]
},
"autoload-dev": {
"psr-4": { "Tests\\": "tests/" }
},
"scripts": {
"test": "phpunit",
"lint": "phpcs --standard=PSR12 src/"
},
"config": {
"sort-packages": true
}
}
files- подгружаются всегда, не по требованию (для глобальных функций-хелперов)autoload-dev- отдельный раздел для тестов (не попадает в прод)scripts- алиасы для команд:composer testзапустит phpunit
Как это в Symfony
Каркас Symfony - это набор Composer-пакетов. Создание проекта:
composer create-project symfony/skeleton:^7.0 myapp
cd myapp
composer require symfony/orm-pack
composer require --dev symfony/maker-bundle
Каждая «фича» - отдельный пакет (symfony/security-bundle, symfony/messenger, symfony/twig-bundle). Их можно ставить по мере необходимости. Все они используют PSR-4 автозагрузку через Composer - без неё современный PHP не существует. Полный обзор PSR - в уроке 17.
Структура папок в Symfony:
src/
Controller/ → App\Controller\
Entity/ → App\Entity\
Repository/ → App\Repository\
Service/ → App\Service\
config/
public/index.php
composer.json → "App\\": "src/"
Типичные ошибки
- Несоответствие namespace и пути. Файл
src/Foo/Bar.php, а внутриnamespace App\Foo;- autoloader не найдёт. Должно совпадать:namespace App\Foo;+ классBar. requireв боевом коде. В PHP-проекте с Composer ручныеrequire/includeдля своего кода - anti-pattern. Всё через autoloader.- Класс без namespace в
src/. PSR-4 ждёт namespace. Если забыл -ClassNotFoundErrorили класс попадёт в глобальный namespace и сломает другие места. - Не коммитить
composer.lock. На проде получишь «работает у меня». Lock-файл - это воспроизводимость. composer installна проде с подкачкой из интернета. Используйcomposer install --no-dev --optimize-autoloaderи кэшируйvendor/в CI.
Best practices
- Следи за точным соответствием namespace и пути по PSR-4:
App\Auth\User→src/Auth/User.php, включая регистр - на Linux это критично. - Всегда коммить
composer.lockи добавляйvendor/в.gitignore- lock гарантирует одинаковые версии на всех машинах. - На проде запускай
composer install --no-dev --optimize-autoloader- без dev-зависимостей и с оптимизированным автолоадером. - Используй
^для диапазонов версий зависимостей (^3.5): фиксирует мажор (ломающие изменения), но позволяет получать патчи автоматически. - Не пиши ручные
requireдля своих классов в проекте с Composer - всё черезvendor/autoload.php.
Мини-задание
- Создай
composer.jsonс PSR-4App\\: src/ - Запусти
composer dump-autoload, создайsrc/Auth/User.php(namespace App\Auth; class User) иsrc/Blog/User.php(namespace App\Blog;) - Точка входа
public/index.php: подключиvendor/autoload.php, импортируй оба класса сas, создай по экземпляру - Установи Monolog:
composer require monolog/monolog. ВыведиLogger infoвphp://stdout - Добавь скрипт
composer test(даже если PHPUnit ещё не установлен - пусть запускаетphp -v)