
Множественные 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-значений: если вся логика исчерпывается возвратом строки, массив предпочтительнее. Полиморфизм оправдан исключительно в тех случаях, когда разные цвета обладают принципиально разной логикой. Если каждый цвет делает одно и то же, не усложняйте код иерархией классов — это затруднит чтение и поддержку, особенно для начинающих разработчиков.
Выбирайте инструмент под задачу, и ваш код останется ясным и удобным.