Розробка бекенду сайту на PHP (CodeIgniter)

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

Це лише деякі з технічних типів сайтів, з якими ми працюємо, і кожен із них може мати свої специфічні особливості та функціональність, а також бути адаптованим під конкретні потреби та цілі клієнта.

Пропоновані послуги
Показано 1 з 1 послугУсі 2065 послуг
Розробка бекенду сайту на PHP (CodeIgniter)
Середня
~3-5 робочих днів
Часті питання
Наші компетенції:
Етапи розробки
Останні роботи
  • image_website-b2b-advance_0.png
    Розробка сайту компанії B2B ADVANCE
    1262
  • image_web-applications_feedme_466_0.webp
    Розробка веб-додатків для компанії FEEDME
    1171
  • image_websites_belfingroup_462_0.webp
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    874
  • image_ecommerce_furnoro_435_0.webp
    Розробка інтернет магазину для компанії FURNORO
    1094
  • image_crm_enviok_479_0.webp
    Розробка веб-додатків для компанії Enviok
    831
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Розробка веб-сайту для компанії ФІКСПЕР
    851

Розробка веб-бекенду на PHP (CodeIgniter)

CodeIgniter — найлегший повноцінний PHP-фреймворк. CodeIgniter 4 повністю переписаний під PHP 8, отримав PSR-сумісність, вбудований ORM (Query Builder + Model), HTTP-прошарок через PSR-7, та зберег основну перевагу: мінімальний overhead, просту документацію, відсутність магії.

Правильні сценарії для CI4: невеликі та середні сайти, хостинг з обмеженнями (shared hosting, застаріла інфраструктура), команди з досвідом CI, проекти з жорсткими вимогами до продуктивності на слабкому обладнанні.

Структура та Маршрутизація

// app/Config/Routes.php
$routes->group('api/v1', ['namespace' => 'App\Controllers\Api\V1'], function ($routes) {
    // Auth (публічні)
    $routes->post('auth/login', 'AuthController::login');
    $routes->post('auth/refresh', 'AuthController::refresh');

    // Захищені фільтром
    $routes->group('', ['filter' => 'jwt'], function ($routes) {
        $routes->get('profile', 'UserController::profile');
        $routes->resource('products', ['controller' => 'ProductController']);
        // Генерує: GET /, GET /:id, POST /, PUT /:id, DELETE /:id
    });

    // Тільки для адміна
    $routes->group('admin', ['filter' => 'jwt:admin'], function ($routes) {
        $routes->resource('users', ['controller' => 'Admin\UserController']);
    });
});

Контролери

namespace App\Controllers\Api\V1;

use App\Controllers\BaseController;
use App\Models\ProductModel;
use CodeIgniter\HTTP\ResponseInterface;

class ProductController extends BaseController
{
    private ProductModel $productModel;

    public function __construct()
    {
        $this->productModel = new ProductModel();
    }

    public function index(): ResponseInterface
    {
        $page  = (int) ($this->request->getGet('page') ?? 1);
        $limit = min((int) ($this->request->getGet('limit') ?? 20), 100);

        $query = $this->productModel
            ->where('is_active', 1)
            ->orderBy('created_at', 'DESC');

        if ($categoryId = $this->request->getGet('category_id')) {
            $query->where('category_id', (int) $categoryId);
        }

        $total    = $query->countAllResults(false);
        $products = $query->paginate($limit, 'default', $page);

        return $this->response->setJSON([
            'data'       => $products,
            'pagination' => [
                'page'  => $page,
                'limit' => $limit,
                'total' => $total,
                'pages' => (int) ceil($total / $limit),
            ],
        ]);
    }

    public function create(): ResponseInterface
    {
        $data = $this->request->getJSON(true);

        if (!$this->validate($this->productModel->getValidationRules())) {
            return $this->response->setStatusCode(422)->setJSON([
                'errors' => $this->validator->getErrors(),
            ]);
        }

        $id = $this->productModel->insert($data);
        $product = $this->productModel->find($id);

        return $this->response->setStatusCode(201)->setJSON($product);
    }
}

Модель та Query Builder

namespace App\Models;

use CodeIgniter\Model;

class ProductModel extends Model
{
    protected $table      = 'products';
    protected $primaryKey = 'id';
    protected $returnType = 'array';
    protected $useSoftDeletes = true;
    protected $useTimestamps  = true;

    protected $allowedFields = ['name', 'slug', 'price', 'category_id', 'description', 'is_active', 'attributes'];

    protected $validationRules = [
        'name'        => 'required|min_length[2]|max_length[255]',
        'price'       => 'required|decimal|greater_than[0]',
        'category_id' => 'permit_empty|integer|is_not_unique[categories.id]',
    ];

    protected $validationMessages = [
        'name'  => ['required' => 'Назва обов\'язкова'],
        'price' => ['greater_than' => 'Ціна має бути більше нуля'],
    ];

    protected $beforeInsert = ['generateSlug'];
    protected $beforeUpdate = ['generateSlug'];

    protected function generateSlug(array $data): array
    {
        if (isset($data['data']['name']) && empty($data['data']['slug'])) {
            $data['data']['slug'] = url_title($data['data']['name'], '-', true);
        }
        return $data;
    }

    // Кастомні методи
    public function getActiveByCategory(int $categoryId, int $limit = 20, int $offset = 0): array
    {
        return $this->where('category_id', $categoryId)
                    ->where('is_active', 1)
                    ->orderBy('created_at', 'DESC')
                    ->findAll($limit, $offset);
    }

    public function getWithCategory(): array
    {
        return $this->db->table($this->table . ' p')
            ->select('p.*, c.name as category_name')
            ->join('categories c', 'c.id = p.category_id', 'left')
            ->where('p.is_active', 1)
            ->get()
            ->getResultArray();
    }
}

JWT Фільтр Автентифікації

namespace App\Filters;

use CodeIgniter\Filters\FilterInterface;
use CodeIgniter\HTTP\RequestInterface;
use CodeIgniter\HTTP\ResponseInterface;
use Firebase\JWT\JWT;
use Firebase\JWT\Key;

class JwtFilter implements FilterInterface
{
    public function before(RequestInterface $request, $arguments = null)
    {
        $authHeader = $request->getHeaderLine('Authorization');
        if (!str_starts_with($authHeader, 'Bearer ')) {
            return service('response')->setStatusCode(401)->setJSON(['error' => 'Unauthorized']);
        }

        try {
            $token = substr($authHeader, 7);
            $payload = JWT::decode($token, new Key(getenv('JWT_SECRET'), 'HS256'));

            // Перевірка ролі якщо передана в arguments
            if ($arguments && !in_array($payload->role, $arguments)) {
                return service('response')->setStatusCode(403)->setJSON(['error' => 'Forbidden']);
            }

            // Зберігаємо в request для контролерів
            $request->user = $payload;
        } catch (\Exception $e) {
            return service('response')->setStatusCode(401)->setJSON(['error' => 'Invalid token']);
        }
    }

    public function after(RequestInterface $request, ResponseInterface $response, $arguments = null) {}
}

Кеширування

// Через cache() helper
$cacheKey = "products_cat_{$categoryId}_page_{$page}";
$products = cache($cacheKey);

if ($products === null) {
    $products = $this->productModel->getActiveByCategory($categoryId, 20, ($page - 1) * 20);
    cache()->save($cacheKey, $products, 300); // 5 хвилин
}

// Теговане кеш — тільки Redis driver
$cache = \Config\Services::cache();
$cache->save("product_{$id}", $product, 3600, ['products', "category_{$product['category_id']}"]);
// Інвалідація за тегом
$cache->deleteMatching('products*'); // wildcard для Redis

Завантаження Файлів

public function upload(): ResponseInterface
{
    $file = $this->request->getFile('image');

    if (!$file->isValid()) {
        return $this->response->setStatusCode(400)->setJSON(['error' => $file->getErrorString()]);
    }

    $rules = [
        'image' => 'uploaded[image]|max_size[image,10240]|is_image[image]|mime_in[image,image/jpg,image/jpeg,image/png,image/webp]',
    ];

    if (!$this->validate($rules)) {
        return $this->response->setStatusCode(422)->setJSON(['errors' => $this->validator->getErrors()]);
    }

    $newName = $file->getRandomName();
    $file->move(WRITEPATH . 'uploads', $newName);

    // Або на S3 через кастомне сховище
    $url = $this->uploadToS3($file->getTempName(), $newName);

    return $this->response->setJSON(['url' => $url]);
}

Терміни Розробки

CodeIgniter 4 стартує швидко:

  • Конфігурація + моделі + міграції — 2–4 дні
  • Маршрути + контролери + auth — 1–1,5 тижня
  • Бізнес-логіка — 1–3 тижні
  • Тести (phpunit + CI Test Tools) — 3–5 днів

Невеликий або середній сайт: 3–7 тижнів. CI4 — прагматичний вибір, коли потрібен робочий бекенд без зайвих залежностей та високого порога входу.