PHP: эквивалент включения с использованием eval

Если код тот же, то, по-видимому, есть разница между:

include 'external.php';

и

eval('?>' . file_get_contents('external.php') . '<?php');

В чем разница? Кто-нибудь знает?


Я знаю, что они разные, потому что include работает нормально, а eval выдает ошибку. Когда я первоначально задал вопрос, я не был уверен, выдает ли он ошибку во всем коде или только в моем (а поскольку код был evaled, было очень сложно выяснить, что означает ошибка). Однако после изучения ответа выясняется, что получение вами ошибки не зависит от кода в external.php, но зависит от ваших настроек php (точнее, short_open_tag).


person Jasper    schedule 26.07.2009    source источник
comment
Спасибо за этот вопрос. В этом помогло: github.com/tedivm/Stash/pull/135   -  person CMCDragonkai    schedule 15.03.2014


Ответы (8)


После еще нескольких исследований я сам выяснил, что было не так. Проблема в том, что <?php является "коротким открывающим тегом" и будет работать, только если short_open_tag установлено в 1 (в php.ini или что-то подобное). Правильный полный тег — <?php, в котором есть пробел после второго p.

Таким образом, правильный эквивалент включения:

eval('?>' . file_get_contents('external.php') . '<?php ');

В качестве альтернативы вы можете вообще не открывать открывающий тег (как указано в комментариях ниже):

eval('?>' . file_get_contents('external.php'));

Мое первоначальное решение состояло в том, чтобы добавить точку с запятой, которая также работает, но выглядит гораздо менее аккуратно, если вы спросите меня:

eval('?>' . file_get_contents('external.php') . '<?php;');
person Jasper    schedule 26.07.2009
comment
не eval(?›.file_get_contents('external.php')); то же, что и include 'external.php'; ?? - person Timo Huovinen; 25.03.2010
comment
ЮрийКоловский, вы правы, опустив тег открытия php, это альтернатива тому, чтобы после него ставить точку с запятой. - person Jasper; 14.04.2010
comment
могу я спросить, почему вы добавили '?›' и '‹?php;' ? Кстати, у меня возникла ошибка, когда я использовал '‹?php;'. Я исправил это, удалив часть «php». - person kapitanluffy; 27.05.2012
comment
@kapitanluffy: include обрабатывает файл, что означает, что он запускается в текстовом режиме (в отличие от режима php, где в режиме php выполняется код, а в текстовом режиме текст просто копируется в вывод), а eval запускается в режиме php . Я вышел из режима php в случае eval, чтобы он делал то же самое, что и include. - person Jasper; 20.06.2012

Насколько я знаю, вы не можете воспользоваться ускорителями php, если используете eval().

person niteria    schedule 26.07.2009
comment
Насколько я знаю, у вас должен быть настоящий файл в файловой системе. - person niteria; 27.07.2009
comment
... и вам не следует беспокоиться об этом, если у вас нет проблем с производительностью. - person niteria; 27.07.2009
comment
Я имел в виду, что ускорители php AFAIK работают только с реальными файлами в файловой системе. Сборка для ускорителей php, скорее всего, усложнит ваш код (вам нужно проверить, доступны ли файлы для записи и т. д.), и, возможно, это не принесет видимых улучшений. Если это просто файлы шаблонов, я думаю, это ничего не изменит. - person niteria; 27.07.2009
comment
@niteria Просто хотел отметить, что вы слишком много говорите. - person leek; 11.10.2010
comment
Я решил удалить (свою часть) обсуждение, которое занимало здесь слишком много места. Этот ответ отвечает на исходную формулировку вопроса, хотя я не верю, что он отвечает на его суть (которая осталась прежней). Обсуждались также вещи, не входящие в вопрос (потоковые оболочки). Наконец, пункт о том, что следует опасаться невозможности использовать кеш кода операции (а поскольку в php 5.5 он встроен в php) с eval, является хорошим, хотя это может быть скорее комментарий, чем ответ с текущей формулировкой . - person Jasper; 23.09.2013

Если вы используете веб-сервер, на котором установлен кэш кода операции, например APC, eval не будет "< em>лучшее решение" : код eval'd не хранится в кеше кода операции, если я правильно помню (и в другом ответе говорилось то же самое, кстати).

Решение, которое вы могли бы использовать, по крайней мере, если код не часто меняется, состоит в том, чтобы получить сочетание кода, хранящегося в базе данных, и включенного кода:

  • при необходимости получить код из БД и сохранить его в файле на диске
  • включить этот файл
  • так как код теперь находится в файле на диске, кэш кода операции сможет его кэшировать, что лучше для производительности.
  • и вам не нужно будет делать запрос к БД каждый раз, когда вам нужно выполнить код.

Я работал с программным обеспечением, использующим это решение (файл на диске был не более чем кешем кода, хранящимся в БД), и я работал не так уж плохо — намного лучше, чем выполнять множество запросов к БД для каждой страницы, так или иначе...

Некоторые не очень хорошие вещи, как следствие:

  • you have to fetch the code from the DB to put it in the file "when necessary"
    • this could mean re-generating the temporary file once every hour, or deleting it when the entry in DB is modified ? Do you have a way to identify when this happens ?
  • you also have to change your code, to use the temporary file, or re-generate it if necessary
    • if you have several places to modifiy, this could mean some work

Кстати, осмелюсь ли я сказать что-то вроде "eval is evil"?

person Pascal MARTIN    schedule 26.07.2009
comment
Извините, но ваше предложение совершенно неуместно. Я пишу какое-то программное обеспечение, и я сделал так, чтобы оно использовало только файлы. Я хотел предоставить альтернативу с использованием базы данных, когда запись файлов невозможна, поэтому я остановился на этом. Однако мы говорим только об одном запросе к базе данных и одном операторе eval. И скорость не так уж и плоха: 1:2 для включения из файла : включение из базы данных, 10:8 для включения из базы данных : eval из базы данных. В любом случае, мне интересно, кэшируется ли включение из базы данных... О, и причина, по которой я пришел сюда, заключалась в том, что отсутствующая точка с запятой сводила меня с ума - person Jasper; 26.07.2009
comment
Хорошо, тогда ; извините за это ^^ - person Pascal MARTIN; 26.07.2009
comment
Как вы справляетесь с динамически генерируемым кодом, если этот файл может измениться на диске, и у вас есть несколько процессов, которые могут обращаться к файлу одновременно? Вы должны как-то гарантировать, что файл не будет прочитан процессом, когда другой процесс его обновляет... Как вы думаете, flock может решить проблему? Тогда что еще, использование eval кажется более безопасным в этом случае... Как вы думаете? - person tonix; 10.04.2018

Как отметил @bwoebi в этот ответ на мой вопрос, замена eval не учитывает контекст пути к файлу включенного файла . В качестве тестового примера:

Baz.php:

<?php return __FILE__;

Foo.php:

<?php
echo eval('?>' . file_get_contents('Baz.php',  FILE_USE_INCLUDE_PATH)) . "\n";
echo (include 'Baz.php') . "\n";

Результат выполнения php Foo.php:

$ php Foo.php 
/path/to/file/Foo.php(2) : eval()'d code
/path/to/file/Baz.php

Я не знаю, как изменить константу __FILE__ и друзей во время выполнения, поэтому я не думаю, что есть какой-то общий способ определить include в терминах eval.

person jameshfisher    schedule 15.05.2014

Только вариант eval('?>' . file_get_contents('external.php')); является правильной заменой для include.

См. тесты:

<?php
$includes = array(
    'some text',
    '<?php print "some text"; ?>',
    '<?php print "some text";',
    'some text<?php',
    'some text<?php ',
    'some text<?php;',
    'some text<?php ?>',
    '<?php ?>some text',
);

$tempFile = tempnam('/tmp', 'test_');

print "\r\n" . "Include:" . "\r\n";
foreach ($includes as $include)
{
    file_put_contents($tempFile, $include);
    var_dump(include $tempFile);
}

unlink($tempFile);

print "\r\n" . "Eval 1:" . "\r\n";
foreach ($includes as $include)
    var_dump(eval('?>' . $include . '<?php '));

print "\r\n" . "Eval 2:" . "\r\n";
foreach ($includes as $include)
    var_dump(eval('?>' . $include));

print "\r\n" . "Eval 3:" . "\r\n";
foreach ($includes as $include)
    var_dump(eval('?>' . $include . '<?php;'));

Вывод:

Include:
some textint(1)
some textint(1)
some textint(1)
some text<?phpint(1)
some textint(1)
some text<?php;int(1)
some textint(1)
some textint(1)

Eval 1:
some textNULL
some textNULL
bool(false)
some text<?phpNULL
bool(false)
some text<?php;NULL
some textNULL
some textNULL

Eval 2:
some textNULL
some textNULL
some textNULL
some text<?phpNULL
some textNULL
some text<?php;NULL
some textNULL
some textNULL

Eval 3:
some text<?php;NULL
some text<?php;NULL
bool(false)
some text<?php<?php;NULL
bool(false)
some text<?php;<?php;NULL
some text<?php;NULL
some text<?php;NULL
person Mihail H.    schedule 07.10.2013

Некоторые мысли о решениях выше:

Временный файл

Не надо. Это очень плохо для производительности, просто не делайте этого. Это не только сводит ваш кеш кода операции с ума (попадание в кеш никогда не происходит + он каждый раз пытается кешировать его снова), но также доставляет вам головную боль, связанную с блокировкой файловой системы при высоких (даже умеренных) нагрузках, так как вам приходится записывать файл и Apache/PHP должен его прочитать.

Простая оценка()

Приемлемо в редких случаях; не делайте этого слишком часто. Действительно, он не кэшируется (плохой кеш кода операции просто не знает, что это та же строка, что и раньше); в то же время, если ваш код меняется каждый раз, eval НАМНОГО ЛУЧШЕ, чем include(), в основном потому, что include() заполняет кэш кода операции при каждом вызове. Как и в случае с временным файлом. Это ужасно (~ в 4 раза медленнее).

Оценка в памяти()

На самом деле, eval работает очень быстро, когда ваш скрипт уже находится в строке; в большинстве случаев это операция с диском, которая возвращает его обратно, теперь, конечно, это зависит от того, что вы делаете в сценарии, но в моем случае с очень маленьким сценарием это было примерно в 400 раз быстрее. (У вас есть memcached? Просто мысли вслух) То, что include() не может сделать, так это вычислить одно и то же дважды без операции с файлом, и это очень важно. Если вы используете его для постоянно меняющихся, небольших строк, генерируемых памятью, очевидно, что выбрать eval - во много-много раз быстрее загружать один раз + eval снова и снова, чем повторяющийся include().

TL;DR

  • Один и тот же код, один раз для каждого запроса: include
  • Тот же код, несколько вызовов на запрос: eval
  • Вариативный код: eval
person dkellner    schedule 21.09.2018

Это позволяет вам включить файл, предполагая, что файловые оболочки для включения включены в PHP:

function stringToTempFileName($str)
{
    if (version_compare(PHP_VERSION, '5.1.0', '>=') && strlen($str < (1024 * 512))) {
        $file = 'data://text/plain;base64,' . base64_encode($str);
    } else {
        $file = Utils::tempFileName();
        file_put_contents($file, $str);
    }
    return $file;
}

... Затем включите этот «файл». Да, это также отключит кеши кода операции, но сделает это «eval» таким же, как и включение в отношении поведения.

person Jaimie Sirovich    schedule 13.04.2010

вот мой подход.

он создает временный файл php и включает его.

но таким образом, если код, который вы хотите запустить в этой функции, имеет ошибки, программа завершает работу перед удалением временного файла

поэтому я делаю процедуру автоочистки в функции. таким образом, он очищает старые временные файлы по тайм-ауту каждый раз, когда запускается функция. вы можете установить тайм-аут или отключить его в настройках при запуске функции

я также добавил опцию игнорирования ошибок для решения неудаленных временных файлов. если ошибки проигнорированы, программа продолжит работу и удалит временный файл.

также некоторые проекты должны отключить автоочистку, потому что она сканирует весь каталог при каждом запуске. это может повредить производительности диска.

function eval2($c) {
    $auto_clean_old_temporary_files=false; //checks old temporary eval2 files for this spesific temporary file names generated by settings below
    $ignore_all_errors=true; //if you ignore errors you can remove temporary files even there is an error 

    $tempfiledirectory=''; //temporary file directory
    $tempfileheader='eval2_'; // temporary file header 
    $tempfiletimeseperator='__'; // temporary file seperator for time
    $tempfileremovetimeout=200; // temp file cleaning time in seconds

    if ($auto_clean_old_temporary_files===true) {

        $sd=scandir('.'); //scaning for old temporary files 
        foreach ($sd as $sf) {
            if (strlen($sf)>(32+strlen($tempfileheader)+strlen($tempfiletimeseperator)+3)) { // if filename long enough
                $t1=substr($sf,(32+strlen($tempfileheader)),strlen($tempfiletimeseperator)); //searching time seperator
                $t2=substr($sf,0,strlen($tempfileheader)); //searching file header

                if ($t1==$tempfiletimeseperator && $t2==$tempfileheader) { //checking for timeseperator and file name header 
                    $ef=explode('.',$sf); 
                    unset($ef[count($ef)]);//removing file extension 
                    $nsf=implode('.',$ef);//joining file name without extension

                    $ef=explode($tempfiletimeseperator,$nsf);
                    $tm=(int)end($ef); //getting time from filename

                    $tmf=time()-$tm;
                    if ($tmf>$tempfileremovetimeout && $tmf<123456 && $tmf>0) { // if time passed more then timeout and difference with real time is logical 
                        unlink($sf); // finally removing temporary file
                    }
                }
            }
        }
    }

    $n=$tempfiledirectory.$tempfileheader . md5(microtime().rand(0,5000)). $tempfiletimeseperator . time() .'.php'; //creating spesific temporary file name
    $c='<?php' . PHP_EOL . $c . PHP_EOL; //generating php content
    file_put_contents($n,$c); //creating temporary file

    if ($ignore_all_errors===true) { // including temporary file by your choise 
        $s=@include($n);
    }else{
        $s=include($n);
    }

    return $s;  

}
person minaretsbayonet    schedule 02.11.2017