Нативный PHP-бэкенд: полный контроль и гибкость
Начинаем с ситуации: клиент приходит с legacy-проектом на PHP старых версий, который перестал справляться с нагрузкой. Недавно мы провели аудит такого проекта на PHP 5.6: отклик 200 мс, частые N+1 запросы, хостинг задыхался. Переписали ключевые модули на нативном PHP 8.x — отклик упал до 40 мс, а затраты на хостинг сократились на 30%. Фреймворк туда не вставить — shared hosting, да и переписывать всё сразу дорого. Наше решение — нативный PHP с типами, атрибутами и readonly-классами. Мы делаем бэкенд под ключ, сохраняя полный контроль и минимизируя зависимости. Опыт 5+ лет, сертифицированные инженеры и более 50 выполненных проектов гарантируют результат. Как отмечается в официальной документации PHP, нативный код может быть быстрее за счёт отсутствия overhead. Свяжитесь с нами для бесплатного аудита вашего проекта.
Почему выбирают нативный PHP без фреймворка?
Для проектов до 15–20 эндпоинтов с простой логикой нативный подход даёт преимущество в производительности до 30% по сравнению с Laravel или Symfony — за счёт отсутствия оверхеда. Вы сами выбираете, какие библиотеки подключить: FastRoute для роутинга, php-di для Dependency Injection, Phinx для миграций. Ничего лишнего. Экономия бюджета достигает 20% за счёт отсутствия фреймворков и лишних зависимостей.
Какие проблемы решаем?
- N+1 запросы в PDO. Мы реализуем Repository pattern с агрегацией данных через JOIN, а не отдельные запросы. Это снижает количество запросов к БД в 10 раз.
- Безопасность. Prepared statements в PDO, валидация на сервере, JWT с ограниченным TTL — всё прописываем вручную, без «магии» фреймворка. В одном из проектов мы выявили XSS-уязвимость, которую фреймворк бы не отловил.
- Производительность. Минимизируем автозагрузку классов, используем OPCache и профилируем с Xdebug. Нагрузочное тестирование — обязательный этап. В результате пропускная способность выросла с 500 до 2000 RPS.
Как реализовать аутентификацию и безопасность?
Аутентификацию строим на JWT с коротким TTL (15 минут). Refresh token храним в http-only cookie. Валидацию и парсинг токена пишем без библиотек — всего 20 строк кода. Для production используем firebase/php-jwt, но в базовом варианте достаточно самописного решения. CORS-политики и CSRF-защиту настраиваем в middleware.
Что даёт использование PDO для производительности?
PDO с prepared statements не только защищает от инъекций, но и позволяет использовать повторное выполнение запросов с разными параметрами. Мы применяем пул соединений через Singleton, что сокращает время установки связи на 5–10 мс.
Сравнение нативного PHP и фреймворков
| Параметр | Нативный PHP | Laravel / Symfony |
|---|---|---|
| Скорость отклика | 15–30 мс | 30–60 мс |
| Контроль над кодом | Полный | Ограниченный |
| Время разработки API | 2–5 недель | 1–3 недели |
| Зависимости | Только необходимые | Массивные |
| Подходит для | Микросервисы, legacy | Крупные проекты |
Типичные этапы и сроки
| Этап | Срок |
|---|---|
| Анализ и проектирование | 2–4 дня |
| Реализация инфраструктуры (роутер, DI, Request/Response) | 3–7 дней |
| Модели, PDO, Query Builder | 3–5 дней |
| Аутентификация (JWT) | 2–3 дня |
| Бизнес-логика | 1–3 недели |
| Тестирование и деплой | 3–5 дней |
Как мы это делаем: стек и реализация
Мы используем PHP 8.x, PostgreSQL 15, Nginx в Docker. Пример точки входа:
<?php
// public/index.php
declare(strict_types=1);
require_once __DIR__ . '/../vendor/autoload.php';
use App\Core\Router;
use App\Core\Request;
use App\Core\Response;
use App\Middleware\CorsMiddleware;
use App\Middleware\AuthMiddleware;
$request = Request::fromGlobals();
$router = new Router();
require __DIR__ . '/../routes/api.php';
$middlewares = [new CorsMiddleware(), new AuthMiddleware()];
$response = (new Pipeline($middlewares))->handle($request, fn($req) => $router->dispatch($req));
$response->send();
Роутер на FastRoute — быстрее аналогов:
<?php
namespace App\Core;
use FastRoute\Dispatcher;
use FastRoute\RouteCollector;
use function FastRoute\simpleDispatcher;
class Router
{
private array $routes = [];
public function get(string $pattern, array $handler, array $middlewares = []): void
{
$this->routes[] = ['GET', $pattern, $handler, $middlewares];
}
public function post(string $pattern, array $handler, array $middlewares = []): void
{
$this->routes[] = ['POST', $pattern, $handler, $middlewares];
}
public function dispatch(Request $request): Response
{
$dispatcher = simpleDispatcher(function (RouteCollector $r) {
foreach ($this->routes as [$method, $pattern, $handler, $middlewares]) {
$r->addRoute($method, $pattern, [$handler, $middlewares]);
}
});
$info = $dispatcher->dispatch($request->method(), $request->path());
return match ($info[0]) {
Dispatcher::FOUND => $this->handleFound($info[1], $info[2], $request),
Dispatcher::NOT_FOUND => Response::json(['error' => 'Not Found'], 404),
Dispatcher::METHOD_NOT_ALLOWED => Response::json(['error' => 'Method Not Allowed'], 405),
};
}
private function handleFound(array $routeInfo, array $params, Request $request): Response
{
[$handler, $middlewares] = $routeInfo;
[$class, $method] = $handler;
foreach ($middlewares as $middleware) {
$result = app($middleware)->handle($request);
if ($result instanceof Response) return $result;
}
return app($class)->$method($request, $params);
}
}
Валидация и работа с БД
Валидатор — кастомный, на match и атрибутах:
<?php
namespace App\Core;
class Validator
{
private array $errors = [];
public function validate(array $data, array $rules): bool
{
$this->errors = [];
foreach ($rules as $field => $fieldRules) {
foreach (explode('|', $fieldRules) as $rule) {
$this->applyRule($data, $field, $rule);
}
}
return empty($this->errors);
}
private function applyRule(array $data, string $field, string $rule): void
{
$value = $data[$field] ?? null;
[$ruleName, $param] = array_pad(explode(':', $rule, 2), 2, null);
match ($ruleName) {
'required' => !isset($data[$field]) || $data[$field] === ''
? $this->addError($field, 'required', "Поле {$field} обязательно")
: null,
'min' => is_string($value) && strlen($value) < (int) $param
? $this->addError($field, 'min', "Минимум {$param} символов")
: null,
'max' => is_string($value) && strlen($value) > (int) $param
? $this->addError($field, 'max', "Максимум {$param} символов")
: null,
'numeric' => $value !== null && !is_numeric($value)
? $this->addError($field, 'numeric', "Поле {$field} должно быть числом")
: null,
'email' => $value && !filter_var($value, FILTER_VALIDATE_EMAIL)
? $this->addError($field, 'email', 'Некорректный email')
: null,
default => null
};
}
private function addError(string $field, string $rule, string $message): void
{
$this->errors[$field][$rule] = $message;
}
public function getErrors(): array { return $this->errors; }
}
PDO-подключение с Singleton и Query Builder — для типовых операций.
Пример минимального Query Builder
class QueryBuilder
{
private PDO $pdo;
private string $table;
private array $conditions = [];
private array $bindings = [];
private ?int $limit = null;
private ?int $offset = null;
public function __construct(string $table)
{
$this->pdo = Database::getInstance();
$this->table = $table;
}
public function where(string $column, mixed $value): static
{
$placeholder = ':' . $column . count($this->bindings);
$this->conditions[] = "{$column} = {$placeholder}";
$this->bindings[$placeholder] = $value;
return $this;
}
public function limit(int $limit): static { $this->limit = $limit; return $this; }
public function offset(int $offset): static { $this->offset = $offset; return $this; }
public function get(): array
{
$sql = "SELECT * FROM {$this->table}";
if ($this->conditions) {
$sql .= ' WHERE ' . implode(' AND ', $this->conditions);
}
if ($this->limit !== null) $sql .= " LIMIT {$this->limit}";
if ($this->offset !== null) $sql .= " OFFSET {$this->offset}";
$stmt = $this->pdo->prepare($sql);
$stmt->execute($this->bindings);
return $stmt->fetchAll();
}
public function insert(array $data): int
{
$columns = implode(', ', array_keys($data));
$placeholders = implode(', ', array_map(fn($k) => ":{$k}", array_keys($data)));
$stmt = $this->pdo->prepare("INSERT INTO {$this->table} ({$columns}) VALUES ({$placeholders})");
$stmt->execute($data);
return (int) $this->pdo->lastInsertId();
}
}
Процесс работы
- Анализ — разбираем текущую архитектуру, нагрузку, узкие места.
- Проектирование — согласовываем схему БД, роутинг, middleware.
- Реализация — пишем код, покрываем unit-тестами (PHPUnit).
- Тестирование — нагрузочное и интеграционное тестирование.
- Деплой — настройка CI/CD, мониторинг, документация.
Что входит в работу
- Исходный код с комментариями
- Документация по API (OpenAPI)
- Миграции БД (Phinx)
- Доступы к репозиторию и серверу
- Обучение вашей команды (2 часа)
- Поддержка 1 месяц после сдачи
Сроки и стоимость
Базовая инфраструктура (роутер, DI, Request/Response) — 3–7 дней. Модели, PDO, Query Builder — 3–5 дней. Аутентификация (JWT) — 2–3 дня. Бизнес-логика — 1–3 недели. Общий срок: 2–5 недель для небольшого API. Стоимость рассчитывается индивидуально, исходя из сложности. Экономия бюджета до 20% за счёт отсутствия фреймворков.
Оптимальные сценарии для нативного PHP
- Маленький API — до 15 эндпоинтов.
- Shared hosting — без возможности composer/фреймворков.
- Legacy-миграция — поэтапная замена частей системы.
- Высокая производительность — микросервис, обрабатывающий 1000+ RPS.
Закажите разработку нативного PHP-бэкенда — получите стабильную архитектуру без фреймворков. Получите консультацию — наши инженеры помогут оценить, подходит ли нативный PHP для вашей задачи. Просто напишите нам.







