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







