eval() — это попытка интерпретатора PHP выполнить произвольную строку как программный код. Это швейцарский нож, у которого вместо лезвия — циркулярная пила. Она удобна для написания «умных» конфигураций или динамических правил, но делает ваш проект уязвимым к RCE-атакам (Remote Code Execution) и невозможным для статического анализа инструментами вроде Psalm или PHPStan.Хорошая новость: в 95% случаев можно найти безопасную альтернативу без потери функционала.

Сценарий 1: Динамические правила и математические выражения
Проблема: Пользователь вводит формулу скидки, например
(price * quantity) - bonus. Вы используете eval("return $formula;").Решение 1: Библиотеки парсинга выражений
Вместо выполнения кода, разберите его синтаксис.
- Инструмент:
symfony/expression-languageилиnxp/math-parser. - Как работает: Эти библиотеки не исполняют код, а строят дерево операций (AST). Они понимают только математику и логику, игнорируя системные вызовы.
ВыделитьPHP
// Было (Опасно)
$result = eval('return ' . $userFormula . ';');
// Стало (Безопасно)
use SymfonyComponentExpressionLanguageExpressionLanguage;
$language = new ExpressionLanguage();
$result = $language->evaluate($userFormula, [
'price' => 100,
'quantity' => 2,
'bonus' => 10
]);Сценарий 2: Хранение настроек в виде PHP-массивов
Проблема: Конфигурационный файл возвращает массив через
return [...]. Вы подключаете его через eval(file_get_contents()) или просто include.Решение 2: Переход на стандартные форматы данных
PHP-код — худший формат для конфигов, так как он имеет полный доступ к серверу.
- Вариант А (Рекомендуемый): Использовать
NEON(из Nette),YAMLили.env. Для их чтения нужны простые парсеры (nette/utils), которые не могут выполнить вредоносный код. - Вариант Б (Если нужно остаться в PHP): Используйте нативный require.
eval('?>' . file_get_contents('config.php'))заменяется на$config = require 'config.php';.- Почему это лучше: Хотя
requireвсе еще выполняет код, область видимости ограничена возвращаемым значением. Главное правило здесь — никогда не давать пользователям права записи в файлы конфигурации.
Сценарий 3: Вызов методов по строке (Callback/Dynamism)
Проблема:
ВыделитьPHP
eval("$class_name::MyStaticMethod();");Это часто встречается в старых роутерах или системах хуков.
Решение 3: call_user_func и стрелочные функции (Callable)
Интерпретатор умеет работать со строками-функциями напрямую, если они валидны.
ВыделитьPHP
// Было
eval("$controller::indexAction($id);");
// Стало (Вариант 1: Простой вызов)
call_user_func([$controller, 'indexAction'], $id);
// Стало (Вариант 2: Современный callable)
$callback = [$controller, 'indexAction'];
$callback($id);Для статических методов:
ВыделитьPHP
call_user_func([ClassName::class, 'methodName']);Сценарий 4: Создание классов "на лету" (Proxy/Decorator)
Проблема: Некоторые старые библиотеки генерируют код класса в строку и запускают eval(), чтобы создать наследника прямо в памяти.
Решение 4: runkit7 или Anonymous Classes
- Anonymous Classes (PHP 7+): Если вам нужен класс-заглушка, используйте встроенные анонимные классы. Они создаются безопасным способом внутри области видимости.
- runkit7: Это PECL-расширение, которое позволяет модифицировать код во время выполнения более контролируемо, чем
eval, хотя оно тоже считается мощным инструментом и требует осторожности.
Сценарий 5: Шаблонизаторы старого типа (preg_replace + /e)
Проблема: Использование устаревшего модификатора /e в регулярных выражениях, который выполнял найденный текст как PHP.
Решение 5: preg_replace_callback
Это стандарт де-факто уже много лет.
ВыделитьPHP
// Устарело и удалено в новых версиях
$html = preg_replace('/{{s*(.+?)s*}}/e', '$this->$1', $template);
// Современный способ
$html = preg_replace_callback('/{{s*(.+?)s*}}/', function ($matches) use ($obj) {
return $obj->{$matches[1]}();
}, $template);Итоговый чек-лист рефакторинга
- Поиск: Выполните команду
grep -R "eval(" .в корне проекта. - Проверка ввода: Если внутри
eval()есть переменная из$_GET,$_POSTили базы данных — это критическая уязвимость. Меняйте немедленно. - Локализация: Если
eval()используется для подключения изолированного файла настроек, замените на require, но убедитесь, что путь жестко прописан и пользователь не может его изменить. - Логика: Если это бизнес-правило, вынесите его в отдельный сервис и используйте библиотеку выражений (Scenario 1).
Отказываясь от
eval(), вы делаете свой код быстрее (OPcache сможет закэшировать файлы), надежнее и понятнее для IDE. Попробуйте заменить хотя бы один вызов сегодня — ваши глаза (и сервера) скажут вам спасибо.