How to access first element in iterator? - php

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.

Related

PHP Recursive Function - Incrementing total values

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

PDO mutliple fetch_assoc from one query [duplicate]

I'm trying to write an iterator for results from a PDO statement but I can't find any way of rewinding to the first row. I would like to avoid the overhead of calling fetchAll and storing all the result data.
// first loop works fine
foreach($statement as $result) {
// do something with result
}
// but subsequent loops don't
foreach($statement as $result) {
// never called
}
Is there some way of reseting the statement or seeking the first row?
I'm pretty sure this is database dependent. Because of that, it is something you should try to avoid. However, I think you can achieve what you want by enabling buffered queries. If that doesn't work, you can always pull the result into an array with fetchAll. Both solutions have implications for your applications performance, so think twice about it, if the resultsets are large.
This little class I wrote wraps a PDOStatement. It only stores the data that is fetched. If this doesn't work, you could move the cache to read and write to a file.
// Wrap a PDOStatement to iterate through all result rows. Uses a
// local cache to allow rewinding.
class PDOStatementIterator implements Iterator
{
public
$stmt,
$cache,
$next;
public function __construct($stmt)
{
$this->cache = array();
$this->stmt = $stmt;
}
public function rewind()
{
reset($this->cache);
$this->next();
}
public function valid()
{
return (FALSE !== $this->next);
}
public function current()
{
return $this->next[1];
}
public function key()
{
return $this->next[0];
}
public function next()
{
// Try to get the next element in our data cache.
$this->next = each($this->cache);
// Past the end of the data cache
if (FALSE === $this->next)
{
// Fetch the next row of data
$row = $this->stmt->fetch(PDO::FETCH_ASSOC);
// Fetch successful
if ($row)
{
// Add row to data cache
$this->cache[] = $row;
}
$this->next = each($this->cache);
}
}
}
see slide 31 from this presentation, you can do a $statement->rewind() if it applies to a buffered query. If you use mysql, you can emulate buffered queries by using PDO_MYSQL_ATTR_USE_BUFFERED_QUERY:
$pdo->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, 1);
#NoahGoodrich pointed you to spl. Here is an example that always works:
$it = new ArrayIterator($stmt->fetchAll());
Asked a long time ago but currently there's another solution.
The method PDOStatement::fetch() may receives a second parameter, the cursor orientation, with one of PDO::FETCH_ORI_* constants. These parameter are only valid if the PDOStatement are created with the atribute PDO::ATTR_CURSOR as PDO::CURSOR_SCROLL.
This way you can navigate as follows.
$sql = "Select * From Tabela";
$statement = $db->prepare($sql, array(
PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL,
));
$statement->execute();
$statement->fetch(PDO::FETCH_BOTH, PDO::FETCH_ORI_NEXT); // return next
$statement->fetch(PDO::FETCH_BOTH, PDO::FETCH_ORI_PRIOR); // return previous
$statement->fetch(PDO::FETCH_BOTH, PDO::FETCH_ORI_FIRST); // return first
$statement->fetch(PDO::FETCH_BOTH, PDO::FETCH_ORI_LAST); // return last
$statement->fetch(PDO::FETCH_BOTH, PDO::FETCH_ORI_ABS, $n); // return to $n position
$statement->fetch(PDO::FETCH_BOTH, PDO::FETCH_ORI_REL, $n); // return to $n position relative to current
More info in docs and PDO predefined constants.
Note: used PDO::FETCH_BOTH because is the default, just customize it for your project.
You'll probably want to take a look at some of the PHP SPL classes that can be extended to provide array-like access to objects.
Standard PHP Library (SPL) I would specifically
recommend that you look at the
ArrayIterator, ArrayObject, and
perhaps the Iterator interface.
Simple
Tutorial
Another
Quick Tutorial

PHP Object References?

I've read up about PHP variable references but I'm not 100% and was hoping someone could help.
If I have a class like the following:
class Item
{
public $value;
}
I then have an array of those items in a variable - lets call that $items. All I did was new Item()...and $items[] = $newItem;.
Now, I want to populate another array but it filters the original array based on its value. So like the following:
foreach($items as $key => $value)
{
$filteredItems[] = &value;
}
Now, I have ANOTHER variable that iterates over that filtered list and does something like so:
$theItem = $filteredItems[10];
$theItem->value = 100;
Now this is where I'm confused. Do I need to set $theItem to &filteredItems[10]; (reference) or will it just know that the value in the array is a reference type and $theItem also becomes a reference to that same item? I'm after that last set of $theItem->value = 100; changes the very original object stored in the $items list.
In PHP 5 objects are always passed around by their "handle" for lack of better word. This means if you do this:
$a = new Item();
$a->value = 1;
$b = $a;
$b->value++;
echo $a->value;
The value of 2 is echoed. Why? Because the handle of the object is copied from $a to $b and they both point to the same object. This isn't a reference in terms of using &, but behaves similarly enough to the point that people generally call it the same thing... even though it's not.
So you do not need any use of references in your code. Usually in PHP, you never need to use references when using objects.
With respect to objects, you really only notice references if you do this (assign a new value to the variable itself):
function foo(Item &$a)
{
$a = null;
}
$b = new Item();
foo($b);
var_dump($b);
This results in NULL, which wouldn't happen without a reference. But again, this is not typical usage, so you can really forget about using references with objects.
(And of course the use of a function isn't necessary here to illustrate the point, but that's the most typical place you'll see them in the "real world.")
It's like this:
foreach($items as $key => &$value) {
$filteredItems[] = $value;
}
The point where you give the original instance into a different scope is where you put the &.
Same is for functions:
function myFunction(&$variable) { }
Example:
<?php
class test {
public $testVar;
public function __construct() {
$this->testVar = "1";
}
}
function changeByReference(&$obj) {
$obj->testVar = "2";
}
$instance = new test();
// Prints 1
echo $instance->testVar, PHP_EOL;
changeByReference($instance);
// Prints 2
echo $instance->testVar, PHP_EOL;
Read more about it here: http://php.net/manual/en/language.oop5.references.php
If you want to copy an instance, use clone - php.net/clone
The easiest way to get it is when you know the difference between these: class, object and instance. (I'd explain it more at this point but it would only confuse you more because my english is not accurate enough for now to explain the details enough.)

What's the best way to verify that an element is ready for use in a foreach() loop in php?

example:
foreach($boxes as $box) {
echo "$box \n";
}
Used to be fairly easy, I could just wrap the foreach around a check like:
if(is_array($boxes) && count($boxes) > 0) {
//foreach loop here
}
Without having to worry about a warning getting thrown if for whatever reason bad input was passed to the $boxes array.
When Iterators, were added to the mix, this no longer works, as Iteratable objects are not arrays. So, I have a few solutions, but am wondering if there is a 'best practice' for this.
// 1:
if($boxes instanceof Traversable && count($boxes) > 0) {
//foreach loop here
}
// 2:
if($boxes && count($boxes) > 0) {
//foreach loops here
}
There are others, but these seem like the most obvious. Anyone have any suggestions. PHP docs seem to be silent.
You shouldn't have the count($array) > 0 part, because a) foreach works fine with empty arrays, b) objects can be Traversable yet not be Countable and c) the value returned by count() may even (for objects) be disconnected from the number of items the traversal will yield.
And #1 there is different from #2; since $boxes instanceOf Traversable is not the same as $boxes. Also note that internally arrays don't implement Traversable.
I would go with
if (is_array($boxes) || $boxes instanceof Traversable) {
foreach (...)
}
This still doesn't guarantee that the traversal will be successful; the iteration may throw an exception at any point. In particular, for some classes it may not make sense to traverse them more than once.
I think generally in these cases you would probably know that the variable is going to be iterable if it is not null or false etc., so I would be happy to just do:
if ($boxes) {
foreach ($boxes as $box) {}
}
Maybe that is naive though
One possibility depending on your php version is a cast:
<?php
$a = array('foo', array('bar'));
foreach ($a as $thing)
foreach ((array) $thing as $item) // <-- here
echo "$item\n";
?>
This test will give true for both arrays and array-like objects
if (is_array($obj) || $obj instanceof Traversable) {
foreach ($obj as $item) { /* foreach loop is safe here */
}
}
In PHP5 you can iterate over any array or object so..
if (is_array($my_var) || is_object($my_var)) {
// Do some foreachin'
}

Is it possible to rewind a PDO result?

I'm trying to write an iterator for results from a PDO statement but I can't find any way of rewinding to the first row. I would like to avoid the overhead of calling fetchAll and storing all the result data.
// first loop works fine
foreach($statement as $result) {
// do something with result
}
// but subsequent loops don't
foreach($statement as $result) {
// never called
}
Is there some way of reseting the statement or seeking the first row?
I'm pretty sure this is database dependent. Because of that, it is something you should try to avoid. However, I think you can achieve what you want by enabling buffered queries. If that doesn't work, you can always pull the result into an array with fetchAll. Both solutions have implications for your applications performance, so think twice about it, if the resultsets are large.
This little class I wrote wraps a PDOStatement. It only stores the data that is fetched. If this doesn't work, you could move the cache to read and write to a file.
// Wrap a PDOStatement to iterate through all result rows. Uses a
// local cache to allow rewinding.
class PDOStatementIterator implements Iterator
{
public
$stmt,
$cache,
$next;
public function __construct($stmt)
{
$this->cache = array();
$this->stmt = $stmt;
}
public function rewind()
{
reset($this->cache);
$this->next();
}
public function valid()
{
return (FALSE !== $this->next);
}
public function current()
{
return $this->next[1];
}
public function key()
{
return $this->next[0];
}
public function next()
{
// Try to get the next element in our data cache.
$this->next = each($this->cache);
// Past the end of the data cache
if (FALSE === $this->next)
{
// Fetch the next row of data
$row = $this->stmt->fetch(PDO::FETCH_ASSOC);
// Fetch successful
if ($row)
{
// Add row to data cache
$this->cache[] = $row;
}
$this->next = each($this->cache);
}
}
}
see slide 31 from this presentation, you can do a $statement->rewind() if it applies to a buffered query. If you use mysql, you can emulate buffered queries by using PDO_MYSQL_ATTR_USE_BUFFERED_QUERY:
$pdo->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, 1);
#NoahGoodrich pointed you to spl. Here is an example that always works:
$it = new ArrayIterator($stmt->fetchAll());
Asked a long time ago but currently there's another solution.
The method PDOStatement::fetch() may receives a second parameter, the cursor orientation, with one of PDO::FETCH_ORI_* constants. These parameter are only valid if the PDOStatement are created with the atribute PDO::ATTR_CURSOR as PDO::CURSOR_SCROLL.
This way you can navigate as follows.
$sql = "Select * From Tabela";
$statement = $db->prepare($sql, array(
PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL,
));
$statement->execute();
$statement->fetch(PDO::FETCH_BOTH, PDO::FETCH_ORI_NEXT); // return next
$statement->fetch(PDO::FETCH_BOTH, PDO::FETCH_ORI_PRIOR); // return previous
$statement->fetch(PDO::FETCH_BOTH, PDO::FETCH_ORI_FIRST); // return first
$statement->fetch(PDO::FETCH_BOTH, PDO::FETCH_ORI_LAST); // return last
$statement->fetch(PDO::FETCH_BOTH, PDO::FETCH_ORI_ABS, $n); // return to $n position
$statement->fetch(PDO::FETCH_BOTH, PDO::FETCH_ORI_REL, $n); // return to $n position relative to current
More info in docs and PDO predefined constants.
Note: used PDO::FETCH_BOTH because is the default, just customize it for your project.
You'll probably want to take a look at some of the PHP SPL classes that can be extended to provide array-like access to objects.
Standard PHP Library (SPL) I would specifically
recommend that you look at the
ArrayIterator, ArrayObject, and
perhaps the Iterator interface.
Simple
Tutorial
Another
Quick Tutorial

Categories