Архитектура ядра PHP-приложения с index.php

Раздел: PHP -> Архитектура

Ядро PHP-приложения: index.php и точка входа

Ядро приложения на PHP (index.php app core) это центральный точка входа, которая обрабатывает все HTTP-запросы. Оно загружает зависимости, выполняет маршрутизацию, инициализирует контроллеры и возвращает ответ. Без правильно организованного ядра приложение становится трудно поддерживаемым и небезопасным.

Как создать эффективное ядро с автоматической загрузкой и маршрутизацией?

Наиболее эффективным решением является использование фронт-контроллера на основе Composer и простого роутера. Файл index.php (или public/index.php) отвечает за:

  • Подключение автозагрузчика Composer (vendor/autoload.php)
  • Загрузка переменных окружения (например, с помощью vlucas/phpdotenv)
  • Создание контейнера зависимостей для сервисов
  • Выполнение роутинга и диспетчеризация запроса к нужному контроллеру
  • Обработка исключений и возврат ответа
<?
// public/index.php
require_once __DIR__ . '/../vendor/autoload.php';

use Dotenv\Dotenv;
use App\Core\Router;
use App\Core\Container;
use App\Core\Request;
use App\Core\Response;

// Загрузка переменных окружения
$dotenv = Dotenv::createImmutable(__DIR__ . '/..');
$dotenv->load();

// Создание контейнера
$container = new Container();
$container->set(Request::class, new Request());

// Создание роутера
$router = new Router($container);

// Определение маршрутов
$router->add('GET', '/', 'HomeController@index');
$router->add('POST', '/submit', 'SubmitController@store');

// Обработка запроса
try {
    $response = $router->dispatch($container->get(Request::class));
    $response->send();
} catch (\Exception $e) {
    http_response_code(500);
    echo 'Ошибка: ' . htmlspecialchars($e->getMessage());
}

Index php app core (ядро приложения на php)

Пояснение:

  1. Автозагрузчик Composer загружает все классы, установленные через composer install.
  2. Класс Router хранит маршруты и выбирает подходящий контроллер.
  3. Контейнер управляет зависимостями, чтобы контроллеры получали готовые сервисы (например, базу данных, логирование).
  4. Блок try-catch перехватывает любые исключения и возвращает ошибку 500.

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

  • Пути до vendor/autoload.php указаны неверно. Решение: использовать __DIR__ и проверять существование файла.
  • Классы роутера или контроллера не найдены. Решение: проверить composer.json и выполнить composer dump-autoload.
  • Забыли обработать маршруты с параметрами. Решение: добавить поддержку регулярных выражений в роутер.

Как реализовать минимальный index.php без ООП (для обучения)?

Простой процедурный подход: все маршруты проверяются через $_SERVER['REQUEST_URI'] и $_SERVER['REQUEST_METHOD']. Этот вариант подходит для крошечных проектов или демонстрации.

<?
// index.php (процедурный)
require 'functions.php';

$uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
$method = $_SERVER['REQUEST_METHOD'];

if ($uri === '/' && $method === 'GET') {
    echo 'Главная страница';
} elseif ($uri === '/about' && $method === 'GET') {
    include 'views/about.php';
} else {
    http_response_code(404);
    echo 'Страница не найдена';
}

Цель: быстрый старт без зависимостей. Использование: только для одностраничников или прототипов.

Проблемы: отсутствие автозагрузки, дублирование кода, сложность поддержки при росте. Решение: перейти на ООП с роутером.

Как использовать микрофреймворк, чтобы не писать ядро самому?

Микрофреймворки (Slim, Flight, Lumen) предоставляют готовое ядро с роутингом, middleware и DI-контейнером. Пример на Slim 4:

<?
// public/index.php с Slim
use Slim\Factory\AppFactory;

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

$app = AppFactory::create();

$app->get('/', function ($request, $response) {
    $response->getBody()->write('Hello, world!');
    return $response;
});

$app->run();

Цель: экономия времени на разработку ядра, если приложение средней сложности. Использование: API, простые веб-приложения.

Проблема: микрофреймворк может не подойти для очень специфических архитектурных требований. Решение: кастомизировать его через middleware или перейти на Symfony/Laravel.

Как построить ядро с использованием PSR-7 и middleware?

Для совместимости и гибкости применяют стандарт PSR-7 для сообщений HTTP и промежуточные обработчики (middleware). Ядро становится цепочкой обработчиков.

<?
// public/index.php с PSR-7 (Zend Diactoros, Relay)
use Laminas\Diactoros\ServerRequestFactory;
use Laminas\HttpHandlerRunner\Emitter\SapiEmitter;
use Relay\Relay;

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

$request = ServerRequestFactory::fromGlobals();

$middlewareQueue = [
    new App\Middleware\ErrorHandler(),
    new App\Middleware\RouterMiddleware(),
    function ($request, $handler) {
        return $handler->handle($request);
    }
];

$relay = new Relay($middlewareQueue);
$response = $relay->handle($request);

(new SapiEmitter())->emit($response);

Цель: высокая модульность, возможность подключать/отключать обработчики (логирование, авторизация, кэширование). Использование: крупные приложения с гибкой архитектурой.

Ошибка: неправильная очередь middleware (например, роутер выполняется до проверки CORS). Решение: тщательно продумывать порядок.

Расширенные примеры ядра PHP-приложения

Маршрутизация с поддержкой параметров и регулярных выражений

Пример
<?
// App\Core\Router.php
class Router
{
    private array $routes = [];

    public function add(string $method, string $pattern, callable|string $handler): void
    {
        // Преобразуем {param} в регулярное выражение
        $pattern = preg_replace('/\{([a-zA-Z_]+)\}/', '(?P<$1>[^/]+)', $pattern);
        $this->routes[] = [
            'method' => $method,
            'pattern' => '#^' . $pattern . '$#',
            'handler' => $handler
        ];
    }

    public function dispatch(string $method, string $uri): mixed
    {
        foreach ($this->routes as $route) {
            if ($route['method'] !== $method) continue;
            if (preg_match($route['pattern'], $uri, $matches)) {
                $params = array_filter($matches, 'is_string', ARRAY_FILTER_USE_KEY);
                if (is_callable($route['handler'])) {
                    return call_user_func($route['handler'], $params);
                }
                // Если handler строка вида 'Controller@method'
                [$controller, $method] = explode('@', $route['handler']);
                $instance = new $controller();
                return $instance->$method($params);
            }
        }
        http_response_code(404);
        return '404 Not Found';
    }
}
Пример
<?
// index.php с использованием
$router->add('GET', '/user/{id}', function($params) {
    return 'User ID: ' . $params['id'];
});
$router->add('GET', '/post/{slug}', 'PostController@show');

$result = $router->dispatch($_SERVER['REQUEST_METHOD'], parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH));
echo $result;
Пример вызова /user/42: User ID: 42

Проблема: если в маршруте используются символы, не соответствующие шаблону (например, точка в slug), регулярное выражение их не захватит. Решение: расширить шаблон на [a-zA-Z0-9_\-]+ или применить более гибкую библиотеку FastRoute.

Обработка исключений и кастомные страницы ошибок

Пример
<?
// public/index.php с обработчиком ошибок
set_error_handler(function($severity, $message, $file, $line) {
    throw new ErrorException($message, 0, $severity, $file, $line);
});

set_exception_handler(function($exception) {
    $code = $exception->getCode() ?: 500;
    http_response_code($code);
    if (in_array($code, [404, 403, 500])) {
        include __DIR__ . "/../views/errors/{$code}.php";
    } else {
        echo '

Ошибка

' . htmlspecialchars($exception->getMessage()) . '

'; } }); // Дальше обычный код app core
Пример
<?
// views/errors/500.php
?>

500 - Внутренняя ошибка

Внутренняя ошибка сервера

Пожалуйста, попробуйте позже.

Пояснение: перехватываются все PHP-ошибки и исключения, возвращается дружелюбная страница.

Реализация простого DI-контейнера для ядра
Пример
<?
// App\Core\Container.php
class Container
{
    private array $services = [];
    private array $definitions = [];

    public function set(string $id, object|callable $definition): void
    {
        if (is_callable($definition)) {
            $this->definitions[$id] = $definition;
        } else {
            $this->services[$id] = $definition;
        }
    }

    public function get(string $id): object
    {
        if (isset($this->services[$id])) {
            return $this->services[$id];
        }
        if (isset($this->definitions[$id])) {
            $instance = call_user_func($this->definitions[$id], $this);
            $this->services[$id] = $instance;
            return $instance;
        }
        throw new \InvalidArgumentException("Service $id not found.");
    }
}

// Использование в роутере
$container->set(DB::class, function($c) {
    return new DB($_ENV['DB_HOST']);
});
$router->add('GET', '/users', function() use ($container) {
    $db = $container->get(DB::class);
    $users = $db->query('SELECT * FROM users');
    return json_encode($users);
});
Результат: JSON с данными пользователей.
Middleware для логирования времени выполнения
Пример
<?
// App\Middleware\PerformanceMiddleware.php
namespace App\Middleware;

use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Psr\Http\Message\ResponseInterface;

class PerformanceMiddleware implements MiddlewareInterface
{
    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
    {
        $start = microtime(true);
        $response = $handler->handle($request);
        $duration = microtime(true) - $start;
        $response = $response->withHeader('X-Execution-Time', round($duration, 4) . ' sec');
        return $response;
    }
}
Пример
<?
// index.php (Slim)
$app->add(new App\Middleware\PerformanceMiddleware());
$app->run();
Пример заголовка ответа: X-Execution-Time: 0.0123 sec

Ядро приложения на PHP - comments

En
Index php app core (php)