PHP: Equivalent of include using eval

2019-01-14 22:39发布

If the code is the same, there appears to be a difference between:

include 'external.php';

and

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

What is the difference? Does anybody know?


I know the two are different because the include works fine and the eval gives an error. When I originally asked the question, I wasn't sure whether it gave an error on all code or just on mine (and because the code was evaled, it was very hard to find out what the error meant). However, after having researched the answer, it turns out that whether or not you get the error does not depend on the code in the external.php, but does depend on your php settings (short_open_tag to be precise).

标签: php include eval
8条回答
仙女界的扛把子
2楼-- · 2019-01-14 22:53

As noted by @bwoebi in this answer to my question, the eval substitution does not respect the file path context of the included file. As a test case:

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";

Result of executing php Foo.php:

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

I don't know of any way to change the __FILE__ constant and friends at runtime, so I do not think there is any general way to define include in terms of eval.

查看更多
叼着烟拽天下
3楼-- · 2019-01-14 22:56

After some more research I found out what was wrong myself. The problem is in the fact that <?php is a "short opening tag" and so will only work if short_open_tag is set to 1 (in php.ini or something to the same effect). The correct full tag is <?php, which has a space after the second p.

As such the proper equivalent of the include is:

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

Alternatively, you can leave the opening tag out all together (as noted in the comments below):

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

My original solution was to add a semicolon, which also works, but looks a lot less clean if you ask me:

eval('?>' . file_get_contents('external.php') . '<?php;');
查看更多
爱情/是我丢掉的垃圾
4楼-- · 2019-01-14 22:56

Only eval('?>' . file_get_contents('external.php')); variant is correct replacement for include.

See tests:

<?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;'));

Output:

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
查看更多
Fickle 薄情
5楼-- · 2019-01-14 23:05

If you are using a webserver on which you have installed an opcode cache, like APC, eval will not be the "best solution" : eval'd code is not store in the opcode cache, if I remember correctly (and another answer said the same thing, btw).

A solution you could use, at least if the code is not often changed, is get a mix of code stored in database and included code :

  • when necessary, fetch the code from DB, and store it in a file on disk
  • include that file
  • as the code is now in a file, on disk, opcode cache will be able to cache it -- which is better for performances
  • and you will not need to make a request to the DB each time you have to execute the code.

I've worked with software that uses this solution (the on-disk file being no more than a cache of the code stored in DB), and I worked not too bad -- way better that doing loads of DB requests of each page, anyway...

Some not so good things, as a consequence :

  • 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

BTW : would I dare saying something like "eval is evil" ?

查看更多
6楼-- · 2019-01-14 23:05

Some thoughts about the solutions above:

Temporary file

Don't. It's very bad for performance, just don't do it. Not only does it drive your opcode cache totally crazy (cache hit never happens + it tries to cache it again every time) but also gives you the headache of filesystem locking under high (even moderate) loads, as you have to write the file and Apache/PHP has to read it.

Simple eval()

Acceptable in rare cases; don't do it too often. Indeed it's not cached (poor opcode cache just doesn't know it's the same string as before); at the same time, if your code is changing each time, eval is A LOT BETTER than include(), mostly because include() fills up the opcode cache on each call. Just like the tempfile case. It's horrible (~4x slower).

In-memory eval()

Actually, eval is very fast when your script is already in the string; most of the time it's the disk operation that pulls it back, now surely this depends on what you do in the script but in my very-small-script case, it was ~400 times faster. (Do you have memcached? Just thinking loud) So what include() can't do is evaluate the same thing twice without file operation, and this is very important. If you use it for ever-changing, small, memory-generated strings, obviously it's eval to choose - it's many-many times faster to load once + eval again and again than an iterated include().

TL;DR

  • Same code, once per request: include
  • Same code, several calls per request: eval
  • Varying code: eval
查看更多
Emotional °昔
7楼-- · 2019-01-14 23:07

This lets you include a file assuming file wrappers for includes is on in 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;
}

... Then include that 'file.' Yes, this will also disable opcode caches, but it makes this 'eval' the same as an include with respect to behavior.

查看更多
登录 后发表回答