Protecting variables from "include pollution" in PHP - php

tl;dr: Is there a way to prevent alteration to (essentially lock) variables declared/defined prior to an include() call, by the file being included? Also, somewhat related question.
I'm wondering about what measures can be taken, to avoid variable pollution from included files. For example, given this fancy little function:
/**
* Recursively loads values by include returns into
* arguments of a callback
*
* If $path is a file, only that file will be included.
* If $path is a directory, all files in that directory
* and all sub-directories will be included.
*
* When a file is included, $callback is invoked passing
* the returned value as an argument.
*
* #param string $path
* #param callable $callback
*/
function load_values_recursive($path, $callback){
$paths[] = path($path);
while(!empty($paths)){
$path = array_pop($paths);
if(is_file($path)){
if(true === $callback(include($path))){
break;
}
}
if(is_dir($path)){
foreach(glob($path . '*') as $path){
$paths[] = path($path);
}
}
}
}
I know it's missing some type-checking and other explanations, let's ignore those.
Anyways, this function basically sifts through a bunch of "data" files that merely return values (typically configuration arrays, or routing tables, but whatever) and then invokes the passed callback so that the value can be filtered or sorted or used somehow. For instance:
$values = array();
load_values_recursive('path/to/dir/', function($value) use(&$values){
$values[] = $value;
});
And path/to/dir/ may have several files that follow this template:
return array(
// yay, data!
);
My problem comes when these "configuration" files (or whatever, trying to keep this portable and cross-functional) start to contain even rudimentary logic. There's always the possibility of polluting the variables local to the function. For instance, a configuration file, that for the sake of cleverness does:
return array(
'path_1' => $path = 'some/long/complicated/path/',
'path_2' => $path . 'foo/',
'path_3' => $path . 'bar/',
);
Now, given $path happens to be a visible directory relative to the current, the function is gonna go wonky:
// ...
if(is_file($path)){
if(true === $callback(include($path))){ // path gets reset to
break; // some/long/complicated/path/
}
}
if(is_dir($path)){ // and gets added into the
foreach(glob($path . '*') as $path){ // search tree
$paths[] = path($path);
}
}
// ...
This would likely have bad-at-best results. The only1 solution I can think of, is wrapping the include() call in yet another anonymous function to change scope:
// ...
if(true === call_user_func(function() use($callback, $path){
return $callback($path);
})){
break;
}
// ...
Thus protecting $path (and more importantly, $callback) from causing side effects with each iteration.
I'm wondering if there exists a simpler way to "lock" variables in PHP under such circumstances.
I just wanna go on the record here; I know I could use, for instance, an elseif to alleviate one of the issues specific to this function, however my question is more interested in circumstance-agnostic solutions, a catch-all if you will.

take a look at Giving PHP include()'d files parent variable scope it has a rather unique approach to the problem that can be used here.
it amounts to unsetting all defined vars before the include and then resetting them after.
it certainly isn't elegant, but it'll work.

I've gone with the following solution to include pollution:
$value = call_user_func(function(){
return include(func_get_arg(0));
}, $path);
$path is nowhere to be seen at inclusion, and it seems most elegant. Surely, calling func_get_arg($i) from the included file will yield passed values, but, well...

Related

Copy and rename multiple files with PHP

Is there a way to copy and rename multiple files in php but get their names from an array or a list of variables.
The nearest thing to what I need that I was able to find is this page
Copy & rename a file to the same directory without deleting the original file
but the only thing the script on this page does is creating a second file and it's name is already preset in the script.
I need to be able to copy and create multiple files, like 100-200 and get their names set from an array.
If I have an initial file called "service.jpg"
I would need the file to be copied multiple times with the different names from the array as such :
$imgnames = array('London', 'New-York','Seattle',);
etc.
Getting a final result of 3 separate files called "service-London.jpg", "service-New-York.jpg" and so on.
I'm sure that it should be a pretty simple script, but my knowledge of PHP is really insignificant at the time.
One approach (untested) that you can take is creating a class to duplicate a directory. You mentioned you would need to get the name of the files in a directory and this approach will handle it for you.
It will iterate over an array of names (whatever you pass to it), and copy/rename all of the files inside a directory of your choice. You might want to add some checks in the copy() method (file_exists, etc) but this will definitely get you going and is flexible.
// Instantiate, passing the array of names and the directory you want copied
$c = new CopyDirectory(['London', 'New-York', 'Seattle'], 'location/of/your/directory/');
// Call copy() to copy the directory
$c->copy();
/**
* CopyDirectory will iterate over all the files in a given directory
* copy them, and rename the file by appending a given name
*/
class CopyDirectory
{
private $imageNames; // array
private $directory; // string
/**
* Constructor sets the imageNames and the directory to duplicate
* #param array
* #param string
*/
public function __construct($imageNames, $directory)
{
$this->imageNames = $imageNames;
$this->directory = $directory;
}
/**
* Method to copy all files within a directory
*/
public function copy()
{
// Iterate over your imageNames
foreach ($this->imageNames as $name) {
// Locate all the files in a directory (array_slice is removing the trailing ..)
foreach (array_slice(scandir($this->directory),2) as $file) {
// Generates array of path information
$pathInfo = pathinfo($this->directory . $file);
// Copy the file, renaming with $name appended
copy($this->directory . $file, $this->directory . $pathInfo['filename'] . '-' . $name .'.'. $pathInfo['extension']);
}
}
}
}
You could use a regular expression to build the new filenames, like this:
$fromFolder = 'Images/folder/';
$fromFile = 'service.jpg';
$toFolder = 'Images/folder/';
$imgnames = array('London', 'New-York','Seattle');
foreach ($imgnames as $imgname) {
$newFile = preg_replace("/(\.[^\.]+)$/", "-" . $imgname . "$1", $fromFile);
echo "Copying $fromFile to $newFile";
copy($fromFolder . $fromFile, $toFolder . $newFile);
}
The above will output the following while copying the files:
Copying service.jpg to service-London.jpg
Copying service.jpg to service-New-York.jpg
Copying service.jpg to service-Seattle.jpg
In the above code, set the $fromFolder and $toFolder to your folders, they can be the same folder, if so needed.

php code snippet, return two arrays from one recursive function, my solution

HereĀ“s my recent result for recursive listing of a user directory.
I use the results to build a filemanger (original screenshots).
(source: ddlab.de)
Sorry, the 654321.jpg is uploaded several times to different folders, thats why it looks a bit messy.
(source: ddlab.de)
Therefor I need two separate arrays, one for the directory tree, the other for the files.
Here only showing the php solution, as I am currently still working on javascript for usability. The array keys contain all currently needed infos. The key "tree" is used to get an ID for the folders as well as a CLASS for the files (using jquery, show files which are related to the active folder and hide which are not) a.s.o.
The folder list is an UL/LI, the files section is a sortable table which includes a "show all files"-function, where files are listed completely, sortable as well, with path info.
The function
function build_tree($dir,$deep=0,$tree='/',&$arr_folder=array(),&$arr_files=array()) {
$dir = rtrim($dir,'/').'/'; // not really necessary if 1st function call is clean
$handle = opendir($dir);
while ($file = readdir($handle))
{
if ($file != "." && $file != "..")
{
if (is_dir($dir.$file))
{
$deep++;
$tree_pre = $tree; // remember for reset
$tree = $tree.$file.'/'; // bulids something like "/","/sub1/","/sub1/sub2/"
$arr_folder[$tree] = array('tree'=>$tree,'deep'=>$deep,'file'=>$file);
build_tree($dir.$file,$deep,$tree,$arr_folder,$arr_files); // recursive function call
$tree = $tree_pre; // reset to go to upper levels
$deep--; // reset to go to upper levels
}
else
{
$arr_files[$file.'.'.$tree] = array('tree'=>$tree,'file'=>$file,'filesize'=>filesize($dir.$file),'filemtime'=>filemtime($dir.$file));
}
}
}
closedir($handle);
return array($arr_folder,$arr_files); //cannot return two separate arrays
}
Calling the function
$build_tree = build_tree($udir); // 1st function call, $udir is my user directory
Get the arrays separated
$arr_folder = $build_tree[0]; // separate the two arrays
$arr_files = $build_tree[1]; // separate the two arrays
see results
print_r($arr_folder);
print_r($arr_files);
It works like a charme,
Whoever might need something like this, be lucky with it.
I promise to post the entire code, when finished :-)

PHP skip case sensitivity in file extension

I have rows on my database table which some are not synchronized on the directory.
ex. on my table I have
image.png
but on my directory I have
image.PNG
now, Im having a problem to check if
file_exist
because of the case sensitivity.
I can't choose the option to synchronize my database and my files on the directory manually because it is too many.
May I know how can I used the file_exist that ignores the sensitivity of file types?
For a more general solution, see the comments in the php docs:
General Solution
Which offers this version of the function:
/**
* Alternative to file_exists() that will also return true if a file exists
* with the same name in a different case.
* eg. say there exists a file /path/product.class.php
* file_exists('/path/Product.class.php')
* => false
* similar_file_exists('/path/Product.class.php')
* => true
*/
function similar_file_exists($filename) {
if (file_exists($filename)) {
return true;
}
$dir = dirname($filename);
$files = glob($dir . '/*');
$lcaseFilename = strtolower($filename);
foreach($files as $file) {
if (strtolower($file) == $lcaseFilename) {
return true;
}
}
return false;
}
Create a function that checks both extensions and use it instead of file_exist directly.
As i think, your root cause is due to following--
If your database have "image.png" and in directory "image.PNG" means there is some problem with your insert query.
It must be same in both the places. It better to advice to store file name in lowercase as Linux systems are case sensitive.

Discovering folder tree via depth-first or breadth-first

I had to find the paths to the "deepest" folders in a folder. For this I implemented two algorithms, and one is way faster than the other.
Does anyone know why ? I suppose this has some link with the hard-disk hardware but I'd like to understand.
Here is the fast one :
private function getHostAux($path) {
$matches = array();
$folder = rtrim($path, DIRECTORY_SEPARATOR);
$moreFolders = glob($folder.DIRECTORY_SEPARATOR.'*', GLOB_ONLYDIR);
if (count($moreFolders) == 0) {
$matches[] = $folder;
} else {
foreach ($moreFolders as $fd) {
$arr = $this->getHostAux($fd);
$matches = array_merge($matches, $arr);
}
}
return $matches;
}
And here is the slow-one :
/**
* Breadth-first function using glob
*/
private function getHostAux($path) {
$matches = array();
$folders = array(rtrim($path, DIRECTORY_SEPARATOR));
$i = 0;
while($folder = array_shift($folders)) {
$moreFolders = glob($folder.DIRECTORY_SEPARATOR.'*', GLOB_ONLYDIR);
if (count($moreFolders == 0)) {
$matches[$i] = $folder;
}
$folders = array_merge($folders, $moreFolders);
$i++;
}
return $matches;
}
Thanks !
You haven't provided additional informations that might be crucial for understanding these "timings" which you observed. (I intentionally wrote the quotes since you haven't specified what "slow" and "fast" mean and how exactly did you measure it.)
Assuming that the supplied informations are true and that the speedup for the first method is greater than a couple of percent and you've tested it on directories of various sizes and depth...
First I would like to comment on the supplied answers:
I wouldn't be so sure about your answer. First I think you mean "kernel handles". But this is not true since glob doesn't open handles. How did you come up with this answer?
Both versions have the same total iteration count.
And add something from myself:
I would suspect array_shift() may cause the slowdown because it reindexes the whole array each time you call it.
The order in which you glob may matter depending on the underlying OS and file system.
You have a bug (probably) in your code. You increment $i after every glob and not after adding an element to the $matches array. That causes that the $matches array is sparse which may cause the merging, shifting or even the adding process to be slower. I don't know exactly if that's the case with PHP but I know several languages in which arrays have these properties which are sometimes hard to keep in mind while coding. I would recommend fixing this, timing the code again and seeing if that makes any difference.
I think that your first algorithm with recursion does less iterations than the second one. Try to watch how many iterations each algorithm does using auxilary variables.

Running methods with returned objects

I am brushing up on my non-framework Object Oriented PHP and decided to do a test. Unfortunately, while I understand the concept of calling methods from a class, this particular test is slightly more complicated and I don't know what the terminology is for this particular type of situation is.
Test
Create a PHP class that parses (unknown number of) text files in a folder and allows to extract the total value of amount field from the file and get filenames of parsed files.
File Format:
The files are plain text csv files. Let's assume that the files contain a list of payments changed in the last N days. There are 2 different types of line:
Card payment collected - type = 1, date, order id, amount
Card payment rejected - type = 2, date, order id, reason, amount
Example file:
1,20090313,542,11.99
1,20090313,543,9.99
2,20090312,500,some reason, 2.99
Usage Example:
The usage could be something like this:
$parser = new Parser(...);
$files = $parser->getFiles();
foreach ($files as $file) {
$filename = $file->getFileName();
$amount_collected = $file->getTotalAmount(...);
$amount_rejected = $file->getTotalAmount(...);
}
My question is:
How can you do $file->method() when the class is called parser? I'm guessing you return an object from the getFiles method in the parser class, but how can you run methods with the returned object?
I attempted to Google this, but as I don't know the terminology for this situation I didn't find anything.
Any help is much appreciated, even if it's just what the terminology for this situation is.
The returned object could be of class ParserFile with getFileName and getTotalAmount methods. This approach would be quite close to the Factory pattern, although it would be a good idea to make the getFiles method static, callable without the parser class itself.
class ParserFile {
public function getFilename() { /* whatever */ }
public function getTotalAmount() { /* whatever */ }
}
class Parser {
public static function getFiles() {
// loop through the available files
// and store them in some $arr
$arr[] = new ParserFile('filename1.txt');
$arr[] = new ParserFile('filename2.txt');
return $arr;
}
}
$files = Parser::getFiles();
foreach ($files as $file) {
$filename = $file->getFilename();
$amount_collected = $file->getTotalAmount();
$amount_rejected = $file->getTotalAmount();
}
Although I'm pretty sure this is not the best design. Another approach would be:
$parser = new Parser();
$files = $parser->getFiles();
foreach ($files as $file) {
$filename = $parser->getFilename($file);
$amount_collected = $parser->getTotalAmount($file);
$amount_rejected = $parser->getTotalAmount($file);
}
So you'll get the array of files into your $files but when you want to parse these files you'll ask $parser to do it for you by passing the current $file to its methods.
There's no 100% correct solution I guess, just use what's best for you. If you then encounter problems, profile, benchmark and refactor.
Hope that helped. Cheers :)
P.S. Hope this isn't homework :D

Categories