Создание PHP приложения: от структуры до развертывания

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

Организация кода PHP приложения

Как создать структуру PHP приложения, которая поддерживает масштабирование и упрощает поддержку?

Наиболее эффективным решением является разделение кода по принципу модульности с использованием автозагрузки PSR-4 и простого роутера. Это позволяет избежать «спагетти-кода» и облегчает тестирование.


// Компоновка папок:
/var/www/html/
├── public/
│   └── index.php      # точка входа
├── src/
│   ├── Controllers/
│   ├── Models/
│   └── Routes.php     # определение маршрутов
├── vendor/            # зависимости Composer
├── composer.json
└── .env               # конфигурация
  

Code php app (код php приложения)

Файл public/index.php - единственный скрипт, доступный извне. Он загружает автозагрузчик Composer и запускает роутер:


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

use App\Core\Router;

$router = new Router();
$router->addRoute('/', 'HomeController@index');
$router->dispatch($_SERVER['REQUEST_URI']);
  

Такой подход гарантирует, что все классы подгружаются только при необходимости, а бизнес-логика остаётся изолированной.

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

  • Забывают выполнить composer dump-autoload после добавления новых классов - возникает ошибка «Class not found».
  • Неправильное пространство имён (namespace) в файлах - автозагрузчик не находит путь.
  • Размещение index.php не в корневой директории веб-сервера - все ссылки ломаются.

Решение: проверять соответствие namespace и пути к файлу (регистр символов важен). Для быстрой диагностики добавить в начало index.php:


error_reporting(E_ALL);
ini_set('display_errors', 1);
    

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

Для небольших скриптов или одноразовых задач можно обойтись без классов. Весь код помещается в один файл или несколько инклудов. Однако при разрастании такой код становится трудно поддерживать.


<?php
$host = 'localhost';
$db   = 'test';
$user = 'root';
$pass = '';

$pdo = new PDO("mysql:host=$host;dbname=$db", $user, $pass);
$stmt = $pdo->query('SELECT * FROM users');
foreach ($stmt as $row) {
    echo $row['name'] . '<br>';
}
  

Этот вариант подходит для быстрых прототипов, но не для коммерческих проектов.

Проблемы: отсутствие разделения логики, сложность добавления новых страниц, опасность SQL-инъекций (если не использовать подготовленные запросы).

Как использовать микрофреймворк Slim для создания REST API?

Микрофреймворки дают готовый роутер, middleware и контейнер зависимостей, сохраняя минимальный размер кода. Пример простого маршрута:


<?php
use Slim\Factory\AppFactory;

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

$app = AppFactory::create();

$app->get('/hello/{name}', function ($request, $response, $args) {
    $response->getBody()->write("Hello, " . $args['name']);
    return $response;
});

$app->run();
  

Такое решение подходит для API-сервисов, где не нужна полная ORM и шаблонизация.

Частая ошибка: забывают вернуть объект $response. Slim ожидает, что хендлер вернёт PSR-7 Response, иначе выпадает исключение.

Как реализовать полноценное MVC приложение на Laravel?

Laravel предоставляет готовую архитектуру, artisan-команды и множество инструментов. Для создания контроллера достаточно выполнить:


php artisan make:controller UserController
  

Пример контроллера:


<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class UserController extends Controller
{
    public function index()
    {
        $users = \App\Models\User::all();
        return view('users.index', compact('users'));
    }
}
  

Такое решение оправдано в крупных проектах с множеством сущностей и сложными бизнес-правилами.

Типичные ошибки: неверный composer.json (несоответствие версий PHP), отсутствие расширений (mbstring, pdo_mysql), забыли сгенерировать ключ приложения (php artisan key:generate).

Расширенные примеры работы с PHP приложением

Пример 1: Реализация маршрутизации с поддержкой параметров и Middleware

Пример

<?php
// src/Core/Router.php
namespace App\Core;

class Router
{
    private array $routes = [];
    private array $middleware = [];

    public function addRoute(string $pattern, callable|array $handler, array $middleware = []): void
    {
        $this->routes[$pattern] = ['handler' => $handler, 'middleware' => $middleware];
    }

    public function dispatch(string $uri): void
    {
        foreach ($this->routes as $pattern => $route) {
            $regex = preg_replace('/\{[a-zA-Z_]+\}/', '([a-zA-Z0-9_]+)', $pattern);
            if (preg_match('#^' . $regex . '$#', $uri, $matches)) {
                array_shift($matches);
                // Запуск middleware
                foreach ($route['middleware'] as $mw) {
                    if ((new $mw)->handle() === false) {
                        http_response_code(403);
                        echo 'Forbidden';
                        return;
                    }
                }
                // Вызов хендлера
                if (is_callable($route['handler'])) {
                    echo call_user_func_array($route['handler'], $matches);
                } else {
                    [$class, $method] = explode('@', $route['handler']);
                    $controller = new $class();
                    echo call_user_func_array([$controller, $method], $matches);
                }
                return;
            }
        }
        http_response_code(404);
        echo 'Not Found';
    }
}
  

Пример использования:

Пример

$router = new \App\Core\Router();
$router->addRoute('/user/{id}', 'App\Controllers\UserController@show', ['App\Middleware\AuthMiddleware']);
$router->dispatch($_SERVER['REQUEST_URI']);
  

Проблема: регулярное выражение может конфликтовать с символами в URI. Лучше использовать готовые библиотеки (FastRoute, nikic/fast-route).

Результат при запросе /user/42 (если пользователь авторизован): данные пользователя ID=42.
  

Пример 2: Работа с базой данных через PDO с подготовленными запросами

Пример

<?php
class UserModel
{
    private PDO $pdo;

    public function __construct(PDO $pdo)
    {
        $this->pdo = $pdo;
    }

    public function findByEmail(string $email): ?array
    {
        $stmt = $this->pdo->prepare('SELECT * FROM users WHERE email = :email LIMIT 1');
        $stmt->execute([':email' => $email]);
        $user = $stmt->fetch(PDO::FETCH_ASSOC);
        return $user ?: null;
    }

    public function create(array $data): int
    {
        $sql = 'INSERT INTO users (name, email) VALUES (:name, :email)';
        $stmt = $this->pdo->prepare($sql);
        $stmt->execute([':name' => $data['name'], ':email' => $data['email']]);
        return (int) $this->pdo->lastInsertId();
    }
}
  

Использование:

Пример

$pdo = new PDO('mysql:host=localhost;dbname=test', 'root', '', [
    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
]);
$userModel = new UserModel($pdo);
$user = $userModel->findByEmail('test@example.com');
  

Ошибка: если забыть установить режим исключений, ошибки SQL будут молча игнорироваться. Всегда задавайте PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION.

Результат: массив с данными пользователя или null, если email не найден.
  

Пример 3: Обработка исключений и логирование

Пример

<?php
// src/Core/ExceptionHandler.php
namespace App\Core;

class ExceptionHandler
{
    public static function register(): void
    {
        set_exception_handler(function (\Throwable $e) {
            http_response_code(500);
            echo '<h1>Ошибка приложения</h1>';
            echo '<p>' . htmlspecialchars($e->getMessage()) . ' в файле ' . $e->getFile() . ' на строке ' . $e->getLine() . '</p>';
            // Логирование в файл
            error_log(date('[Y-m-d H:i:s] ') . $e->getMessage() . "\n", 3, __DIR__ . '/../../var/log/error.log');
        });
    }
}
  

Вызов при запуске:

Пример

\App\Core\ExceptionHandler::register();
  

Также можно использовать библиотеку monolog/monolog для более продвинутого логирования.

Проблема: в production не следует показывать пользователю детали ошибки - используйте условный вывод только в dev-режиме.

При возникновении исключения (например, соединение с БД) отображается сообщение об ошибке, а в файл var/log/error.log записывается строка с датой и текстом.
  

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

En
Code php app (php)