Нативний 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 для вашого завдання. Просто напишіть нам.







