Что делает autoloading

Autoloading в PHP — это механизм, который загружает файл с классом в тот момент, когда код впервые обращается к ещё не объявленному class-like имени: классу, интерфейсу, trait или enum. Он не заменяет Неймспейсы и use: namespace даёт имя, use сокращает это имя внутри файла, а autoloader отвечает за вопрос «из какого .php-файла взять объявление этого имени?».

Без автозагрузки большой OOP-проект быстро превращается в список require_once в начале каждого entrypoint. С автозагрузкой код обычно начинается с одного bootstrap-файла:

<?php

declare(strict_types=1);

require __DIR__ . '/vendor/autoload.php';

use App\Billing\Invoice;

$invoice = new Invoice(1500);

Когда PHP доходит до new Invoice(...), имя уже развёрнуто в App\Billing\Invoice. Если такого класса ещё нет в памяти, PHP вызывает зарегистрированные autoloader-функции. В современных проектах почти всегда используется Composer autoloader.

flowchart TD A["Код обращается к App\\Billing\\Invoice"] --> B{"Класс уже объявлен?"} B -- "да" --> C["PHP использует класс"] B -- "нет" --> D["PHP вызывает зарегистрированные autoloader'ы"] D --> E["Composer читает PSR-4 mapping: App\\ => src/"] E --> F["App\\Billing\\Invoice -> src/Billing/Invoice.php"] F --> G{"Файл найден и объявил нужный FQCN?"} G -- "да" --> C G -- "нет" --> H["Следующий autoloader или ошибка Class not found"]
flowchart TD
    A["Код обращается к App\\Billing\\Invoice"] --> B{"Класс уже объявлен?"}
    B -- "да" --> C["PHP использует класс"]
    B -- "нет" --> D["PHP вызывает зарегистрированные autoloader'ы"]
    D --> E["Composer читает PSR-4 mapping: App\\ => src/"]
    E --> F["App\\Billing\\Invoice -> src/Billing/Invoice.php"]
    F --> G{"Файл найден и объявил нужный FQCN?"}
    G -- "да" --> C
    G -- "нет" --> H["Следующий autoloader или ошибка Class not found"]
Как имя класса проходит путь от обращения в коде до `.php`-файла по правилу PSR-4.
Quick recall
В PHP-коде выполнено `new App\Billing\Invoice()`, но класс ещё не объявлен. Что делает autoloading в этот момент?

spl_autoload_register

Низкоуровневый API языка — spl_autoload_register(). Он регистрирует callback, который получает полное имя класса и может подключить нужный файл.

<?php

declare(strict_types=1);

spl_autoload_register(function (string $class): void {
    $prefix = 'App\\';
    $baseDir = __DIR__ . '/src/';

    if (!str_starts_with($class, $prefix)) {
        return;
    }

    $relativeClass = substr($class, strlen($prefix));
    $file = $baseDir . str_replace('\\', '/', $relativeClass) . '.php';

    if (is_file($file)) {
        require $file;
    }
});

$service = new App\Billing\InvoiceService();

Это рабочая иллюстрация, но в обычном проекте такой код писать не нужно. Composer уже делает это надёжнее: поддерживает несколько правил автозагрузки, пакеты из vendor/, autoload-dev, classmap, оптимизацию для продакшна и edge cases, которые легко забыть в самописном загрузчике.

Важно: autoloader — это «последний шанс» перед ошибкой «class not found». Он не должен выполнять бизнес-логику, ходить в базу, менять глобальное состояние или бросать исключения. По стандарту PSR-4 autoloader не должен выбрасывать исключения и поднимать ошибки: если он не нашёл файл, он просто уступает место следующему autoloader.

Quick recall
Что должен делать callback, зарегистрированный через `spl_autoload_register()`, если переданный класс не относится к его prefix?

PSR-4: имя класса как путь к файлу

PSR-4 — это стандарт PHP-FIG, который описывает, как сопоставлять fully qualified class name с файловым путём. Он не является частью синтаксиса PHP, но стал основным соглашением экосистемы: Composer, IDE, фреймворки и статические анализаторы ожидают именно такую структуру.

Минимальная идея такая:

FQCN:             App\Catalog\Product
Namespace prefix: App\
Base directory:  src/
File:            src/Catalog/Product.php

А файл выглядит так:

<?php

declare(strict_types=1);

namespace App\Catalog;

final class Product
{
    public function __construct(
        public readonly string $sku,
        public readonly string $name,
    ) {}
}

PSR-4 использует несколько правил. У класса должно быть полное имя с верхним namespace, часто его называют vendor namespace: App, Symfony, Psr, VendorName. Начальная часть имени — namespace prefix — сопоставляется с одной или несколькими base directory. Оставшиеся subnamespace превращаются в поддиректории. Последний сегмент имени класса становится именем файла с расширением .php.

Регистр важен. App\Catalog\Product должен лежать в src/Catalog/Product.php, а не в src/catalog/product.php. На macOS такая ошибка может пройти незаметно из-за особенностей файловой системы, а на Linux-сервере сломать деплой. Это одна из причин, почему CI, линтеры и автоматические проверки полезны даже для небольших PHP-проектов.

Quick recall
Дан PSR-4 mapping: `"App\\": "src/"`. В какой файл должен попадать класс `App\Catalog\Product`?

Composer autoload

В Composer правило PSR-4 задаётся в composer.json:

{
  "autoload": {
    "psr-4": {
      "App\\": "src/"
    }
  },
  "autoload-dev": {
    "psr-4": {
      "Tests\\": "tests/"
    }
  }
}

Двойной обратный слеш здесь нужен из-за JSON: строка "App\\" означает реальный PHP-префикс App\. После изменения секции autoload нужно пересобрать файлы автозагрузки:

composer dump-autoload

или, если Composer установлен локально как composer.phar:

php composer.phar dump-autoload

Composer генерирует vendor/autoload.php и вспомогательные файлы внутри vendor/composer/. Именно vendor/autoload.php подключают из entrypoint приложения, тестов, консольных скриптов или фронт-контроллера. Связь с Composer, Packagist и composer.json прямая: тот же файл описывает зависимости проекта и правила, по которым собственный код становится доступен без ручных require.

Для продакшна часто используют оптимизацию:

composer dump-autoload --optimize
# коротко:
composer dump-autoload -o

Оптимизированный autoloader превращает PSR-0/PSR-4-правила в classmap там, где это возможно, чтобы быстрее находить классы. Более жёсткий режим --classmap-authoritative говорит Composer искать только в classmap; он может быть полезен в стабильной продакшн-сборке, но неудобен во время разработки, когда файлы часто добавляются и переименовываются.

Что автозагружается, а что нет

PSR-4 относится к class-like сущностям: class, interface, trait, enum. Поэтому Enum в PHP автозагружаются так же, как классы:

namespace App\Order;

enum OrderStatus: string
{
    case Draft = 'draft';
    case Paid = 'paid';
}

Функции и константы сами по себе через PSR-4 не автозагружаются. Если проекту нужны глобальные helper-функции, Composer поддерживает режим files, но это другой механизм: указанные файлы подключаются при загрузке autoloader, а не по первому вызову функции.

{
  "autoload": {
    "psr-4": {
      "App\\": "src/"
    },
    "files": [
      "src/Support/helpers.php"
    ]
  }
}

Используйте files аккуратно. В OOP-коде чаще лучше вынести поведение в классы, сервисы или value objects, особенно если с этим кодом будут работать PHPDoc, generics и статический анализ.

Типичные поломки

Самая частая ошибка — namespace в файле не совпадает с путём. Например, файл лежит в src/Billing/Invoice.php, но внутри написано namespace App\Billings;. Composer может честно искать App\Billing\Invoice, открыть ожидаемый файл и всё равно не найти нужный класс, потому что в файле объявлено другое имя.

Вторая ошибка — забыли composer dump-autoload после изменения composer.json. Новые классы в PSR-4-директории обычно находятся без пересборки, но новые namespace mappings, autoload-dev, files и classmap требуют обновить автозагрузчик.

Третья — конфликт имён из-за неаккуратного use. Autoloading не решает смысловые конфликты: если в файле импортирован не тот User, PHP загрузит именно тот класс, который указан именем. Здесь помогают явные импорты, alias и стиль из PSR-1, PSR-12 и стиль кода.

Четвёртая — побочные эффекты в файлах классов. Файл, который загружается autoloader, должен в основном объявлять класс. Не стоит печатать HTML, читать $_POST, запускать миграции или создавать соединение с БД прямо на верхнем уровне файла. Иначе простой new App\Service\ReportBuilder() внезапно запускает невидимую работу.

Практическая модель в голове

Хороший PHP-проект держит три слоя отдельно. Неймспейсы и use задают имена и короткие ссылки в коде. PSR-4 задаёт соглашение «имя → путь». Composer генерирует реальный autoloader, который PHP вызывает при первом обращении к классу.

Если класс не находится, проверяйте в таком порядке: точное FQCN в коде, namespace внутри файла, путь и регистр директорий, правило autoload.psr-4 в composer.json, запуск composer dump-autoload, подключение vendor/autoload.php в entrypoint. Такой порядок быстрее, чем хаотично добавлять require_once и ломать архитектуру проекта.

См. также

Sources

  1. PHP Manual: Autoloading Classes
  2. PHP-FIG: PSR-4 Autoloader
  3. Composer Docs: Basic usage — Autoloading
  4. Composer Docs: CLI — dump-autoload / dumpautoload