Архитектура приложений: компоненты index.php и action

Раздел: Веб-разработка на PHP -> Архитектура приложений

Веб-приложения на PHP часто строятся по принципу единой точки входа (front controller), где все запросы направляются на один файл index.php. Этот файл выступает диспетчером: анализирует запрос, определяет, какой компонент (контроллер) и какое действие (action) должны быть выполнены, и запускает соответствующую логику. Такой подход упрощает маршрутизацию, безопасность и поддержку кода. Ниже рассматривается несколько способов организации компонентов внутри index.php.

Основное решение - компонентный front controller

Как организовать единую точку входа с поддержкой компонентов и действий?

Наиболее эффективное решение предполагает использование централизованного роутера, который хранит соответствия между запросами и классами/методами. Это позволяет гибко настраивать маршруты, легко добавлять новые компоненты и переиспользовать код.

Структура приложения:

  • index.php - точка входа;
  • src/Controllers/ - папка с компонентами (контроллерами);
  • config/routes.php - файл с определениями маршрутов.

Пример файла config/routes.php:

<?php
// config/routes.php
return [
    'user' => [
        'class' => App\Controllers\UserController::class,
        'actions' => [
            'list' => 'listAction',
            'view' => 'viewAction',
        ],
    ],
    'article' => [
        'class' => App\Controllers\ArticleController::class,
        'actions' => [
            'list' => 'listAction',
            'create' => 'createAction',
        ],
    ],
];

Com index php action (компоненты index.php в php)

Файл index.php:

<?php
// index.php
require_once __DIR__ . '/vendor/autoload.php';

$routes = require __DIR__ . '/config/routes.php';

$component = $_GET['com'] ?? 'user';
$action = $_GET['action'] ?? 'list';

if (!isset($routes[$component])) {
    http_response_code(404);
    echo "Компонент не найден";
    exit;
}

$componentConfig = $routes[$component];
$controllerClass = $componentConfig['class'];

if (!isset($componentConfig['actions'][$action])) {
    http_response_code(404);
    echo "Действие не найдено";
    exit;
}

$method = $componentConfig['actions'][$action];

$controller = new $controllerClass();
if (!method_exists($controller, $method)) {
    throw new \RuntimeException("Метод $method не существует в $controllerClass");
}

$result = $controller->$method();
echo $result;

Application web php (веб-приложение на php)

Пояснения:

  • Автозагрузка классов осуществляется через Composer (PSR-4).
  • Маршруты вынесены в отдельный файл для удобства конфигурирования.
  • Параметры com и action передаются через URL (например, index.php?com=article&action=create).
  • Проверки существования компонента и действия позволяют избежать ошибок.

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

  • Ошибка 404 при обращении к несуществующему компоненту - необходимо добавить обработку исключений или логирование.
  • Не обработаны параметры запроса - стоит использовать фильтрацию (например, filter_input).
  • Медленная работа при большом количестве маршрутов - можно заменить массив на кэшированный роутер.

Как реализовать простую маршрутизацию с помощью switch?

Switch-case подходит для небольших приложений с фиксированным набором компонентов. Внутри index.php определяется переменная $component, и для каждого значения выполняется соответствующий код.

<?php
$component = $_GET['com'] ?? 'home';

switch ($component) {
    case 'user':
        require 'controllers/user.php';
        break;
    case 'article':
        require 'controllers/article.php';
        break;
    default:
        http_response_code(404);
        echo "Страница не найдена";
}

Проблемы: при возрастании числа компонентов файл index.php раздувается, дублирование кода, сложно тестировать.

  • При добавлении нового компонента нужно редактировать switch, что нарушает принцип открытости/закрытости.
  • Отсутствует унифицированная обработка ошибок.

Как использовать массив для регистрации маршрутов?

Массив callable позволяет регистрировать замыкания или имена классов. Пример:

<?php
$routes = [
    'user' => ['controller' => 'App\Controllers\UserController', 'method' => 'listAction'],
    'article' => ['controller' => 'App\Controllers\ArticleController', 'method' => 'listAction'],
];

$com = $_GET['com'] ?? 'user';
if (isset($routes[$com])) {
    $controllerClass = $routes[$com]['controller'];
    $method = $routes[$com]['method'];
    $controller = new $controllerClass();
    echo $controller->$method();
} else {
    http_response_code(404);
}

Этот вариант проще switch, но всё ещё требует ручного добавления каждой записи.

Ошибка: если метод не существует, приложение упадёт. Необходима проверка method_exists.

Как применять PSR-7 и middleware?

Для сложных приложений используется PSR-7 (HTTP messages) и цепочка middleware. index.php передаёт запрос через стек middleware, один из которых отвечает за роутинг. Пример с минимальным роутером на основе библиотеки relay/relay:

<?php
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Laminas\Diactoros\Response;

$router = function (ServerRequestInterface $request, RequestHandlerInterface $handler) {
    $path = $request->getUri()->getPath();
    if ($path === '/user') {
        return new Response\HtmlResponse('Список пользователей');
    }
    return $handler->handle($request);
};

$middlewareQueue = [
    $router,
    function (ServerRequestInterface $request) {
        return new Response\HtmlResponse('404 Not Found', 404);
    },
];

$relay = new \Relay\Relay($middlewareQueue);
$response = $relay->handle(\Laminas\Diactoros\ServerRequestFactory::fromGlobals());

Сложность настройки и зависимость от внешних библиотек. Не подходит для простых проектов.

Как использовать автозагрузку классов по PSR-4?

При правильной структуре классов можно опустить явное включение файлов. index.php только загружает Composer autoload и вызывает метод диспетчера.

<?php
require_once __DIR__ . '/vendor/autoload.php';

use App\Core\Dispatcher;

$dispatcher = new Dispatcher();
echo $dispatcher->run($_GET);

Класс Dispatcher содержит всю логику маршрутизации, что улучшает тестирование и разделение ответственности.

Ошибка: неправильное пространство имён в autoload.json. Решение - проверка composer.json и выполнение composer dump-autoload.

Как определить действия прямо в index.php с помощью замыканий?

Для микроприложений можно хранить действия в виде анонимных функций в массиве.

<?php
$handlers = [
    'user.list' => function () {
        return 'Список пользователей';
    },
    'article.create' => function () {
        return 'Создание статьи';
    },
];

$key = ($_GET['com'] ?? 'user') . '.' . ($_GET['action'] ?? 'list');

if (isset($handlers[$key])) {
    echo $handlers[$key]();
} else {
    http_response_code(404);
}

При росте приложения замыкания в index.php становятся нечитаемыми. Невозможно переиспользовать код между разными файлами. Подходит только для прототипов.

Рассмотрим расширенный пример реализации front controller с поддержкой параметров, валидацией и обработкой исключений. Структура проекта:

Пример
project/
├── index.php
├── config/
│   └── routes.php
├── src/
│   ├── Controllers/
│   │   ├── UserController.php
│   │   └── ArticleController.php
│   └── Core/
│       └── Router.php
└── vendor/

config/routes.php

Пример
<?php
return [
    'user' => [
        'class' => App\Controllers\UserController::class,
        'actions' => [
            'list'   => 'listAction',
            'view'   => 'viewAction',
            'create' => 'createAction',
        ],
    ],
    'article' => [
        'class' => App\Controllers\ArticleController::class,
        'actions' => [
            'list'   => 'listAction',
            'view'   => 'viewAction',
        ],
    ],
];

src/Core/Router.php

Пример
<?php
namespace App\Core;

use Psr\Log\LoggerInterface;

class Router
{
    private array $routes;
    private LoggerInterface $logger;

    public function __construct(array $routes, LoggerInterface $logger = null)
    {
        $this->routes = $routes;
        $this->logger = $logger ?? new class implements LoggerInterface {
            public function log($level, $message, array $context = []) {}
        };
    }

    public function dispatch(string $component, string $action): string
    {
        if (!isset($this->routes[$component])) {
            $this->logger->warning('Component not found', ['component' => $component]);
            http_response_code(404);
            return 'Component not found';
        }

        $config = $this->routes[$component];
        $class = $config['class'];

        if (!isset($config['actions'][$action])) {
            $this->logger->warning('Action not found', ['component' => $component, 'action' => $action]);
            http_response_code(404);
            return 'Action not found';
        }

        $method = $config['actions'][$action];

        if (!class_exists($class)) {
            throw new \RuntimeException("Class $class does not exist");
        }

        $controller = new $class();

        if (!method_exists($controller, $method)) {
            throw new \RuntimeException("Method $method not found in $class");
        }

        return $controller->$method();
    }
}

index.php

Пример
<?php
require_once __DIR__ . '/vendor/autoload.php';

use App\Core\Router;

$routes = require __DIR__ . '/config/routes.php';
$router = new Router($routes);

$component = filter_input(INPUT_GET, 'com', FILTER_SANITIZE_STRING) ?? 'user';
$action = filter_input(INPUT_GET, 'action', FILTER_SANITIZE_STRING) ?? 'list';

try {
    $content = $router->dispatch($component, $action);
    echo $content;
} catch (\Throwable $e) {
    http_response_code(500);
    echo 'Internal Server Error';
    // В production логировать ошибку, а не выводить
    error_log($e->getMessage());
}

src/Controllers/UserController.php

Пример
<?php
namespace App\Controllers;

class UserController
{
    public function listAction(): string
    {
        return '<h1>User list</h1>';
    }

    public function viewAction(): string
    {
        $id = $_GET['id'] ?? 0;
        $id = (int)$id;
        return "<h1>User $id</h1>";
    }

    public function createAction(): string
    {
        return '<h1>Create user form</h1>';
    }
}

Примеры вызовов и результатов:

Пример
Запрос: index.php?com=user&action=list
Результат: <h1>User list</h1>

Запрос: index.php?com=user&action=view&id=5
Результат: <h1>User 5</h1>

Запрос: index.php?com=article&action=view
Результат: <h1>Article view</h1> (если реализовано)

Запрос: index.php?com=unknown&action=list
Результат: 404 Component not found
Пример вывода для корректного запроса:
<h1>User list</h1>

Этот пример демонстрирует чёткое разделение ответственности, использование исключений и фильтрацию входных данных. Расширение функционала сводится к добавлению нового контроллера и обновлению конфигурации.

Компоненты index.php в PHP - comments

En
Com index php action (php)