Flatten recursive folder structure in PHP for mysql dbase - php

Perhaps there's a better method than the PHP route, I am open to ideas.
The problem: I have a folder structure for each user. This folder may contain files or sub-folders. I am only concerned with the folders which have files. I am able to run a query and get a recursive listing on a multidimensional array (from PHP code posted on the web). The issue is that I get a multidimensional array that when flattened only lists the inner most file name, and I need to store the path and file -or- the path+file in my sql dbase. The idea is to allow the user to view his files and delete them using a web interface.
Here is an example result from the PHP recursive function. Inside a folder named "Jimmy", you find:
Array
(
[0] => info.txt
[1] => log.tmp
[2] => README.md
[css] => Array
(
[0] => style.css
)
[images] => Array
(
[0] => flower.gif
)
[3] => index.php
[testDir] => Array
(
[anotherTestDir] => Array
(
[0] => test2.php
)
)
[5] => listing.txt
[temp] => Array
(
)
)
What I'd wold like to see is this:
/jimmy/info.txt
/jimmy/log.tmp
/jimmy/README.md
/jimmy/css/style.css
/jimmy/images/flower.gif
/jimmy/index.php
/jimmy/testDir/anotherTestDir/test2.php
/jimmy/listing.txt
And then I would need to sort the string and add it to mysql dbase. Any help on how to achieve this effect would be much appreciated. Once I have my flatten array i want to add it in one go to the user's "folder" table.

<?php
$d = new RecursiveIteratorIterator(new RecursiveDirectoryIterator('/path/to/dir'));
foreach($d as $file){
if($file->isFile()) echo $d->getSubPathname().PHP_EOL;
}

If you actually read the question, these "files" are not on a local filesystem (or, any filesystem) but in a virtual tree stored in a database, so the Iterator example is not directly useful in this case.
You could however use that example if you write a file stream wrapper for your database structure. But even then you'd be stuck on a tough recursive SQL query (to make your database server 'build' you the strings in a computed field, which is what I came here hoping to find...), or lots of CPU time in PHP collating through the structure to generate the "long name" of each file or folder.
However if you just want to work with the data as input, you could use a loop and some is_numeric() or is_string() logic (though you can't have a directory that is numeric then, which may or may not be an issue)... or detect the leafs by the fact they have no array subchildren and then you know that's a 'file' and the parents are all 'folders' regardless of the array index "data type". For recursing arrays like that I usually use by-reference loops, especially if I will be modifying nodes (or adding elements within the tree such as a 'full_path' field for each node). There are some good examples of this on the PHP doc site for the page about references and dereferencing. Also if your data set will never be large this may work acceptably. In my project it won't, we've already hit some resource limits and it's taking too long to process within each 'hit' to our file manager interface. So I strongly urge making the database do the heavy lifting and string concatenations (and then tell me how you did it!)

Related

Loop through nested arrays in PHP

I have a very complex array that I need to loop through.
Array(
[1] => Array(
[1] => ""
[2] => Array(
[1] => ""
[2] => Array(
[1] => ""
)
)
)
)
I can't use nested loops because this array could contain hundreds of nested arrays. Also, the nested ones could contain nested arrays too.
This array presents comments and replies, Where replies could contain more replies.
Any thoughts?
You could use a \RecursiveArrayIterator, which is part of the PHP SPL, shipped non-optional, with the PHP core.
<?php
$arr = [
'lvl1-A' => [
'lvl2' => [
'lvl3' => 'done'
],
],
'lvl1-B' => 'done',
];
function traverse( \Traversable $it ): void {
while ( $it->valid() ) {
$it->hasChildren()
? print "{$it->key()} => \n" and traverse( $it->getChildren() )
: print "{$it->key()} => {$it->current()}\n";
$it->next();
}
}
$it = new \RecursiveArrayIterator( $arr );
$it->rewind();
traverse( $it );
print 'Done.';
Run and play this example in the REPL here: https://3v4l.org/cGtoi
The code is just meant to verbosely explain what you can expect to see. The Iterator walks each level. How you actually code it is up to you. Keep in mind that filtering or flattening the array (read: transforming it up front) might be another option. You could as well use a generator and emit each level and maybe go with Cooperative Multitasking/ Coroutines as PHP core maintainer nikic explained in his blog post.
ProTip: Monitor your RAM consumption with different variants in case your nested Array really is large and maybe requested often or should deliver results fast.
In case you really need to be fast, consider streaming the result, so you can process the output while you are still working on processing the input array.
A last option might be to split the actual array in chunks (like when you are streaming them), therefore processing smaller parts.
The case is quite complex, as you have to loop, but you can't or don't want to for some reasons:
... that I need to loop through
and
I can't use nested loops because this array could contain hundreds of nested arrays
It means you have to either handle your data differently, as you can pack that huge amount of data to be processed later.
If for some reasons it's not an option, you can consider to:
split somehow this big array into smaller arrays
check how does it work with json_encode and parsing string with str_* functions and regex
Your question contains too many things we can't be sure e.g. what exactly these subarrays contain, can you ignore some parts of them, can you change the code that creates huge array in first place etc.
Assuming on the other hand that you could loop. What could bother you? The memory usage, how long it will take etc.?
You can always use cron to run it daily etc. but the most important is to find the cause why you ended up with huge array in the first place.

PHP API returns an array - need to reuse it in variables

I have a PHP script that dumps data from an API.
The dump is an array
print_r($answer);
outputs
Array ( [success] => 1 [serial] => s001 [url] => http://gooole.com )
I want to have another variable called $url that holds the value url from the array (held in $answer) in PHP.
I'm unfamiliar with this.
check out extract() it will take the keys from an array, and create variable of the same name to store them in. There are a few flags you can pass it, to determine exactly what it does with things like pre-existing variables of the same name.
EDIT: as mentioned in the comments on your question, though, $url = $answer['url']; is probably the simplest way to go.

navigate through huge nested array in PHP

This might be a simple question, but I am dumping an object ($this) which is absolutely huge and I need to get to a specific point in the array
$this->varA->varB->varC->varD->what_I_need
I know that the variable that I need is in there and I can use ctrl+f to find it, but the array is so nested that I don't know how I should get to it in PHP. Any ideas on what the best way is to do this?
Do not hesitate to look at libraries from frameworks.
[CakePHP] made an awesome class which is able to navigate into arrays using a string in dot syntax notation. This library is known as Hash, just look at it.
If you have this :
$var = array(
'Element1' => array(
'First_rule' => true,
'Second_rule' => false,
),
'Element2' => array(
'First_rule' => 'none',
'Other_rule' => 'otherone',
),
);
You can extract datas from this array simply with a string.
You can take only one information from a specific element :
$extracted_other_rule = Hash::extract($var, 'Element2.Other_rule');
Returns :
Array
(
[0] => otherone
)
Or you can even extract all "First_rule" indexes from any element in the array containing it :
$extracted_rules = Hash::extract($var, '{s}.First_rule');
Returns this :
Array
(
[0] => 1
[1] => none
)
If you need to navigate through a huge array with undefined depth, just make a recursive function, transferring a string named "$path" to recursive actions. I made a big function of this kind to parse a whole XML stream into a JSON string with all keys parsed with my own rules, with an array of parameters.

Why does the sort order of multidimensional child arrays revert as soon as foreach loop used for sorting ends?

I have a very strange array sorting related problem in PHP that is driving me completely crazy. I have googled for hours, and still NOTHING indicates that other people have this problem, or that this should happen to begin with, so a solution to this mystery would be GREATLY appreciated!
To describe the problem/question in as few words as possible: When sorting an array based on values inside a multiple levels deeply nested array, using a foreach loop, the resulting array sort order reverts as soon as execution leaves the loop, even though it works fine inside the loop. Why is this, and how do I work around it?
Here is sample code for my problem, which should hopefully be a little more clear than the sentence above:
$top_level_array = array('key_1' => array('sub_array' => array('sub_sub_array_1' => array(1),
'sub_sub_array_2' => array(3),
'sub_sub_array_3' => array(2)
)
)
);
function mycmp($arr_1, $arr_2)
{
if ($arr_1[0] == $arr_2[0])
{
return 0;
}
return ($arr_1[0] < $arr_2[0]) ? -1 : 1;
}
foreach($top_level_array as $current_top_level_member)
{
//This loop will only have one iteration, but never mind that...
print("Inside loop before sort operation:\n\n");
print_r($current_top_level_member['sub_array']);
uasort($current_top_level_member['sub_array'], 'mycmp');
print("\nInside loop after sort operation:\n\n");
print_r($current_top_level_member['sub_array']);
}
print("\nOutside of loop (i.e. after all sort operations finished):\n\n");
print_r($top_level_array);
The output of this is as follows:
Inside loop before sort operation:
Array
(
[sub_sub_array_1] => Array
(
[0] => 1
)
[sub_sub_array_2] => Array
(
[0] => 3
)
[sub_sub_array_3] => Array
(
[0] => 2
)
)
Inside loop after sort operation:
Array
(
[sub_sub_array_1] => Array
(
[0] => 1
)
[sub_sub_array_3] => Array
(
[0] => 2
)
[sub_sub_array_2] => Array
(
[0] => 3
)
)
Outside of loop (i.e. after all sort operations finished):
Array
(
[key_1] => Array
(
[sub_array] => Array
(
[sub_sub_array_1] => Array
(
[0] => 1
)
[sub_sub_array_2] => Array
(
[0] => 3
)
[sub_sub_array_3] => Array
(
[0] => 2
)
)
)
)
As you can see, the sort order is "wrong" (i.e. not ordered by the desired value in the innermost array) before the sort operation inside the loop (as expected), then is becomes "correct" after the sort operation inside the loop (as expected).
So far so good.
But THEN, once we're outside the loop again, all of a sudden the order has reverted to its original state, as if the sort loop didn't execute at all?!?
How come this happens, and how will I ever be able to sort this array in the desired way then?
I was under the impression that neither foreach loops nor the uasort() function operated on separate instances of the items in question (but rather on references, i.e. in place), but the result above seems to indicate otherwise? And if so, how will I ever be able to perform the desired sort operation?
(and WHY doesn't anyone else than me on the entire internet seem to have this problem?)
PS.
Never mind the reason behind the design of the strange array to be sorted in this example, it is of course only a simplified PoC of a real problem in much more complex code.
Your problem is a misunderstanding of how PHP provides your "value" in the foreach construct.
foreach($top_level_array as $current_top_level_member)
The variable $current_top_level_member is a copy of the value in the array, not a reference to inside the $top_level_array. Therefore all your work happens on the copy and is discarded after the loop completes. (Actually it is in the $current_top_level_member variable, but $top_level_array never sees the changes.)
You want a reference instead:
foreach($top_level_array as $key => $value)
{
$current_top_level_member =& $top_level_array[$key];
EDIT:
You can also use the foreach by reference notation (hat tip to air4x) to avoid the extra assignment. Note that if you are working with an array of Objects, they are already passed by reference.
foreach($top_level_array as &$current_top_level_member)
To answer you question as to why PHP defaults to a copy instead of a reference, it's simply because of the rules of the language. Scalar values and arrays are assigned by value, unless the & prefix is used, and objects are always assigned by reference (as of PHP 5). And that is likely due to a general consensus that it's generally better to work with copies of everything expect objects. BUT--it is not slow like you might expect. PHP uses a lazy copy called copy on write, where it is really a read-only reference. On the first write, the copy is made.
PHP uses a lazy-copy mechanism (also called copy-on-write) that does
not actually create a copy of a variable until it is modified.
Source: http://www.thedeveloperday.com/php-lazy-copy/
You can add & before $current_top_level_member and use it as reference to the variable in the original array. Then you would be making changes to the original array.
foreach ($top_level_array as &$current_top_level_member) {

How to access array element "Array ( [#text] =>"

I had an XML that I turned into an array to sort it, now I want to save it as an XML again. This would not be a problem if I didn't have the following: [caseid] => Array ( [#text] => 885470 ...
I need: <caseid> 885470 </caseid>
Writing the DOM has been fine for the fieldname "caseid", but the fieldvalue is an array titled "#text" that I cannot figure out how to parse.
I can't search google for "#" symbols, so searching has been pretty tough.
I was able to access the number in the array by referencing it as a string. Stupid way to do it, but it works; the best solution would be to edit the array to xml conversion, but accessing array elements via strings worked too.
I was previously accessed array elements like so:
print_r($array[caseid][#text]); //<--Does not work with #
print_r($array[caseid]['#text']); //works
Again, not the prettiest, but a viable workaround.

Categories