PHP: Equivalent of include using eval

后端 未结 8 1230
清酒与你
清酒与你 2020-12-06 05:56

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

include \'external.php\';

and

eval(\'?>\' . file_get_conten

相关标签:
8条回答
  • 2020-12-06 05:59

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

    0 讨论(0)
  • 2020-12-06 06:04

    AFAIK you can't take advantage of php accelerators if you use eval().

    0 讨论(0)
  • 2020-12-06 06:09

    here is my approach.

    it creates temporary php file and includes it.

    but this way if code you want to run on this function has errors program exits before removing temporary file

    so i make an autoclean procedure in function. this way it cleans old temporary files by an timeout everytime function runs. you can set timeout or disable it from options at start of function

    i also added ignore error option for solving non removed temporary files. if errors ignored, program will continue and remove temporary file.

    also some projects have to disable autoclean because it scans whole directory everytime it runs. it could hurt disk performance.

    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;  
    
    }
    
    0 讨论(0)
  • 2020-12-06 06:12

    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
    0 讨论(0)
  • 2020-12-06 06:13

    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.

    0 讨论(0)
  • 2020-12-06 06:13

    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
    
    0 讨论(0)
提交回复
热议问题