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