Организация кода: модульная структура и роутинг на PHP
Организация модулей и страниц в PHP приложениях
При разработке на PHP важно структурировать код так, чтобы его было удобно поддерживать, расширять и тестировать. Модульная архитектура позволяет разделить приложение на независимые части, каждая из которых отвечает за свою функциональность. В этой части рассмотрим различные подходы к организации модулей и обработке страниц, от простейших до профессиональных.
Как подключить модуль вручную с помощью require?
Самый простой вариант - разместить код каждого модуля в отдельном файле и подключать его через require или include. Такой подход часто используется в небольших проектах или для быстрого прототипирования.
Цель: быстро получить работающий код без дополнительных инструментов. Случаи использования: скрипты на несколько страниц, учебные проекты.
<?php
// modules/user.php
function getUserName($id) {
return 'User_' . $id;
}
?>
// index.php
require_once 'modules/user.php';
echo getUserName(5);
Modules php name pages (модули и страницы на php)
Проблема: при росте числа файлов легко запутаться в зависимостях, дублируются подключения, возможны конфликты имен функций. Ошибка: если файл не найден - фатальная ошибка. Решение: использовать require_once и тщательно контролировать пути.
Как организовать модули с использованием автозагрузки классов PSR-4?
Наиболее эффективное решение - использовать стандарт PSR-4 и менеджер пакетов Composer. Код организуется в пространствах имен (namespace), а автозагрузка настраивается автоматически. Каждый класс хранится в отдельном файле, путь совпадает с именем класса и неймспейсом.
Цель: создать масштабируемую, предсказуемую структуру, которая легко расширяется и соответствует лучшим практикам. Случаи использования: любые серьезные проекты, командная разработка.
// Структура папок:
project/
src/
Module/
User/
Repository.php
Controller.php
public/
index.php
composer.json
// composer.json
{
"autoload": {
"psr-4": {
"App\\": "src/"
}
}
}
// src/Module/User/Repository.php
<?php
namespace App\Module\User;
class Repository {
public function find($id) {
return ['id' => $id, 'name' => 'User_' . $id];
}
}
После выполнения composer dump-autoload классы загружаются автоматически. В index.php достаточно вызвать:
<?php
require_once 'vendor/autoload.php';
use App\Module\User\Repository;
$repo = new Repository();
$user = $repo->find(42);
print_r($user);
Array
(
[id] => 42
[name] => User_42
)
Типичные ошибки: неймспейс не соответствует пути (регистр символов), забыли выполнить composer dump-autoload после добавления новых классов, конфликт классов из разных пакетов. Решение: проверять регистр, перегенерировать автозагрузку, использовать уникальные префиксы неймспейсов.
Как построить маршрутизацию страниц (роутинг) в модульной архитектуре?
Роутинг - это механизм, который связывает URL с определенным кодом (контроллером) внутри модуля. Можно реализовать его вручную или использовать готовые компоненты.
Вариант 1: простой роутер на основе switch.
<?php
// public/index.php
require_once 'vendor/autoload.php';
$uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
switch ($uri) {
case '/':
echo 'Главная страница';
break;
case '/users':
$controller = new App\Module\User\Controller();
echo $controller->list();
break;
default:
http_response_code(404);
echo 'Страница не найдена';
}
Проблема: код быстро разрастается, сложно добавлять новые маршруты, нет гибкости (параметры, middleware).
Вариант 2: использовать микро-фреймворк, например Slim или FastRoute. Они предоставляют удобный API и автоматическое разрешение зависимостей.
// composer require slim/slim:^4
// public/index.php
<?php
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Slim\Factory\AppFactory;
require_once 'vendor/autoload.php';
$app = AppFactory::create();
$app->get('/', function (Request $request, Response $response) {
$response->getBody()->write('Главная страница');
return $response;
});
$app->get('/users/{id}', function (Request $request, Response $response, array $args) {
$id = $args['id'];
$repo = new App\Module\User\Repository();
$user = $repo->find($id);
$response->getBody()->write(json_encode($user));
return $response->withHeader('Content-Type', 'application/json');
});
$app->run();
Как использовать фреймворк Laravel для модульной архитектуры?
Laravel предлагает встроенный сервис-провайдеры, роутинг, Eloquent ORM. Модули могут быть организованы через пакеты (packages) или кастомные модульные структуры (например, с помощью пакета nWidart/laravel-modules).
Цель: использовать мощный фреймворк с готовой инфраструктурой для крупных проектов. Случаи использования: enterprise-приложения, SaaS-решения.
# Установка модульного пакета
composer require nwidart/laravel-modules
php artisan module:make User
# Создается папка Modules/User с подпапками Controllers, Models, Routes
# routes/web.php внутри модуля:
Route::get('/users', 'UserController@index');
Route::get('/users/{id}', 'UserController@show');
Ошибки: несоответствие версий пакетов, конфликты зависимостей, неправильная конфигурация автозагрузки модулей. Решение: следовать документации, выполнять php artisan optimize после изменений.
Расширенные примеры модульной архитектуры с пошаговыми пояснениями
Ниже приведены детальные примеры реализации модулей и страниц с использованием PSR-4 автозагрузки, маршрутизации и обработки запросов.
Пример 1: Полная структура мини-приложения с автозагрузкой и роутингом
Создадим проект, который выводит информацию о пользователях через REST-подобные URL.
# Структура:
myapp/
composer.json
public/
index.php
.htaccess
src/
Core/
Router.php
Module/
User/
Controller.php
Repository.php
vendor/
# .htaccess
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ index.php [QSA,L]
# composer.json
{
"name": "myapp/demo",
"autoload": {
"psr-4": {
"MyApp\\": "src/"
}
},
"require": {}
}
# src/Core/Router.php
<?php
namespace MyApp\Core;
class Router {
private array $routes = [];
public function add(string $method, string $pattern, callable $handler): void {
$this->routes[] = ['method' => $method, 'pattern' => $pattern, 'handler' => $handler];
}
public function dispatch(string $method, string $uri): void {
$uri = parse_url($uri, PHP_URL_PATH);
foreach ($this->routes as $route) {
if ($route['method'] !== $method) continue;
$pattern = preg_replace('/\{([^}]+)\}/', '(?P<$1>[^/]+)', $route['pattern']);
$pattern = '#^' . $pattern . '$#';
if (preg_match($pattern, $uri, $matches)) {
$params = array_filter($matches, 'is_string', ARRAY_FILTER_USE_KEY);
call_user_func($route['handler'], $params);
return;
}
}
http_response_code(404);
echo '404 Not Found';
}
}
# src/Module/User/Repository.php
<?php
namespace MyApp\Module\User;
class Repository {
private array $data = [
1 => ['name' => 'Alice', 'email' => 'alice@example.com'],
2 => ['name' => 'Bob', 'email' => 'bob@example.com']
];
public function find(int $id): ?array {
return $this->data[$id] ?? null;
}
public function all(): array {
return $this->data;
}
}
# src/Module/User/Controller.php
<?php
namespace MyApp\Module\User;
class Controller {
private Repository $repo;
public function __construct() {
$this->repo = new Repository();
}
public function list(): string {
$users = $this->repo->all();
$output = '<h1>Users</h1><ul>';
foreach ($users as $id => $user) {
$output .= "<li><a href='user/{$id}'>{$user['name']}</a></li>";
}
return $output . '</ul>';
}
public function show(int $id): string {
$user = $this->repo->find($id);
if (!$user) {
return '<h1>User not found</h1>';
}
return "<h1>User: {$user['name']}</h1><p>Email: {$user['email']}</p>";
}
}
# public/index.php
<?php
require_once '../vendor/autoload.php';
use MyApp\Core\Router;
use MyApp\Module\User\Controller;
$router = new Router();
$controller = new Controller();
$router->add('GET', '/', function() use ($controller) {
echo $controller->list();
});
$router->add('GET', '/user/{id}', function(array $params) use ($controller) {
$id = (int)$params['id'];
echo $controller->show($id);
});
$router->dispatch($_SERVER['REQUEST_METHOD'], $_SERVER['REQUEST_URI']);
# Запуск: php -S localhost:8000 -t public/
Результат запроса GET /:
<h1>Users</h1><ul><li><a href='user/1'>Alice</a></li><li><a href='user/2'>Bob</a></li></ul>
Результат запроса GET /user/1:
<h1>User: Alice</h1><p>Email: alice@example.com</p>
Пример 2: Использование Slim Framework для модульной организации
Покажем, как разбить приложение на модули с помощью контейнера зависимостей и групп маршрутов.
# composer.json (только необходимые зависимости)
{
"require": {
"slim/slim": "^4.0",
"php-di/php-di": "^7.0"
},
"autoload": {
"psr-4": {
"App\\": "src/"
}
}
}
# src/Module/User/UserController.php
<?php
namespace App\Module\User;
use Psr\Container\ContainerInterface;
use Slim\Psr7\Response;
use Slim\Psr7\Request;
class UserController {
private ContainerInterface $container;
public function __construct(ContainerInterface $container) {
$this->container = $container;
}
public function list(Request $request, Response $response): Response {
$repo = $this->container->get(UserRepository::class);
$users = $repo->all();
$payload = json_encode($users);
$response->getBody()->write($payload);
return $response->withHeader('Content-Type', 'application/json');
}
public function show(Request $request, Response $response, array $args): Response {
$id = (int)$args['id'];
$repo = $this->container->get(UserRepository::class);
$user = $repo->find($id);
if (!$user) {
$response->getBody()->write(json_encode(['error' => 'Not found']));
return $response->withStatus(404)->withHeader('Content-Type', 'application/json');
}
$payload = json_encode($user);
$response->getBody()->write($payload);
return $response->withHeader('Content-Type', 'application/json');
}
}
# src/Module/User/UserRepository.php
<?php
namespace App\Module\User;
class UserRepository {
public function all(): array {
return [
['id' => 1, 'name' => 'Alice'],
['id' => 2, 'name' => 'Bob']
];
}
public function find(int $id): ?array {
foreach ($this->all() as $user) {
if ($user['id'] === $id) return $user;
}
return null;
}
}
# public/index.php
<?php
use DI\ContainerBuilder;
use Slim\Factory\AppFactory;
require_once '../vendor/autoload.php';
$containerBuilder = new ContainerBuilder();
$container = $containerBuilder->build();
AppFactory::setContainer($container);
$app = AppFactory::create();
// Регистрация репозитория
$container->set(App\Module\User\UserRepository::class, function() {
return new App\Module\User\UserRepository();
});
// Определение маршрутов модуля User
$app->group('/users', function ($group) {
$group->get('', [App\Module\User\UserController::class, 'list']);
$group->get('/{id:[0-9]+}', [App\Module\User\UserController::class, 'show']);
});
$app->run();
# Запуск: php -S localhost:8000 -t public/
Результат запроса GET /users:
[{"id":1,"name":"Alice"},{"id":2,"name":"Bob"}]
Результат запроса GET /users/1:
{"id":1,"name":"Alice"}
Пример 3: Модульная структура с использованием Composer-пакетов
Можно вынести каждый модуль в отдельный Composer-пакет и подключать его через репозиторий. Это удобно для крупных проектов с командной разработкой.
# В корне проекта composer.json:
{
"repositories": [
{
"type": "path",
"url": "modules/*"
}
],
"require": {
"myapp/user-module": "*"
},
"autoload": {
"psr-4": {
"App\\": "src/"
}
}
}
# modules/user-module/composer.json
{
"name": "myapp/user-module",
"autoload": {
"psr-4": {
"MyApp\\UserModule\\": "src/"
}
},
"require": {}
}
# modules/user-module/src/UserService.php
<?php
namespace MyApp\UserModule;
class UserService {
public function greet(string $name): string {
return "Hello, $name!";
}
}
# public/index.php (после composer update)
<?php
require_once 'vendor/autoload.php';
$service = new MyApp\UserModule\UserService();
echo $service->greet('World');
Hello, World!
Такой подход позволяет разрабатывать и версионировать модули независимо.