Организация кода: модульная структура и роутинг на PHP

Раздел: Разработка на 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!
  

Такой подход позволяет разрабатывать и версионировать модули независимо.

Модули и страницы на PHP - comments

En
Modules php name pages (php)