For a project we have created a recursive direcory iterator inside a class
the class is as follows
class Helpers {
public static function fs_to_array($directory){
$iritator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($directory), \RecursiveIteratorIterator::CHILD_FIRST);
$array_result = array();
foreach ($iritator as $splFileInfo) {
$fn = $splFileInfo->getFilename();
if ($splFileInfo->isDir()){
if ($fn == '..' || $fn == '.' ){
continue;
}
$rec_path = array($fn => array());
}else{
continue;
}
for ($depth = $iritator->getDepth() - 1; $depth >= 0; $depth--) {
$rec_path = array($iritator->getSubIterator($depth)->current()->getFilename() => $rec_path);
}
$array_result = array_merge_recursive($array_result, $rec_path);
}
return $array_result;
}
}
it takes a directory as an argument and returns the dir structure in the following form
Array
(
[dir3] => Array
(
[dir_in_dir3] => Array
(
)
)
[dir1] => Array
(
[dir_in_dir1] => Array
(
)
)
[dir2] => Array
(
)
)
I would like these to be allphavetically sorted.
How Can i make this with the iterator?
Thanks everybody in advance!
An iterator can't be sorted directly and the directory iterators also have not support for sorting of the underlying data. But you can convert the RecursiveIteratorIterator to an array with iterator_to_array() and then sort the array with usort() and a custom callback function using getPathName() on the elements. You could also use a CallbackFilterIterator() before the conversion to reduce the size of your array.
EDIT example:
$directory = '...';
$it = new CallbackFilterIterator(
new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($directory), RecursiveIteratorIterator::CHILD_FIRST
), function ($entry) {
// filtering unwanted elements to keep array small
$fn = $entry->getFilename();
if (!$entry->isDir()) {
return false;
} else if ($fn == '..' || $fn == '.') {
return false;
} else {
return true;
}
}
);
$array = iterator_to_array($it);
// sorting entries
uasort($array, function ($a, $b) {
return strcmp($a->getPathname(), $b->getPathname());
});
// do whatever you want - can be uses just like the RecursiveIteratorIterator before
foreach ($array as $v) {
var_dump($v->getPathname());
}
Related
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.
Imagine you have a deep array like this:
<?php
$array = ['hello' => ['deep' => 'treasure']];
Then you had an array of the keys to access the string 'treasure'
['hello', 'deep'];
How do you delete the string treasure if you did not know the depth of the array till run time
Edit:
My apologises I've definitely not provided enough information for what I'm looking to achieve
Here is some code I've come up with which does what I need but uses unsafe eval (keep in mind the target destination could be an array so array_walk_recursive won't work)
function iterator_keys($iterator, $outer_data) {
$keys = array();
for ($i = 0; $i < $iterator->getDepth() + 1; $i++) {
$sub_iterator = $iterator->getSubIterator($i);
$keys[$i] = ($i == 0 && is_object($outer_data)
|| $i > 0 && is_object($last_iterator->current())) ?
'->{"' . $sub_iterator->key() . '"}' :
'["' . $sub_iterator->key() . '"]';
$last_iterator = $sub_iterator;
}
return $keys;
}
function recursive_filter($data, callable $selector_function, $iterator = NULL) {
$iterator = $iterator ?? new RecursiveIteratorIterator(
new RecursiveArrayIterator($data),
RecursiveIteratorIterator::CHILD_FIRST
);
foreach ($iterator as $key => $value) {
if ($selector_function($value, $key, $iterator)) {
eval('unset($data' . implode('', iterator_keys($iterator, $data)) . ');');
}
}
return $data;
}
The intention is to have a deep data structure a function that evalutes each node and if it matches a condition then remove it from the data structure in place, if this can be done without eval that would be amazing but so far I think PHP can't programmatically delete something that is more than one level deep
Hello I think what you want is somethings like this
<?php
$array = ['hello' => ['deep' => ['deep1' => 'treasure']]];
$keys = ["hello", "deep", "deep1"];
function remove_recursive(&$array, $keys, $level = 0)
{
if ($level >= count($keys)) {
return $array;
}
if (isset($array[$keys[$level]]) && $level == count($keys) - 1) {
unset($array[$keys[$level]]);
} elseif (isset($array[$keys[$level]])) {
$array[$keys[$level]] = remove_recursive($array[$keys[$level]], $keys, $level + 1);
}
return $array;
}
var_dump(remove_recursive($array, $keys));
Well you can try this really quick and dirty way that uses eval to achieve your goal:
$array = ['hello' => ['deep' => 'treasure']];
$keys = ['hello', 'deep'];
$expression = 'unset($array[\'' . implode('\'][\'', $keys) . '\']);';
eval($expression);
But maybe you can tell us more about your development and we can help you reorganize it somehow to avoid this problem at all.
This will set the target array element to null. Optionally you could use '':
$array = ['hello' => ['deep' => 'treasure']];
$path = ['hello', 'deep'];
$temp = &$array;
foreach($path as $key) {
$temp =& $temp[$key];
}
$temp = null;
print_r($array);
Yields:
Array
(
[hello] => Array
(
[deep] =>
)
)
I am using a custom method to return a query as an array.
This is being used to check if a discount code posted is in the DB.
The array ends up as example:
Array
(
[0] => stdClass Object
(
[code] => SS2015
)
[1] => stdClass Object
(
[code] => SS2016
)
)
So when I am trying to do:
if ( ! in_array($discount_code, $valid_codes)) {
}
Its not working. Is there a way I can still use the function for query to array I am using and check if its in the array?
No issues, I can make a plain array of the codes but just wanted to keep things consistent.
Read about json_encode (serialize data to json) and json_decode (return associative array from serialized json, if secondary param is true). Also array_column gets values by field name. so we have array of values in 1 dimensional array, then let's check with in_array.
function isInCodes($code, $codes) {
$codes = json_encode($codes); // serialize array of objects to json
$codes = json_decode($codes, true); // unserialize json to associative array
$codes = array_column($codes, 'code'); // build 1 dimensional array of code fields
return in_array($code, $codes); // check if exists
}
if(!isInCodes($discount_code, $valid_codes)) {
// do something
}
Use array_filter() to identify the objects having property code equal with $discount_code:
$in_array = array_filter(
$valid_codes,
function ($item) use ($discount_code) {
return $item->code == $discount_code;
}
);
if (! count($in_array)) {
// $discount_code is not in $valid_codes
}
If you need to do the same check many times, in different files, you can convert the above code snippet to a function:
function code_in_array($code, array $array)
{
return count(
array_filter(
$array,
function ($item) use ($code) {
return $item->code == $code;
}
)
) != 0;
}
if (! code_in_array($discount_code, $valid_codes)) {
// ...
}
try this
function in_array_r($needle, $haystack, $strict = false) {
foreach ($haystack as $item) {
if (($strict ? $item === $needle : $item == $needle) || (is_array($item) && in_array_r($needle, $item, $strict))) {
return true;
}
}
return false;
}
then
echo in_array_r("SS2015", $array) ? 'found' : 'not found';
why not solve it as a school task - fast and easy:
for($i = 0; $i < count($valid_codes); $i++) if ($valid_codes[$]->code == $discount_code) break;
if ( ! ($i < count($valid_codes))) { // not in array
}
I'm trying to write a recursive array iterator function in which the function will return a result set of all sets that are specified by '$needle'. Where $needle = key
Here is my function:
function recursive($needle, $array, $holder = array()) {
foreach ($array as $key => $value) {
if (gettype($value) == 'array') {
if ($key != $needle) {
recursive($needle, $value);
} elseif ($key == $needle) {
if (!empty($value)) {
array_push($holder, $value);
}
}
}
}
return $holder;
}
But I'm not getting all the results back and instead get a few empty results, if I don't specify the !empty($value), although the input array does not have any empty sets. What am I doing wrong?
You don't need to reinvent the wheel since PHP has standard Recursive Iterator API:
//$array is your multi-dimensional array
$result = [];
$search = 'foo';
$iterator = new RecursiveIteratorIterator(
new RecursiveArrayIterator(
$array,
RecursiveArrayIterator::CHILD_ARRAYS_ONLY
)
);
foreach($iterator as $key=>$value)
{
if($search==$key && $value!=='')
{
$result[] = $value;
}
}
-note, that, since you're searching for value by key - in common case $value will hold entire subsection.
If you want to do this in your own recursive function, here's one:
function recursive($needle, $array, $holder = [])
{
$holder = [];
foreach($array as $key=>$value)
{
if($key===$needle && $value!=='')
{
$holder = array_merge($holder, [$value]);
}
if(is_array($value))
{
$holder = array_merge($holder, recursive($needle, $value, $holder));
}
}
return $holder;
}
More fine-grained control is perhaps possible with true (tm) recursive array traversal via RecursiveIterator interface and some key filters and array conversion functions:
$needle = '0';
$array = [[1]];
$it = new KeyFilter(
new RecursiveIteratorIterator(
new MyRecursiveArrayIterator($array)
, RecursiveIteratorIterator::SELF_FIRST
)
, $needle
);
$result = iterator_to_array($it, FALSE);
var_dump($result);
Providing an exemplary result as:
array(2) {
[0] =>
array(1) {
[0] =>
int(1)
}
[1] =>
int(1)
}
Full code example (Demo):
<?php
/**
* #link http://stackoverflow.com/q/19709410/367456
*/
Class MyRecursiveArrayIterator extends ArrayIterator implements RecursiveIterator
{
public function hasChildren()
{
$current = $this->current();
return is_array($current) && count($current);
}
public function getChildren()
{
return new self($this->current());
}
}
class KeyFilter extends RegexIterator
{
public function __construct(Iterator $iterator, $key)
{
parent::__construct(
$iterator, '/' . preg_quote($key) . '/', NULL, RegexIterator::USE_KEY
);
}
}
$needle = '0';
$array = [[1]];
$it = new KeyFilter(
new RecursiveIteratorIterator(
new MyRecursiveArrayIterator($array)
, RecursiveIteratorIterator::SELF_FIRST
)
, $needle
);
$result = iterator_to_array($it, FALSE);
var_dump($result);
A tiny modification of your construction:
$holder = recursive($needle, $value, $holder);
Ay?
Is it possible in PHP to extract values from an array with a particular key path and return an array of those values? I'll explain with an example:
$user =
array (
array(
'id' => 1,
'email' =>'asd#example.com',
'project' => array ('project_id' => 222, 'project_name' => 'design')
),
array(
'id' => 2,
'email' =>'asd2#example.com',
'project' => array ('project_id' => 333, 'project_name' => 'design')
)
);
/** I have to write a function something like: */
$projectIds = extractValuesWithKey($user, array('project', 'project_id'));
print_r($projectIds);
Output:
Array(
[0] => 222,
[1] => 333
)
I would have gone for a different approach (not that there's anything wrong with the array-function-based answers) by using a recursive iterator to flatten the array which makes the key-path comparison fairly simple.
function extractValuesWithKey($array, $keys) {
$iterator = new RecursiveIteratorIterator(new RecursiveArrayIterator($array));
$keys_count = count($keys);
// No point going deeper than we have to!
$iterator->setMaxDepth($keys_count);
$result = array();
foreach ($iterator as $value) {
// Skip any level that can never match our keys
if ($iterator->getDepth() !== $keys_count) {
continue;
}
// Build key path to current item for comparison
$key_path = array();
for ($depth = 1; $depth <= $keys_count; $depth++) {
$key_path[] = $iterator->getSubIterator($depth)->key();
}
// If key paths match, add to results
if ($key_path === $keys) {
$result[] = $value;
}
}
return $result;
}
To make the whole thing more useful, you could even wrap the code into a custom FilterIterator rather than a basic function, but I guess that's probably a different question entirely.
Well, that's easier than you think.
function extractValuesWithKey($array, $parts) {
$return = array();
$rawParts = $parts;
foreach ($array as $value) {
$tmp = $value;
$found = true;
foreach ($parts as $key) {
if (!is_array($tmp) || !isset($tmp[$key])) {
$found = false;
continue;
} else {
$tmp = $tmp[$key];
}
}
if ($found) {
$return[] = $tmp;
}
}
return $return;
}
If the 'key path' isn't dynamic, you can do a one-liner with array_map:
$projectIds = array_map(function($arr) { return $arr['project']['project_id']; }, $user);
Alternatively, for dynamic paths:
function extractValuesWithKey($users, $path) {
return array_map(function($array) use ($path) {
array_walk($path, function($key) use (&$array) { $array = $array[$key]; });
return $array;
}, $users);
}
The closures/anonymous functions only work with PHP 5.3+, and I've no idea how this would compare performance-wise to a double foreach loop. Note also that there's no error checking to ensure that the path exists.
I also used a similiar function in one of my projects, maybe you find this useful:
function extractValuesWithKey($data, $path) {
if(!count($path)) return false;
$currentPathKey = $path[0];
if(isset($data[$currentPathKey])) {
$value = $data[$currentPathKey];
return is_array($value) ? extractValuesWithKey($value, array_slice($path, 1)) : $value;
}
else {
$tmp = array();
foreach($data as $key => $value) {
if(is_array($value)) $tmp[] = extractValuesWithKey($value, $path);
}
return $tmp;
}
}