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: автозагрузка по конвенции

Маппинг namespace на путь к файлу через 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/"

Типичные ошибки

  1. Несоответствие namespace и пути. Файл src/Foo/Bar.php, а внутри namespace App\Foo; - autoloader не найдёт. Должно совпадать: namespace App\Foo; + класс Bar.
  2. require в боевом коде. В PHP-проекте с Composer ручные require/include для своего кода - anti-pattern. Всё через autoloader.
  3. Класс без namespace в src/. PSR-4 ждёт namespace. Если забыл - ClassNotFoundError или класс попадёт в глобальный namespace и сломает другие места.
  4. Не коммитить composer.lock. На проде получишь «работает у меня». Lock-файл - это воспроизводимость.
  5. composer install на проде с подкачкой из интернета. Используй composer install --no-dev --optimize-autoloader и кэшируй vendor/ в CI.

Best practices

  • Следи за точным соответствием namespace и пути по PSR-4: App\Auth\Usersrc/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-4 App\\: 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)

Зарегистрируйтесь бесплатно, чтобы пройти квиз, решить задание с автопроверкой и вести прогресс.