Статья про эксперимент в котором проводились попытки запуска PHP кода, представленного в виде строки. Необходимость реализации данного действия появилась, когда оптимальным результатом одной из операций разрабатываемой системы было хранение в базе данных заготовок php кода, который выполняет определенные действия в зависимости от того, что должно происходить. Возникли некоторые сложно, но результатов добиться удалось, пусть и не тех на которые рассчитывали.
В PHP при использовании при использовании в коде "require_onc"e файл подключается к текущему и становятся доступны класс и методы из этого файла. Допустим есть сценарий при этом мне заранее не известно название файла и я его подключаю в коде (название файла, например, получено из базы данных или текстового файла с диска или загружено из интернета в процессе исполнения основного кода). В этом случае можно исполнить "require_once" и функции и класс из подключенного файла можно использовать. Допустим у меня есть несколько неизвестных файлов и я их подключаю в цикле в зависимости от того что мне нужно сделать и каждый файл содержит группы методов включая тот который будет вызываться в процессе исполнения цикла и этот метод в этих файлах назван одинаково. Как в таком случае поведет себя программа?
Первое обнаруженное решение:
call_user_func — Вызывает пользовательскую функцию, указанную в первом параметре
Оказалось не пригодным потому, что возможно только уже с подключенным файлом, но это может пригодиться если у вас функции одинаковые имена, но разное число на конце - что-то типа индекса функции, но этим методом можно воспользоваться и вызывать функции подряд в цикле. Не представляю где такое может понадобиться, но учитывая, что я хочу сделать в своем случае, то мне кажется у меня диагноз еще серьезнее.
Далее была обнаружено следующее:
eval — Исполняет код PHP, содержащейся в строке.
Исполняет строку, переданную в параметре code, как код PHP.
Предостережение
Использование eval() может быть очень опасно, поскольку позволяет исполнить произвольный код. Использование данной функции не рекомендуется. Если вы полностью убеждены, что нет иного способа воспроизведения необходимого функционала, обратите особое внимание на исключения обработки таким образом данных, вводимых пользователем, без специальной обработки и валидации.
Судя по описанию то что мне нужно, поэтому я решил провести первый тест используя 3 файла.
<?php /*test1.php*/
$str = substr(file_get_contents(dirname(__FILE__).'/test2.php'), strlen("<?php"));
eval($str);
function test3($str) {
$str2 = substr(file_get_contents(dirname(__FILE__).'/test3.php'), strlen("<?php"));
eval($str2);
}
test();
test3("test3");
<?php /*test2.php*/
function test() {
for ($i = 0; $i < 1000000; $i++) {
if ($i * 2 / 2 * 4 / 4 * 2 / 2 * 1 / 1 * 1 / 1 >= 10000000) {
break;
}
}
print "test2.\n";
}
test();
<?php /*test3.php*/
function test2($str) {
print "{$str}.\n";
}
test2($str);
Как оказалось, это не работает так как надо, все дело в том, что код который был отправлен в eval подключается к основному коду. Как вы можете заметите в test1 я вызвал функцию test() из файла, который я не подключал при помощи include, и результат выполнения кода был следующим:
alex@debian:~/myhost$ php -f test1.php
test2.
test2.
test3.
Это подтверждает мои слова о том, что код выполненный в eval был подключен к основному, а значит не получиться вызвать две одинаковые функции в двух разных файлах. Далее я поменял файлы 1 и 2 следующим образом:
<?php /*test1.php*/
$str = substr(file_get_contents(dirname(__FILE__).'/test2.php'), strlen("<?php"));
eval($str);
function test3($str) {
$str2 = substr(file_get_contents(dirname(__FILE__).'/test3.php'), strlen("<?php"));
eval($str2);
}
print $k . "\n";
test();
test3("test3");
<?php /*test2.php*/
function test() {
for ($i = 0; $i < 1000000; $i++) {
if ($i * 2 / 2 * 4 / 4 * 2 / 2 * 1 / 1 * 1 / 1 >= 10000000) {
break;
}
}
print "test2.\n";
}
$k = 2;
test();
В результате вывод программы был таким:
alex@debian:~/myhost$ php -f test1.php
test2.
test2.
2
test3.
Это говорит о том, что использование данной функции не решает поставленную задачу. Как вариант я попробовал сделать так, что в строке посылаемой в eval() будет измениться окончание функции test5(), test6(), test7() и т.д. И сделал так цикл до 10000. После запуска такого фрагмента в системе заметно подскочил уровень использованной оперативной памяти, скорее всего по понятной причине, что весь код пропущенный через eval() был подключен к основному коду программу, что увеличило размер, который она занимается в оперативке. Печально, поэтому продолжаю поиски.
Дополнительный тест, показал, что можно избежать подключение кода из eval() к основному коду, для этого в коде исполняем в через eval() не должно быть никаких функций, в противном случае их потом можно вызывать из основного кода или при одних и тех именах функций в строке кода и основном коде будет ошибка.
Для того, чтобы не было проблем в eval() нужно просто написать исполняемый фрагмент кода, но при этом никаких функций. В качестве тесте в тестовом файле test3.php был так же добавлена глобальная переменная, которая потом была отправлена на печать после функции test3() в файле test1.php в терминале появилась ошибка о том, что переменная которая печаталась не может быть выведена потому, что ее нет.
<?php /*test1.php*/
$str = substr(file_get_contents(dirname(__FILE__).'/test2.php'), strlen("<?php"));
eval($str);
function test3($str) {
$str2 = substr(file_get_contents(dirname(__FILE__).'/test3.php'), strlen("<?php"));
eval($str2);
}
test();
print $k . "\n";
test3("test3");
print $ok;
<?php /*test3.php*/
function test2($str) {
print "{$str}.\n";
}
$ok = 3;
test2($str);
Вывод был таким:
alex@debian:~/myhost$ php -f test1.php
test2.
test2.
2
test3.
PHP Notice: Undefined variable: ok in /home/alex/myhost/test1.php on line 13
Таким образом в коде можно исполнять огромное количество кода, который находится в какой-нибудь функции. Если в файле test1.php, код для вызова файла test3.php убрать за пределы функции test3(), что равно сделать код глобальным как в случае, когда вы вызываем код из файла test2.php . Был проведен тест с последовательным вызовом кода "$ok = 'testik';" внутри функции при этом вызов функции был зациклен:
<?php /*test1.php*/
function iteration($i) {
eval(substr(file_get_contents(dirname(__FILE__).'/test3.php'), strlen("<?php")));
}
for($i=0;$i<100000000000000;$i++) {
iteration($i);
}
<?php /*test3.php*/
$string = 'number - ' . $i;
Данный код нисколько не нагрузил оперативную память.
|