Разработка бэкенда сайта на PHP (нативный)

Наша компания занимается разработкой, поддержкой и обслуживанием сайтов любой сложности. От простых одностраничных сайтов до масштабных кластерных систем построенных на микро сервисах. Опыт разработчиков подтвержден сертификатами от вендоров.
Разработка и обслуживание любых видов сайтов:
Информационные сайты или веб-приложения
Сайты визитки, landing page, корпоративные сайты, онлайн каталоги, квиз, промо-сайты, блоги, новостные ресурсы, информационные порталы, форумы, агрегаторы
Сайты или веб-приложения электронной коммерции
Интернет-магазины, B2B-порталы, маркетплейсы, онлайн-обменники, кэшбэк-сайты, биржи, дропшиппинг-платформы, парсеры товаров
Веб-приложения для управления бизнес-процессами
CRM-системы, ERP-системы, корпоративные порталы, системы управления производством, парсеры информации
Сайты или веб-приложения электронных услуг
Доски объявлений, онлайн-школы, онлайн-кинотеатры, конструкторы сайтов, порталы предоставления электронных услуг, видеохостинги, тематические порталы

Это лишь некоторые из технических типов сайтов, с которыми мы работаем, и каждый из них может иметь свои специфические особенности и функциональность, а также быть адаптированным под конкретные потребности и цели клиента

Предлагаемые услуги
Показано 1 из 1 услугВсе 2065 услуг
Разработка бэкенда сайта на PHP (нативный)
Средняя
~3-5 рабочих дней
Часто задаваемые вопросы
Наши компетенции:
Этапы разработки
Последние работы
  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1214
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1161
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    852
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1041
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    823
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Разработка веб-сайта для компании ФИКСПЕР
    815

Разработка бэкенда сайта на PHP (нативный)

Нативный PHP без фреймворка — не архаика. Это осознанный выбор, когда нужен максимальный контроль, минимальный overhead или когда проект уже существует и менять стек нецелесообразно. PHP 8.2+ с типами, атрибутами, readonly-классами и фибрами — это совсем другой язык по сравнению с PHP 5.

Нативный PHP оправдан для: небольших API с предсказуемой функциональностью, shared hosting без возможности установки фреймворков, миграции legacy-проектов по частям, критичных по производительности микросервисов.

Точка входа и роутинг

Центральный паттерн — единая точка входа (index.php) с роутером:

<?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';

// Pipeline middleware
$middlewares = [new CorsMiddleware(), new AuthMiddleware()];
$response    = (new Pipeline($middlewares))->handle($request, fn($req) => $router->dispatch($req));
$response->send();
<?php
// routes/api.php

use App\Controllers\ProductController;
use App\Controllers\AuthController;

$router->get('/api/v1/products',         [ProductController::class, 'index']);
$router->get('/api/v1/products/{id:\d+}', [ProductController::class, 'show']);
$router->post('/api/v1/products',         [ProductController::class, 'create'], ['auth', 'admin']);
$router->delete('/api/v1/products/{id}',  [ProductController::class, 'destroy'], ['auth', 'admin']);

$router->post('/api/v1/auth/login',   [AuthController::class, 'login']);
$router->post('/api/v1/auth/refresh', [AuthController::class, 'refresh']);

Роутер

<?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;

        // Проверка middleware
        foreach ($middlewares as $middleware) {
            $result = app($middleware)->handle($request);
            if ($result instanceof Response) return $result;
        }

        return app($class)->$method($request, $params);
    }
}

fastroute/fastroute — лучший PHP-роутер по производительности, используется внутри Slim и других фреймворков.

PDO и работа с БД

<?php
namespace App\Core;

use PDO;
use PDOStatement;

class Database
{
    private static ?PDO $instance = null;

    public static function getInstance(): PDO
    {
        if (self::$instance === null) {
            $dsn = sprintf('pgsql:host=%s;dbname=%s;port=%s',
                getenv('DB_HOST'), getenv('DB_NAME'), getenv('DB_PORT') ?: '5432'
            );
            self::$instance = new PDO($dsn, getenv('DB_USER'), getenv('DB_PASS'), [
                PDO::ATTR_ERRMODE            => PDO::ERRMODE_EXCEPTION,
                PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
                PDO::ATTR_EMULATE_PREPARES   => false,
            ]);
        }
        return self::$instance;
    }
}

// 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();
    }
}

Валидация

<?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; }
}

JWT без библиотек для простых случаев

function createJwt(array $payload, string $secret, int $ttl = 3600): string
{
    $header   = base64_url_encode(json_encode(['alg' => 'HS256', 'typ' => 'JWT']));
    $payload  = base64_url_encode(json_encode(array_merge($payload, ['exp' => time() + $ttl])));
    $sig      = base64_url_encode(hash_hmac('sha256', "{$header}.{$payload}", $secret, true));
    return "{$header}.{$payload}.{$sig}";
}

// Но лучше использовать firebase/php-jwt для production

Когда нативный PHP заканчивается

Нативный подход требует самостоятельного написания инфраструктурного кода — роутер, DI-контейнер, валидатор, миграции. На практике это означает либо использование composer-библиотек (fastroute, php-di, phinx), либо написание своего велосипеда. В обоих случаях при росте проекта начинает работать фреймворк — либо чужой (Symfony Components), либо свой неполный.

Граница рентабельности нативного PHP — проекты до 15–20 эндпоинтов с простой логикой. Дальше фреймворк экономит время.

Сроки разработки

  • Базовая инфраструктура: роутер, DI, Request/Response — 3–7 дней
  • Модели + PDO + Query Builder — 3–5 дней
  • Auth + JWT — 2–3 дня
  • Бизнес-логика — 1–3 недели

Небольшой API или виджет для существующего сайта: 2–5 недель.