Feature Flags в PHP: как перестать бояться и начать включать фичи

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Feature Flags в PHP: магия включения и выключения функций без деплоя

Представьте: вы написали новую фичу — супер-пупер систему рекомендаций. Всё протестировано на локальной машине, но как только код попадает на продакшен, начинается ад. Пользователи в ярости, конверсия падает, а вы судорожно откатываете коммиты. Знакомо?

А теперь представьте другой сценарий. Вы выкатываете ту же фичу, но она выключена по умолчанию. Включаете её для 5% пользователей, смотрите метрики, раскатываете дальше. Если что-то пошло не так — просто выключаете одним кликом. Без деплоя. Без нервов. Это и есть магия Feature Flags (они же фича-тогглы, флаги функций).

В этой статье на PythonLib мы разберём, как внедрить feature flags в PHP с нуля. Вы узнаете не только теорию, но и получите готовые куски кода, которые можно забрать прямо сейчас.

Что такое Feature Flags и зачем они PHP-разработчику?

Feature Flag (фича-флаг) — это программный переключатель, который позволяет включать или выключать функциональность приложения без изменения кода и повторного деплоя. Проще говоря: вы пишете код один раз, а решаете, показывать его пользователю или нет, уже на лету.

Топ-3 причины использовать флаги функций в PHP:

  • Безопасный деплой: можно выкатывать код в любое время суток. Если что-то сломалось — просто выключаем флаг.
  • A/B тестирование: показываем разным сегментам пользователей разные версии интерфейса и сравниваем метрики.
  • Canary releases (канареечные релизы): раскатываем новую фичу сначала на 1% аудитории, потом на 10%, потом на 100%.
«Feature Flags — это как выключатель света. Вы можете включить его для всей комнаты, а можете — только для одного угла. И вам не нужно переклеивать проводку каждый раз».

Простейшая реализация Feature Flags в PHP

Давайте начнём с самого примитивного, но рабочего варианта. Допустим, у нас есть файл config.php, где хранятся все флаги:

<?php
// config.php
return [
    'features' => [
        'new_checkout' => false,
        'dark_mode'    => true,
        'search_v2'    => false,
    ],
];

А теперь простой класс-обёртка для проверки флагов:

<?php
class FeatureFlagManager
{
    private array $features;

    public function __construct(array $config)
    {
        $this->features = $config['features'] ?? [];
    }

    public function isEnabled(string $flag): bool
    {
        return $this->features[$flag] ?? false;
    }
}

// Использование:
$manager = new FeatureFlagManager(require 'config.php');

if ($manager->isEnabled('dark_mode')) {
    echo '<link rel="stylesheet" href="dark.css">';
}

Минус такого подхода очевиден: чтобы переключить флаг, нужно менять конфиг и передеплоивать приложение. Это не лучше, чем коммитить код. Нам нужно динамическое управление.

Динамические Feature Flags с хранением в БД

Самый распространённый паттерн — хранить состояния флагов в базе данных или Redis. Тогда менять их можно через админ-панель без перезапуска приложения.

Структура таблицы MySQL

CREATE TABLE feature_flags (
    id INT AUTO_INCREMENT PRIMARY KEY,
    flag_name VARCHAR(100) UNIQUE NOT NULL,
    is_enabled TINYINT(1) DEFAULT 0,
    description TEXT,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);

Класс для работы с БД

<?php
class DbFeatureFlags
{
    private PDO $pdo;
    private array $cache = [];

    public function __construct(PDO $pdo)
    {
        $this->pdo = $pdo;
    }

    public function isEnabled(string $flagName): bool
    {
        if (!isset($this->cache[$flagName])) {
            $stmt = $this->pdo->prepare(
                'SELECT is_enabled FROM feature_flags WHERE flag_name = ?'
            );
            $stmt->execute([$flagName]);
            $row = $stmt->fetch();
            $this->cache[$flagName] = $row ? (bool)$row['is_enabled'] : false;
        }
        return $this->cache[$flagName];
    }

    public function enable(string $flagName): void
    {
        $this->setFlag($flagName, true);
    }

    public function disable(string $flagName): void
    {
        $this->setFlag($flagName, false);
    }

    private function setFlag(string $flagName, bool $value): void
    {
        $stmt = $this->pdo->prepare(
            'INSERT INTO feature_flags (flag_name, is_enabled) 
             VALUES (?, ?) 
             ON DUPLICATE KEY UPDATE is_enabled = ?'
        );
        $stmt->execute([$flagName, (int)$value, (int)$value]);
        $this->cache[$flagName] = $value;
    }
}

Пример использования в контроллере

<?php
// Внедряем зависимость через DI-контейнер
$featureFlags = new DbFeatureFlags($pdo);

// В контроллере корзины
if ($featureFlags->isEnabled('new_checkout')) {
    // показываем новый интерфейс оформления заказа
    return $this->render('checkout_new.html.twig');
} else {
    return $this->render('checkout_old.html.twig');
}

Это уже рабочий вариант. Но есть нюанс: каждый запрос к БД — это лишняя нагрузка. Решение — кеширование в Redis или использование in-memory cache.

Продвинутые техники: целевая аудитория и A/B тесты

Настоящая сила feature flags в PHP раскрывается, когда мы начинаем включать фичи не для всех, а для конкретных пользователей. Например:

  • Только для админов
  • Только для пользователей с ID < 1000
  • Для 10% случайных посетителей
  • Для пользователей из конкретного города

Feature Flag с условиями

<?php
class ConditionalFeatureFlag
{
    private DbFeatureFlags $db;
    private array $conditions;

    public function __construct(DbFeatureFlags $db, array $conditions = [])
    {
        $this->db = $db;
        $this->conditions = $conditions;
    }

    public function isEnabledForUser(string $flagName, User $user): bool
    {
        if (!$this->db->isEnabled($flagName)) {
            return false;
        }

        // Проверка кастомных условий
        if (isset($this->conditions[$flagName])) {
            return $this->evaluate($this->conditions[$flagName], $user);
        }

        return true;
    }

    private function evaluate(array $condition                

Блоги

Рекомендаций по книгам