WDN

Рефакторинг switch на PHP

Множественные if или switch быстро становятся громоздкими, когда вариантов много: нарушается принцип открытости/закрытости, код дублируется, а добавление новых условий требует изменения метода. Рассмотрим несколько способов сделать такой код гибче и проще в сопровождении на примере функции, возвращающей HEX-значение цвета по его названию.

Для начала взглянем на типичную реализацию.


class Refactoring
{
    public function getColorWithIf(string $color): ?string
    {
        if ($color === 'red') {
            return '#FF0000';
        } elseif ($color === 'blue') {
            return '#0000FF';
        }
        return null;
    }

    public function getColorWithSwitch(string $color): ?string
    {
        switch ($color) {
            case 'red':
                return '#FF0000';
            case 'blue':
                return '#0000FF';
        }
        return null;
    }
}

Пока цветов два, проблем нет. Но представьте, что их станет двадцать, и вам регулярно нужно добавлять новые. Код начнёт распухать, а сопровождать его станет сложнее. Займёмся рефакторингом.

Начиная с PHP 8.0 можно заменить switch на match. Он компактнее и безопаснее (строгое сравнение). Для небольшого количества веток это самый простой способ улучшить читаемость.


class Refactoring
{
    public function getColorWithMatch(string $color): ?string
    {
        return match ($color) {
            'red'  => '#FF0000',
            'blue' => '#0000FF',
            default => null,
        };
    }
}

$test = new Refactoring();
var_dump($test->getColorWithMatch('red')); // string(7) "#FF0000"

Однако match не решает проблему нарушения принципа открытости/закрытости: при добавлении нового цвета всё равно придётся лезть внутрь метода. Для 5–7 значений он оправдан, для большего — лучше пойти дальше.

Самый простой и действенный способ полноценного рефакторинга — заменить условные операторы поиском по массиву. Данные выносятся в константу класса, чтобы не создавать массив заново при каждом вызове.


class Refactoring
{
    private const COLORS = [
        'red'  => '#FF0000',
        'blue' => '#0000FF',
    ];

    public function getColorWithArray(string $color): ?string
    {
        return self::COLORS[$color] ?? null;
    }
}

$test = new Refactoring();
var_dump($test->getColorWithArray('red')); // string(7) "#FF0000"

Метод стал однострочным, легко читается и расширяется — достаточно добавить элемент в массив. Для статического маппинга «имя → значение» этот подход оптимален.

Если требуется не только вернуть значение, но и использовать его в типизированном виде по всему приложению, подойдут перечисления. Важно сохранить семантику метода: при неизвестном цвете он должен возвращать null, а не падать с ошибкой.


enum Colors: string
{
    case Red = '#FF0000';
    case Blue = '#0000FF';

    public static function tryFromName(string $name): ?self
    {
        foreach (self::cases() as $case) {
            if ($case->name === $name) {
                return $case;
            }
        }
        return null;
    }
}

class Refactoring
{
    public function getColorWithEnum(string $color): ?string
    {
        $enum = Colors::tryFromName(ucfirst($color));
        return $enum?->value;
    }
}

$test = new Refactoring();
var_dump($test->getColorWithEnum('red'));   // string(7) "#FF0000"
var_dump($test->getColorWithEnum('green')); // NULL

Если каждый цвет должен не просто отдавать HEX-код, но и реализовывать различную логику (расчёт яркости, смешивание, рендеринг), можно воспользоваться полиморфизмом. Для простого маппинга это избыточно, но идею полезно знать.


interface ColorInterface
{
    public function getColor(): string;
}

class Red implements ColorInterface
{
    public function getColor(): string
    {
        return '#FF0000';
    }
}

class Blue implements ColorInterface
{
    public function getColor(): string
    {
        return '#0000FF';
    }
}

class ColorFactory
{
    private const MAP = [
        'red'  => Red::class,
        'blue' => Blue::class,
    ];

    public static function create(string $color): ?ColorInterface
    {
        $class = self::MAP[$color] ?? null;
        return $class ? new $class() : null;
    }
}

class Refactoring
{
    public function getColorPolymorphic(string $color): ?string
    {
        $colorObj = ColorFactory::create($color);
        return $colorObj?->getColor();
    }
}

$test = new Refactoring();
var_dump($test->getColorPolymorphic('red'));   // string(7) "#FF0000"
var_dump($test->getColorPolymorphic('green')); // NULL

Фабрика инкапсулирует логику создания и маппинг названий на классы. Это хороший способ избежать проблем с автозагрузкой и пространствами имён, которые неизбежны при прямом new $className. Такой подход соответствует принципу открытости/закрытости: для добавления нового цвета достаточно создать класс и зарегистрировать его в фабрике.

Оператор match предназначен для быстрого рефакторинга небольших цепочек условий, примерно до 5–7 веток. Он чист, выразителен и не требует вынесения данных в константы класса, однако фундаментальную проблему расширения не решает. Ассоциативный массив — наилучший выбор для простого сопоставления «строка → HEX». Он компактен, безопасен, легко расширяется и не перегружает код. Перечисления стоит использовать, когда значение цвета — не просто строка, которая в будущем может потребовать дополнительного поведения. При этом не следует превращать enum в божественный объект только ради пары HEX-значений: если вся логика исчерпывается возвратом строки, массив предпочтительнее. Полиморфизм оправдан исключительно в тех случаях, когда разные цвета обладают принципиально разной логикой. Если каждый цвет делает одно и то же, не усложняйте код иерархией классов — это затруднит чтение и поддержку, особенно для начинающих разработчиков.

Выбирайте инструмент под задачу, и ваш код останется ясным и удобным.

29.11.2024

Обо мне | RSS
© 2010-2026 WDN