PHP Recursive Function - Incrementing total values - php

I'm having this problem.
Let's assume that I have a series of folders, inside of these folders, they have have a unlimited supply of sub folders and you can have an unlimited supply of files within this.
Using Recusion, I am trying to get the number of all of the files that exist within the sub directories, as well as this, get the total number of files from the sub sub directly.
I am using the following:
$SubSectionTotalCount = 0;
$SubSectionComplete = 0;
function GetStats($child, $groups, $progress, $resCount, $complete)
{
foreach($child->children as $childElement)
{
if($childElement->resources != null)
{
foreach($childElement->resources->groups as $groupss)
{
if(Check($groupss->id, $groups))
{
if(array_key_exists($childElement->parent_id, $progress))
{
if(array_key_exists($childElement->resources->id, $progress[$childElement->parent_id]['tasks']))
{
$complete++;
}
}
$resCount++;
var_dump($resCount);
}
}
}
GetStats($childElement, $groups, $progress, $resCount, $complete);
}
}
I currently have 4 sections (which therefore resCount should print 4) but instead, I am getting:
int 1
int 2
int 3
int 2
If I don't increment the variables, and just var_dump("getting here") I get the following:
Getting here
Getting here
Getting here
Getting here
So the recursion is working, however I don't understand why incrementing is not producing the desired output?

I'm not sure I'm reading you code correctly, but to me it seems like you're trying to modify a value inside a function, but then the value is not returned outside of it.
Basically you should pass your parameter by reference (using &) instead of by value (which is what you're doing here), or a better option would be to refactor your code so that your function returns a value instead of trying to change one. That seems to be the problem to me.

There are two commonly used methods for recursively walking directories in PHP. Primarily using either glob or RecursiveDirectoryIterator / RecursiveIteratorIterator.
Between the two, the iterator method is a more direct representation of file traversing. Try to get used to using iterators if you can.
Common Recursive Glob Method ( PHP 4+)
function getAllFiles($pattern)
{
$files = glob($pattern, 0);
foreach (glob(dirname($pattern).'/*', GLOB_ONLYDIR|GLOB_NOSORT) as $dir)
$files = array_merge($files, getAllFiles($dir.'/'.basename($pattern), 0));
return $files;
}
$numFiles = count( getAllFiles('*') );
Common Recursive Iterator Method (PHP 5+)
$dirIterator = new RecursiveDirectoryIterator('/path');
$iterator = new RecursiveIteratorIterator(
$dirIterator,
RecursiveIteratorIterator::SELF_FIRST
);
$numFiles=0;
foreach ($iterator as $file) ++$numFiles; //If you need to access files also.
$numFiles=iterator_count($iterator); //If you only want the count

Related

PHP returns true for all files that are run thru if statement

I'm totally stumped. I have a array holding all the files in a directory on my website. I use an if statement within a foreach loop to filter the results. If the file name begins with a certain combination of three letters ("wwa" or "wea" or "swa" or "hta" or "cta" or "twa" or "fra") then that file name is assigned to $alert_string.
For some reason, all of the files return true inside the if statement, so the variable is simply overwritten each time, and the last file scanned is assigned the variable.
Code:
$alert_files = scandir("/home/prww/public_html/blog/");
foreach ($alert_files as $values) {
if ((substr($values, 0, 3)) == ("wwa" or "wea" or "swa" or "hta" or "cta" or "twa" or "fra")) {
$alert_string = $values;
}
}
Files in the directory:
Clearly, only the file beginning with "hta" should return true in the if statement. So why does every file return true and how can I fix this?
Your if syntax is wrong.
You can simplify the logic using an array:
//array of allowed substrings, used as index for quicker lookup
$substringKeys = array_flip(["wwa","wea","swa","hta","cta","twa","fra"]);
$alert_string='';
$alert_files = scandir("/home/prww/public_html/blog/");
foreach ($alert_files as $filename) {
$substr = substr($filename, 0, 3);
if (isset($substringkeys[$substr])) {
//add to, not overwrite, $alert_string
$alert_string .= $values;
}
}
("wwa" or "wea" or "swa" or "hta" or "cta" or "twa" or "fra")
yields a boolean TRUE. As long as substr($values, 0, 3) returns something, your conditional expression will always be TRUE.
You can't write code that looks like it makes sense in English without understanding how the operators actually work. And when all else fails, echo intermediate results so you can understand what your program is doing.
You can use function to achieve this with help for array_map
$alert_files = scandir("/home/prww/public_html/blog/");
$fileNames = array_filter(array_map("checkName",$alert_files));
print_r($fileNames);
function checkName($fileName)
{
$file = substr($fileName,0,3);
$conditionArray = array("wwa","wea","swa","hta","cta","twa","fra");
if(in_array($file,$conditionArray))
{
return $fileName;
}
}

How to access first element in iterator?

I'm using a PHP framework that returns SQL results as iteratable objects. Problem is I have a SQL query that that returns one row and I don't want to have to create a foreach-loop to get at the first - and only - element.
So how do I do it?
These don't work:
$obj->item(0)->propName;
$obj->next()->propName;
$obj[0]->propName;
Any ideas?
Assuming by "iterable", you mean that the object implements the Iterator interface, you can use $obj->current() to retrieve the current element, so $obj->current()->propName is probably what you want.
If the iterator pointer has been moved (for example, if it was used in a foreach, which doesn't reset the pointer), then you can call $obj->rewind() to set the pointer back to the first element before you call $obj->current().
There are only two class interfaces that can be traversed: Iterator and IteratorAggregate (any other must implement one of them).
Iterator
First element of Iterator can be obtained as follows:
$iterator->rewind();
if (!$iterator->valid()) {
throw new Exception('There is no any element!');
}
$firstElement = $iterator->current();
If you are sure:
the $iterator was never been traversed by foreach, or if it was but the loop was never stopped with break or return (since PHP 7 this point is irrelevant because foreach does not affect pointer)
there was never called $iterator->next();
you can omit the $iterator->rewind(); from the previous example.
If you are sure the count of elements in $iterator is not zero, you can even omit the condition block testing $iterator->valid().
So if these previous conditions are preserved then what you need is just:
$firstElement = $iterator->current();
IteratorAggregate
IteratorAggergate is actually just an envelope for an Iterator or another IteratorAggregate. Logically that means there is an Iterator at the end.
If you know how many levels deep the Iterator is, just grab it and use as in the very first example:
$iterator = $iteratorAggregate->getIterator();
But if you don't know the deepness, you may use a solution which works also for an Iterator:
$array = iterator_to_array($iteratorAggregate);
// $array is an ordinary array now
if (count($array) === 0) {
throw new Exception('There is no any element!');
}
$firstElement = reset($array);
Unfortunately in case of biig array this is a little overkill because copy of all elements must be created despite we need just one. Besides if the Iterator is an infinite Generator you will run out of memory.
There is one solution that works:
while ($iterator instanceof \IteratorAggregate) {
$iterator = $iterator->getIterator();
}
// $iterator now contains instance of Iterator
I made a simple benchmark for an array of 10000 members and this solution was almost 6 times faster.
So, the universal solution which will work for all cases is:
while ($iterator instanceof \IteratorAggregate) {
$iterator = $iterator->getIterator();
}
$iterator->rewind();
if (!$iterator->valid()) {
throw new Exception('There is no any element!');
}
$firstElement = $iterator->current();
LLAP
For any types of iterables(array, Iterator, IteratorAggregate) you can use this function:
/* public static */ function first(iterable $iterable, $default = null) {
foreach ($iterable as $item){
return $item;
}
return $default;
}
If you prefer to throw an exception if iterable is empty, you can use that version:
/* public static */ function firstOrThrow(iterable $iterable, \Throwable $e) {
foreach ($iterable as $item){
return $item;
}
throw $e;
}
That works both for arrays and iterator objects and also computes only first item in iterators which can improve performance is some cases.
An alternative way of doing this, which I personally find less verbose is to convert the iterator to an array first and then check if the array is empty or not.
$result = iterator_to_array($iterator, true);
if(!empty($result)){
$user = end($result)->id;
}
Pretty useful when you know your iterator will return only one 'iteration'. However, if you are going to have thousands, be cautious that you'll be populating an array with all the data first which might increase your memory usage until the array if empty again.

How to access a multidimensinal PHP array, when path (level) to value is dynamic changing?

To set a value in a multidimentional array is easy. E.g.:
$myArray['levelA']['levelB']['levelC'] = $value
to read it is simple, too. E.g.:
$value = $myArray['levelA']['levelB']['levelC']-
Easy when i know how many levels are used. But what is when we do not know how many levels could occure?
What if i have a config file like this:
levelA.levelB.levelC.levelD = someValue
and what if i want to use levelA.levelB.levelC.levelD to map it (as a "path") to the multidimentional array, whichout knowing how deep my config could be?
Here's a small class I wrote that would determine the number of dimensions. You could easily modify it to do whatever you wanted, or just use the number it returns to guide your own function:
class DimensionCounter {
var $i;
public function count($array) {
$this->i = 1;
return $this->readArray($array);
}
private function readArray($array) {
foreach($array as $value) {
if (is_array($value)) {
$this->i++;
$this->readArray($value);
}
return $this->i;
}
}
}
In your case, $counter = new DimensionCounter(); $num = $counter->count($value); would give you the number of dimensions.
But what is when we do not know how many levels could occure[sic!] ?
Well it's just that you don't know how many levels there are or not.
You can use isset() to find out easily anyway:
$exists = isset($myArray['levelA']['levelB']['levelC']['levelD']);
But I must also admit that I have some problem to understand you question.
Are you looking for:
use strings to access (potentially large) multidimensional arrays
or similar?

basic PHP variable local/global issue

I have come from a background of C++ and am relatively new into the world of PHP.
I'm currently writing a little piece that has an index.php and definitions.php.
In definitions I have the variable
$passfile = "../ass1_data/passwords.txt";
I want to use $passfile inside a function in my index.
Can I just use it $passfile or do I have to use global with it as well?
This is what I have
function checkPasswd($login,$passwd){
global $passfile;
$p = $passfile;
foreach ($p as $line) {
// some code
}
}
At the moment I am getting the error 'Invalid argument supplied for foreach()'
Any help or explanations would be appreciated, thank you.
Assuming the text file contains lines with passwords in it you need to read the contents of the file in first and then loop through the results:
$passfile= file('../ass1_data/passwords.txt');
function checkPasswd($login,$passwd){
global $passfile;
foreach ($passfile as $line) {
// some code
}
}
1- you have to use global keyword for using global variable inside functions.
2- for second case(foreach) first you have to read the file, then use explode function to split it and use it in foreach.:
$passfile = "../ass1_data/passwords.txt";
function checkPasswd($login,$passwd){
global $passfile;
$p = explode("\n",file_get_contents($passfile));
foreach ($p as $line) {
// some code
}
}
You can't simply foreach over a string (even though you can access its bytes with subscript notation).
You need to turn it into an array first, one option for doing that is explode("\n").
However, it sounds like you want to open the file at that location. In that case, you can use file(), which will automatically provide you with an array of lines in that file.

Length of a PHP superglobal array

Is it possible to check how many items are within $_FILES[] using some sort of length() or size() ?
It's a normal array besides the fact that it's superglobal - simply use count($_FILES).
To count the successfully uploaded files, you could do the following in PHP5.3:
$successCount = array_reduce($_FILES, function($val, $file) {
if($file['error'] == UPLOAD_ERR_OK) return $val + 1;
return $val;
}, 0);
In older PHP versions the easiest way (I consider string function names for callbacks ugly) would be using a simple loop:
$count = 0;
foreach($_FILES as $file) {
if($file['error'] == UPLOAD_ERR_OK) $count++;
}
The count() function does that: http://php.net/manual/en/function.count.php
$c = count($_FILES);
there are 2 ways in naming fields for the upload.
While filename[] will make this array messy, filename1, filename2 etc will make $_FILES array behave as you expected and count($_FILES) will return number of input fields
If you don't know array structure, you should use print_r($_FILES) to see it first and then decide, what you want to count.
Also note that array name is $_FILES, not $_FILES[] as you mentioned. It's operator, not array name.

Categories