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).
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;');
AFAIK you can't take advantage of php accelerators if you use eval().
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" ?
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.
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.
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
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
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;
}
Related
I am collecting a series of php files and testing to see if a single function returns valid output. To facilitate the process, all their functions are named identically. So then I can run:
foreach($fileList as $file) {
require($file);
echo $testFunction();
}
The problem is that php throws an error 'Cannot redeclare function' since the second file's function is named the same as the first. What I want to do is 'undeclare' a function after I test its output but I know this isn't possible and I'm trying to handle this procedurally. unlink($file) does not remove the instance of the function, unfortunately. Is there a simple way to handle this without using an OOP approach?
UPDATE #1
Using exec() instead of shell_exec() allows me to check err status (which is #2). CHMOD was necessary as user/group prevented execution (security settings on this offline server to be updated once the script is functioning). At this point, it does not echo anything since shell_exec() is returning an error (at least I think so since the output from shell_exec is empty and since exec is returning error #2). Here is an updated test:
$fileList = array('test.php');
foreach($fileList as $file) {
// load code from the current file into a $code variable,
// and append a call to the function held in the $testFunction variable
$code = file_get_contents($file) . "\n" . 'testFunction();';
// save this to a temporary file
file_put_contents('test-file.php', $code);
// execute the test file in a separate php process,
// storing the output in the $output variable for examination
//*************** */
$output=null;
$retval=null;
$absPath = realpath('test-file.php');
chmod($absPath,0777);
echo $absPath;
exec($absPath, $output, $retval);
echo "Returned with status $retval and output:\n";
print_r($output);
}
UPDATE #2
While you can't undeclare a function, you can repeatedly assign different functions to the same var. For example:
$listOfFunctionNames = array('function1', 'function2', 'function3);
foreach($listOfFunctionNames as $func) {
$funxion = $func;
$funxion();
}
You can execute the files in another process, for example (assuming $testFunction is defined in the files), you could do something like this (assuming you are running on Linux):
foreach($fileList as $file) {
// load code from the current file into a $code variable,
// and append a call to the function held in the $testFunction variable
$code = file_get_contents($file) . "\n" . '$testFunction();';
// save this to a temporary file
file_put_contents('/tmp/test-file.php', $code);
// execute the test file in a separate php process,
// storing the output in the $output variable for examination
$output = shell_exec('php /tmp/test-file.php');
// examine output as you wish
}
unlink('/tmp/test-file.php');
EDIT:
Since testFunction does not echo, and instead returns the output to be examined, we can simply modify the test file to echo testFunction();.
$code = file_get_contents($file) . "\n" . 'echo testFunction();'; // <- NOTE: the semi-colon after testFunction();
I noticed my original answer was lacking a semi-colon in the test file, which is probably where the error was coming from. What you can do to ensure it's correct is have this script generate the first test file and terminate early. You can then manually inspect the file for correctness and also use PHP to ensure it's parse-able, from the command line:
php -l /tmp/test-file.php
Note also there are more sophisticated ways you could check correctness of each test file, however I am trying to keep the answer concise, as that is starting to stray into a separate question.
Is there any appreciable difference, in terms of speed on a low-traffic website, between the following snippets of code?
$html = file_get_contents('cache/foo.html');
if ($html) {
echo $html;
exit;
}
Or this:
$file = 'cache/foo.html';
if (file_exists($file)) {
echo file_get_contents($file);
exit;
}
In the first snippet, there's a single call to file_get_contents() whereas in the second there's also a call to file_exists(). The page does involve database access - and this caching would avoid that entirely.
It will be unnoticeably slower on a low-traffic website; but there is no reason to perform that check anyway if you're going to get the contents if it exists, since file_get_contents() already performs that check behind-the-scenes, returning false if the file doesn't exist.
You can even put the call to file_get_contents() directly inside the condition:
if ($html = file_get_contents('cache/foo.html')) {
echo $html;
exit;
}
The runtime differences are so minimal for both variants that it does not matter in practice.
The first variant is slightly faster if the file exists. The second variant is faster if the file does not exist.
Both solutions do not have the best performance because the entire HTML is first loaded into memory before the output is done with echo. Better is:
$ok = #readfile ('cache/foo.html');
With readfile the file is output directly or without detours. The # operator suppresses the warning if the file does not exist.
$ok contains the number of bytes output if the output was successful and false if the file does not exist.
For debugging purposes, when working on PHP projects with many file / many include (example: Wordpress code), I would sometimes be interested in seeing the "unwrapped" code, and to amalgamate / flatten ("flatten" is the terminology used in Photoshop-like tools when you merge many layers into one layer) all files into one big PHP file.
How to do an amalgamation of multiple PHP files?
Example:
$ php index.php --amalgamation
would take these files as input:
vars.php
<?php
$color = 'green';
$fruit = 'apple';
?>
index.php
<?php
include 'vars.php';
echo "A $color $fruit";
?>
and produce this amalgamated output:
<?php
$color = 'green';
$fruit = 'apple';
echo "A $color $fruit";
?>
(it should work also with many files, e.g. if index.php includes vars.php which itself includes abc.php).
We can write an amalgamation/bundling script that fetches a given file's contents and matches any instances of include|require, and then fetches any referred files' contents, and substitutes the include/require calls with the actual code.
The following is a rudimentary implementation that will work (based on a very limited test on files with nested references) with any number of files that include/require other files.
<?php
// Main file that references further files:
$start = 'test/test.php';
function bundle_files(string $filepath)
{
// Fetch current code
$code = file_get_contents($filepath);
// Set directory for referred files
$dirname = pathinfo($filepath, PATHINFO_DIRNAME);
// Match and substitute include/require(_once) with code:
$rx = '~((include|require)(_once)?)\s+[\'"](?<path>[^\'"]+)[\'"];~';
$code = preg_replace_callback($rx, function($m) use ($dirname) {
// Ensure a valid filepath or abort:
if($path = realpath($dirname . '/' . $m['path'])) {
return bundle_files($path);
} else {
die("Filepath Read Fail: {$dirname}/{$m['path']}");
}
}, $code);
// Remove opening PHP tags, note source filepath
$code = preg_replace('~^\s*<\?php\s*~i', "\n// ==== Source: {$filepath} ====\n\n", $code);
// Remove closing PHP tags, if any
$code = preg_replace('~\?>\s*$~', '', $code);
return $code;
}
$bundle = '<?php ' . "\n" . bundle_files($start);
file_put_contents('bundle.php', $bundle);
echo $bundle;
Here we use preg_replace_callback() to match and substitute in order of appearance, with the callback calling the bundling function on each matched filepath and substituting include/require references with the actual code. The function also includes a comment line indicating the source of the included file, which may come in handy if/when you're debugging the compiled bundle file.
Notes/Homework:
You may need to refine the base directory reference routine. (Expect trouble with "incomplete" filepaths that rely on PHP include_path.)
There is no control of _once, code will be re-included. (Easy to remedy by recording included filepaths and skipping recurrences.)
Matching is only made on "path/file.php", ie. unbroken strings inside single/double quotes. Concatenated strings are not matched.
Paths including variables or constants are not understood. Files would have to be evaluated, without side-effects!, for that to be possible.
If you use declare(strict_types=1);, place it atop and eliminate following instances.
There may be other side-effects from the bundling of files that are not addressed here.
The regex does no lookbehind/around to see if your include/require is commented out!
If your code jumps in and out of PHP mode and blurts out HTML, all bets are off
Managing the inclusion of autoloaded classes is beyond this snippet.
Please report any glitches and edge cases. Feel free to develop and (freely) share.
It's been a while since I've touched PHP, and I've been working in C# for a while. I need to do some file reading/writing, but I'm not sure where to start. I've been spoiled by Visual Studio's code-completion and real-time error checking, and it's a bit difficult going over to such a weakly-typed language.
In PHP, what's returned when reading a file, and what needs to be written when writing?
I need to work with the file in hex, but decimal would be fine too. Is there any way to read it in any way but a string?
There is a several ways to read and write files:
You can create a handler by fopen() function.
The other way is just file_get_contents(), this function just returns content. And file_put_contents() just put any data to file.
As example of the handler, here is a logging stuff:
if (!is_writable($this->file) && $name !== self::CORE_LOG)
{
self::getInstance(self::CORE_LOG)->log(sprintf('Couldn\'t write to file %s. Please, check file credentials.', $name));
}
else
{
$this->handler = fopen($this->file, 'a+');
self::$instances[$name] = &$this;
}
...
if ($this->handler)
fwrite($this->handler, '[' . date('r') . '] : ' . $l . "\n");
...
if ($this->handler)
fclose($this->handler);
Here you can read about and Filesystem managment functions
I would need a tool, if it exists or if you can write in under 5 mins (don't want to waste anyone's time).
The tool in question would resolve the includes, requires, include_once and require_once in a PHP script and actually harcode the contents of then, recursively.
This would be needed to ship PHP scripts in one big file that actually use code and resources from multiple included files.
I know that PHP is not the best tool for CLI scripts, but as I'm the most pro-efficient at it, I use it to write some personal or semi-personal tools. I don't want un-helpful answers or comments that tell me to use something else than PHP or learn something else.
The idea of that approach is to be able to have a single file that would represent everything needed to put it in my personal ~/.bin/ directory and let it live there as a completely functional and self-contained script. I know I could set include paths in the script to something that would honor the XDG data directories standards or anything else, but I wanted to try that approach.
Anyway, I ask there because I don't want to re-invent the wheel and all my searches gave nothing, but if I don't have any insight here, I will continue in the way I was going to and actually write a tool that will resolve the includes and requires.
Thanks for any help!
P.S.: I forgot to include examples and don't want to rephrase the message:
Those two files
mainfile.php
<?php
include('resource.php');
include_once('resource.php');
echo returnBeef();
?>
resource.php
<?php
function returnBeef() {
return "The beef!";
}
?>
Would be "compiled" as (comments added for clarity)
<?php
/* begin of include('resource.php'); */?><?php
function returnBeef() {
return "The beef!";
}
?><?php /* end of include('resource.php); */
/*
NOT INCLUDED BECAUSE resource.php WAS PREVIOUSLY INCLUDED
include_once('resource.php');
*/
echo returnBeef();
?>
The script does not have to output explicit comments, but it could be nice if it did.
Thanks again for any help!
EDIT 1
I made a simple modification to the script. As I have begun writing the tool myself, I have seen a mistake I made in the original script. The included file would have, to do the least amount of work, to be enclosed out of start and end tags (<?php ?>)
The resulting script example has been modified in consequence, but it has not been tested.
EDIT 2
The script does not actually need to do heavy-duty parsing of the PHP script as in run-time accurate parsing. Simple includes only have to be treated (like include('file.php');).
I started working on my script and am reading the file to unintelligently parse them to include only when in <?php ?> tags, not in comments nor in strings. A small goal is to also be able to detect dirname(__FILE__)."" in an include directive and actually honor it.
An interesting problem, but one that's not really solvable without detailed runtime knowledge. Conditional includes would be nearly impossible to determine, but if you make enough simple assumptions, perhaps something like this will suffice:
<?php
# import.php
#
# Usage:
# php import.php basefile.php
if (!isset($argv[1])) die("Invalid usage.\n");
$included_files = array();
echo import_file($argv[1])."\n";
function import_file($filename)
{
global $included_files;
# this could fail because the file doesn't exist, or
# if the include path contains a run time variable
# like include($foo);
$file = #file_get_contents($filename);
if ($file === false) die("Error: Unable to open $filename\n");
# trimming whitespace so that the str_replace() at the end of
# this routine works. however, this could cause minor problems if
# the whitespace is considered significant
$file = trim($file);
# look for require/include statements. Note that this looks
# everywhere, including non-PHP portions and comments!
if (!preg_match_all('!((require|include)(_once)?)\\s*\\(?\\s*(\'|")(.+)\\4\\s*\\)?\\s*;!U', $file, $matches, PREG_SET_ORDER | PREG_OFFSET_CAPTURE ))
{
# nothing found, so return file contents as-is
return $file;
}
$new_file = "";
$i = 0;
foreach ($matches as $match)
{
# append the plain PHP code up to the include statement
$new_file .= substr($file, $i, $match[0][1] - $i);
# make sure to honor "include once" files
if ($match[3][0] != "_once" || !isset($included_files[$match[5][0]]))
{
# include this file
$included_files[$match[5][0]] = true;
$new_file .= ' ?>'.import_file($match[5][0]).'<?php ';
}
# update the index pointer to where the next plain chunk starts
$i = $match[0][1] + strlen($match[0][0]);
}
# append the remainder of the source PHP code
$new_file .= substr($file, $i);
return str_replace('?><?php', '', $new_file);
}
?>
There are many caveats to the above code, some of which can be worked around. (I leave that as an exercise for somebody else.) To name a few:
It doesn't honor <?php ?> blocks, so it will match inside HTML
It doesn't know about any PHP rules, so it will match inside PHP comments
It cannot handle variable includes (e.g., include $foo;)
It may introduce scope errors. (e.g., if (true) include('foo.php'); should be if (true) { include('foo.php'); }
It doesn't check for infinitely recursive includes
It doesn't know about include paths
etc...
But even in such a primitive state, it may still be useful.
You could use the built in function get_included_files which returns an array of, you guessed it, all the included files.
Here's an example, you'd drop this code at the END of mainfile.php and then run mainfile.php.
$includes = get_included_files();
$all = "";
foreach($includes as $filename) {
$all .= file_get_contents($filename);
}
file_put_contents('all.php',$all);
A few things to note:
any include which is actually not processed (ie. an include inside a function) will not be dumped into the final file. Only includes which have actually run.
This will also have a around each file but you can have multiple blocks like that with no issues inside a single text file.
This WILL include anything included within another include.
Yes, get_included_files will list the script actually running as well.
If this HAD to be a stand-alone tool instead of a drop in, you could read the inital file in, add this code in as text, then eval the entire thing (possibly dangerous).