Корректные имена файлов в PHP проектах
В PHP имена файлов напрямую влияют на возможность автозагрузки классов и общую организацию проекта. Соблюдение единых соглашений облегчает понимание кода и интеграцию с фреймворками. Рассмотрим основные правила и варианты их применения.
Основное правило: стандарт PSR-4
Современный стандарт PSR-4 предписывает, что имя файла должно в точности соответствовать имени класса с учетом регистра, а путь к файлу должен повторять пространство имен (namespace) относительно корневой директории, указанной в автозагрузке. Расширение файла - обязательно .php. Этот подход используется большинством современных фреймворков (Laravel, Symfony, Yii2) и является рекомендуемым.
Пример: Класс App\Models\User должен находиться в файле src/Models/User.php, если корневое пространство имен App привязано к директории src.
// Файл src/Models/User.php
namespace App\Models;
class User {
// ...
}
Настройка автозагрузки через composer.json:
{
"autoload": {
"psr-4": {
"App\\": "src/"
}
}
}
После выполнения composer dump-autoload все классы в указанной директории будут автоматически найдены по имени файла.
Как автоматически загружать классы без PSR-4?
До появления PSR-4 широко использовался стиль PEAR / Zend Framework, где имя класса содержит подчеркивания, а файл называется в точности как класс (с подчеркиваниями) и помещается в иерархию директорий, где подчеркивания заменяются на разделители путей.
Например, класс App_Models_User должен лежать в файле App/Models/User.php. Для автозагрузки используется специальный автозагрузчик:
spl_autoload_register(function ($class) {
$file = __DIR__ . '/' . str_replace('_', '/', $class) . '.php';
if (file_exists($file)) {
require $file;
}
});
Этот вариант всё ещё встречается в устаревших проектах, но не рекомендуется для новых разработок.
Как называть файлы с вспомогательными функциями?
Для файлов, не содержащих классов (например, наборы функций или конфигурационные файлы), стандарты менее строги. Часто используется kebab-case (слова через дефис) или snake_case. Например: app-helpers.php, config.php, database-connections.php. Главное - сохранять единообразие в проекте.
// Файл helpers/app-helpers.php
function formatDate(string $date): string {
// ...
}
Как правильно именовать файлы тестов?
Тестовые классы обычно наследуют TestCase и следуют тому же PSR-4. Имя файла соответствует имени класса теста. Распространённая практика - добавлять суффикс Test к имени тестируемого класса. Например, для класса User тест будет UserTest в файле UserTest.php.
// tests/UserTest.php
use PHPUnit\Framework\TestCase;
class UserTest extends TestCase {
// ...
}
Как отличать файлы интерфейсов и трейтов?
Для интерфейсов и трейтов нет отдельного соглашения по имени файла - оно должно совпадать с именем самого интерфейса/трейта. Однако для удобства часто добавляют суффиксы Interface и Trait в само имя типа. Файл при этом называется соответственно: LoggerInterface.php, LoggableTrait.php.
// Contracts/LoggerInterface.php
namespace App\Contracts;
interface LoggerInterface {
public function log(string $message): void;
}
// Traits/LoggableTrait.php
namespace App\Traits;
trait LoggableTrait {
public function log(string $message): void {
// ...
}
}
Типичные проблемы и ошибки
- Регистрозависимость. На файловых системах Linux (ext4) имена файлов чувствительны к регистру, а на Windows (NTFS) - нет. Код, работающий локально на Windows, может упасть на сервере Linux, если регистр в имени класса и файла не совпадает. Решение: всегда выравнивать регистр по PSR-4 и проверять в CI/CD под Linux.
- Несоответствие namespace и пути. Если пространство имен класса не соответствует пути в composer.json, автозагрузчик не найдёт файл. Решение: проверять раздел autoload и выполнять
composer dump-autoloadпосле изменений. - Дублирование классов. Два файла с именем
User.phpв разных неймспейсах допустимы, но если в одном файле объявлен не тот класс, автозагрузка сломается. Решение: придерживаться правила один класс - один файл. - Использование специальных символов. Буквы с диакритикой, пробелы, нелатинские символы в именах файлов могут вызвать проблемы на разных ОС. Решение: использовать только латинские буквы, цифры, подчеркивание и дефис.
Подробные примеры и сценарии
Пример 1: Структура проекта с PSR-4 и composer
Создадим проект с классом App\Controller\HomeController. Имя файла HomeController.php в папке src/Controller/.
// composer.json
{
"autoload": {
"psr-4": { "App\\": "src/" }
}
}
// src/Controller/HomeController.php
namespace App\Controller;
class HomeController {
public function index(): string {
return "Home";
}
}
Результат: после composer dump-autoload класс доступен для автозагрузки. Можно вызвать new \App\Controller\HomeController() без явного require.
Пример 2: Legacy автозагрузка с подчеркиваниями
Допустим, есть класс MyLib_DB_Connector. Файл должен называться MyLib_DB_Connector.php и помещаться в корень, где работает автозагрузчик, или в папку, где подчеркивания заменяются на разделители.
// MyLib_DB_Connector.php
class MyLib_DB_Connector {
public function connect() { /* ... */ }
}
// автозагрузчик
spl_autoload_register(function ($class) {
$file = __DIR__ . "/" . $class . ".php";
if (file_exists($file)) {
require $file;
}
});
// использование
$connector = new MyLib_DB_Connector();
Результат: автозагрузка срабатывает, если файл MyLib_DB_Connector.php существует. Проблема: при глубокой вложенности название класса становится длинным.
Пример 3: Автозагрузка PSR-4 вручную (без Composer)
Можно реализовать свой автозагрузчик, соответствующий PSR-4:
spl_autoload_register(function ($class) {
$prefixes = [
"App\\" => __DIR__ . "/src/",
"Lib\\" => __DIR__ . "/lib/",
];
foreach ($prefixes as $prefix => $baseDir) {
$len = strlen($prefix);
if (strncmp($prefix, $class, $len) === 0) {
$relativeClass = substr($class, $len);
$file = $baseDir . str_replace("\\", "/", $relativeClass) . ".php";
if (file_exists($file)) {
require $file;
}
}
}
});
Результат: для класса App\Service\Mailer будет открыт файл src/Service/Mailer.php. Этот код полезен для проектов без Composer.
Пример 4: Проблема регистра на разных ОС
Пусть на Windows класс объявлен как UserController в файле Usercontroller.php (разный регистр). Windows найдёт файл, но Linux - нет. Чтобы избежать, нужно строго соблюдать регистр:
// Неправильно:
class UserController {} // файл Usercontroller.php
// Правильно:
class UserController {} // файл UserController.php
Проверка: на Linux при загрузке класса UserController произойдет ошибка, если файл назван иначе. Решение: использовать инструменты анализа кода, например, PHPStan или проверку в CI.
Пример 5: Именование интерфейсов и трейтов с суффиксами
Создадим интерфейс CacheInterface и трейт CacheableTrait. Файлы называем соответственно.
// src/Cache/CacheInterface.php
namespace App\Cache;
interface CacheInterface {
public function get(string $key): mixed;
}
// src/Cache/CacheableTrait.php
namespace App\Cache;
trait CacheableTrait {
public function get(string $key): mixed {
// реализация
}
}
Результат: автозагрузка работает; по имени файла сразу понятно, что это интерфейс или трейт. Можно хранить их в одной папке Cache или в отдельных.