Создание 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 записывается строка с датой и текстом.