I wrote this code a long time ago to get files from a folder structure given in $dir.
$recursiveIterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dir), RecursiveIteratorIterator::CHILD_FIRST);
$ritit = new RegexIterator($recursiveIterator, $filter);
foreach ($ritit as $splFileInfo) {
if(($splFileInfo->getFileName() != ".") && ($splFileInfo->getFileName() != "..")) {
$path = $splFileInfo->isDir()
? array($splFileInfo->getFilename() => array())
: array($splFileInfo->getFilename());
for ($depth = $ritit->getDepth() - 1; $depth >= 0; $depth--) {
$path = array($ritit->getSubIterator($depth)->current()->getFilename() => $path);
}
$return = array_merge_recursive($return, $path);
}
}
And as the title suggests, I want the $return array to have the folders first. I first attempted to correct this with a foreach after the loop, and sort into $folders and $files array, however this wouldnt change the contents inside the folders, if there were mutliple children inside children.
Is there a way to modify the above loop so that all folders appear first in the array and files after? Including children and children's children?
I don't think that you can modify the loop to get the output array the way you want it. Instead, I'd rather use recursive sorting function to sort the array after the loop.
First, create function that defines the logic for sorting elements. In your case, you want the array-type elements to be the first elements in a tier, so the sorting function could look like this:
function dirFirstSorting($a, $b)
{
if (is_array($a) && is_string($b)) {
return -1;
} elseif (is_string($a) && is_array($b)) {
return 1;
} else {
return 0;
}
}
Then, create a function that recursively sorts elements in array:
function sortFilesListing(&$array)
{
if (is_array($array)) {
uasort($array, "dirFirstSorting");
array_walk($array, "sortFilesListing");
}
}
All you need to do now, is to call sortFilesListing function with $return array provided:
sortFilesListing($return);
The $return array elements should be now sorted accordingly.
Related
I am trying to create a function that removes the oldest files based on date, for a max of 30 files. I grab all the files in the dir. If there are more than 30, they get sorted by date. Then the oldest gets deleted.
public function cleanUpFolder($path){
$files = [];
try {
$dir = new DirectoryIterator($path);
foreach ($dir as $fileinfo) {
if (!$fileinfo->isDot()) {
$files[] = $fileinfo;
// in here i can call any valid method like getPathname()
}
}
$fileCount = count($files);
if($fileCount > self::MAX_BACKUPS){
// sort with the youngest file first
usort($files, function($a, $b) {
// in here, i can call functions like getMTime()
// and even getPath()
// but getPathname or getFileName return false or ""
return $a->getMTime() < $b->getMTime();
});
for($i = $fileCount - 1; $i > 30; $i--){
unlink($files[$i]->getPathname());
}
}
return true;
}
catch (Exception $e){
return false;
}
}
What is working
Getting the files
What is not working
Sort? I cannot tell if the sort works
Calling some methods on the DirectoryIterator while looping through the $files array
It seems like putting the $fileInfo into an array, most function calls no longer work..
I found that the $files array's values got messed up outside of the foreach loop. A var_dump of the array showed all the values were empty, which is odd. Not really a solution, but a workaround I found from this question:
$files = array();
$dir = new DirectoryIterator($path);
foreach ($dir as $fileinfo) {
if (!$fileinfo->isDot()) {
$files[] = array(
"pathname" => $fileinfo->getPathname(),
"modified" => $fileinfo->getMTime()
);
}
}
and then your usort becomes:
usort($files, function($a, $b) {
return $a['modified'] < $b['modified'];
});
and your unlink becomes:
for($i = $fileCount - 1; $i > 30; $i--){
unlink($files['pathname']);
}
=== EDIT ===
I'm not expert in PHP so this could be wrong, but the top comment of FilesystemIterator could be a clue as to why the methods are returning empty.
When you iteterate using DirectoryIterator each "value" returned is
the same DirectoryIterator object. The internal state is changed so
when you call isDir(), getPathname(), etc the correct information is
returned. If you were to ask for a key when iterating you will get an
integer index value.
FilesystemIterator (and RecursiveDirectoryIterator) on the other hand
returns a new, different SplFileInfo object for each iteration step.
The key is the full pathname of the file. This is by default. You can
change what is returned for the key or value using the "flags"
arguement to the constructor.
Hello I am trying to make the following function iterative. It browses threw all directories and gives me all files in there.
function getFilesFromDirectory($directory, &$results = array()){
$files = scandir($directory);
foreach($files as $key => $value){
$path = realpath($directory.DIRECTORY_SEPARATOR.$value);
if(!is_dir($path)) {
$results[] = $path;
} else if($value != "." && $value != "..") {
getFilesFromDirectory($path, $results);
$results[] = $path;
}
}
return $results;
}
I am sure that it is possible to make this function iterative but I really have no approach how I can do this.
Your going to want to use a few PHP base classes to implement this.
Using a RecursiveDirectoryIterator inside of a RecursiveIteratorIterator will allow you to iterate over everything within a directory regardless of how nested.
Its worth noting when looping over the $iterator below each $item is an object of type SplFileinfo. Information on this class can be found here: http://php.net/manual/en/class.splfileinfo.php
<?php
//Iterate over a directory and add the filenames of all found children
function getFilesFromDirectory($directory){
//Return an empty array if the directory could not be found
if(!is_dir($directory)){
return array();
}
$iterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($directory)
);
$found = array();
foreach($iterator as $item){
if(method_exists($item, 'isFile') && $item->isFile()){
//Uncomment the below to exclude dot files
//if(method_exists($item, 'isDot') && $item->isDot()){
// continue;
//}
//Pathname == full file path
$found[] = $item->getPathname();
}
}
return $found;
}
An var_dump of some found files i did using this function as a test:
Hope this helps!
I create a function for remove child dirs on arrays structure. USer for error or mistake, has put on scenario, subdirs child of a parent dir already in config list.
/dir_a/subdir_a
/dir_b/subdir_a/subdir_a
/dir_b/subdir_a/subdir_b
/dir_b/
/dir_c/subdir_a/
...
Code
$local_pre_order = array_unique($local_sync);
asort($local_pre_order);
$local_order = array();
foreach ($local_pre_order as $value)
{
$repeat = false;
foreach ($local_pre_order as $value2)
{
$pos = strpos($value,$value2);
if (($pos !== false ) && ($value != $value2) && ($pos == 0)) {
$repeat = true;
break;
}
}
if (!$repeat) {
$local_order[] = $value;
}
}
Sort OK
/dir_a/subdir_a
/dir_b/
/dir_c/subdir_a/
I think it's a no good programing. Work but not fine. IMHO. Any ideas for best code?
Sorting is the way to go. Note that when sorted, all the child subdirs will be immediatelly after their parents. So it's just a matter of avoiding adding subdirs if the last added dir has a common prefix with it.
PHP isn't my first language, so please don't mind this non-idiomatic code.
$local_sync = [
"/dir_a/subdir_a",
"/dir_b/subdir_a/subdir_a",
"/dir_b/subdir_a/subdir_b",
"/dir_b/",
"/dir_c/subdir_a/"
];
$copy = $local_sync;
asort($copy);
$output = array();
foreach ($copy as $value)
if (strpos($value, end($output)) !== 0)
$output[] = $value;
print_r($output)
It's best not to rely on the array's structure, don't rely on the array being sorted or a comparison between the current path and the previous. What you want to do is use dirname () to get each path's directory name, and search the output array for each path, only adding the path to the output array if it's not already in the output array.
Like so...
$directory_paths = array (
'/dir_a/subdir_a',
'/dir_b/subdir_a/subdir_a',
'/dir_b/subdir_a/subdir_b',
'/dir_b/',
'/dir_c/subdir_a/'
);
$sorted_paths = $directory_paths;
asort ($sorted_paths);
$output = array ();
foreach ($sorted_paths as $key => $path) {
$directory = $path;
$levels = substr_count (trim ($path, '/'), '/') - 1;
for ($i = 0; $i < $levels; $i++) {
$directory = dirname ($directory);
}
if (in_array ($directory, $sorted_paths)) {
$output[$key] = $path;
}
}
ksort ($output);
$output = array_values ($output);
echo print_r ($output, true);
How do I put every file name on the root directory and and all the file names in each sub folder into an array in php?
Use scandir() and you'll find a recursive function for sub-directories as well as tons of other examples to achieve what you want.
I have always preferred DirectoryIterator and RecursiveDirectoryIterator.
Here is an example:
function drawArray(DirectoryIterator $directory)
{
$result=array();
foreach($directory as $object)
{
if($object->isDir()&&!$object->isDot())
{
$result[$object->getFilename()]=drawArray(new DirectoryIterator($object->getPathname()));
}
else if($object->isFile())
{
$result[]=$object->getFilename();
}
}
return $result;
}
$array=drawArray(new DirectoryIterator('/path'));
print_r($array);
Have a look at glob() [and give condolence to your RAM by the way if you really want to skim all your files].
This is how I do it.
/**
Recursively returns all the items in the given directory alphabetically as keys in an array, optionally in descending order.
If a key has an array value it's a directory and its items are in the array in the same manner.
*/
function scandir_tree($directory_name, $sort_order = SCANDIR_SORT_ASCENDING, $_recursed = false)
{
if (!$_recursed || is_dir($directory_name))
{
$items = array_diff(scandir($directory_name, (int) $sort_order), ['.', '..']);
$tree = [];
foreach ($items as $item)
{
$tree[$item] = scandir_tree($directory_name . $item, $sort_order, true);
}
return $tree;
}
return $directory_name;
}
Try this:
$ar=array();
$g=scandir('..');
foreach($g as $x)
{
if(is_dir($x))$ar[$x]=scandir($x);
else $ar[]=$x;
}
print_r($ar);
I have a multidimensional array nested to an unknown/unlimited depth.
I'd like to be able to loop through every element.
I don't want to use, foreach(){foreach(){foreach(){}}} as I don't know the depth.
I'm eventually looking for all nested arrays called "xyz". Has anyone got any suggestions?
I'm eventually looking for all nested arrays called "xyz". Has anyone got any suggestions?
Sure. Building on the suggestions to use some iterators, you can do:
$iterator = new RecursiveIteratorIterator(
new RecursiveArrayIterator($array),
RecursiveIteratorIterator::SELF_FIRST
);
foreach ($iterator as $key => $item) {
if (is_array($item) && $key === 'xyz') {
echo "Found xyz: ";
var_dump($item);
}
}
The important difference between the other answers and this being that the RecursiveIteratorIterator::SELF_FIRST flag is being employed to make the non-leaf (i.e. parent) items (i.e. arrays) visible when iterating.
You could also make use of a ParentIterator around the array iterator, rather than checking for arrays within the loop, to make the latter a little tidier.
Recursion.
Write a function that walks one array; for each element that is also an array, it calls itself; otherwise, when it finds the target string, it returns.
There is a vast difference between unknown and unlimited. However, you can make use of the SPL Iterators instead of using multiple nested foreach loops.
Example:
$array_obj = new RecursiveIteratorIterator(new RecursiveArrayIterator($array));
foreach($array_obj as $key => $value) {
echo $value;
}
Take a look to the RecursiveIteratorIterator interface.
$interface = new RecursiveIteratorIterator( new RecursiveArrayIterator($your_array) );
foreach($interface as $k=>$v) { /* your function*/ }
Using the comments above, I've found the answer:
function findXyz($array){
foreach($array as $foo=>$bar){
if (is_array($bar)){
if ($bar["xyz"]){
echo "<br />The array of xyz has now been found";
print_r($bar['xyz']);
}else{
findXyz($bar);
}
}
}
}
findXyz($myarray);
This loops through all nested arrays and looks for any element who has a sub-array of xyz, as per my original request. array_walk_array and RecursiveIteratorIterator were unable to achieve this.
Have you thought about using array_walk_recursive for this?
Another (slower) approach would be to flatten the array first before performing a search, ie:
$myarray = array('a','b',array(array(array('x'),'y','z')),array(array('p')));
function array_flatten($array,$return)
{
for($x = 0; $x <= count($array); $x++)
{
if(is_array($array[$x]))
{
$return = array_flatten($array[$x],$return);
}
else
{
if($array[$x])
{
$return[] = $array[$x];
}
}
}
return $return;
}
$res = array_flatten($myarray,array());
Or, for a recursive search, see here for an example:
function arrayRecursiveSearch($needle, $haystack, $path=""){
if(!is_array($haystack)){
die("second argument is not array");
}
global $matches;
foreach($haystack as $key=>$value)
{
if(preg_match("/$needle/i", $key)){
$matches[] = array($path . "$key/", "KEY: $key");
}
if(is_array($value)){
$path .= "$key/";
arrayRecursiveSearch($needle, $value, $path);
unset($path);
}else{
if(preg_match("/$needle/i", $value)){
$matches[] = array($path . "$key/", "VALUE: $value");
}
}
}
return $matches;
}
$arr = array("Asia"=>array('rambutan','duku'),
"Australia"=>array('pear','kiwi'),
"Arab"=>array('kurma'));
print_r(arrayRecursiveSearch("ra",$arr));