Is it possible to rewind a PDO result? - php

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

Related

Laravel cursor usage dilemma

get works while cursor returns blank
$excep[] = DB::table('table')->where('user_id', $user)->select('data')->get();
return $excep; // returns some sql data
But with cursor:
$excep[] = DB::table('table')->where('user_id', $user)->select('data')->cursor();
return $excep; // returns [{}]
Information isn't enough to understand: https://laravel.com/docs/5.5/eloquent#chunking-results
cursor only can be used on iterations, if you need all data at once, you must use get.
// All users will be loaded on memory first on collection and then, iterate.
// You can return all users as return UserModel::get();
foreach (UserModel::get() as $user) {
$this->sendWellcomeMail($user);
}
// Only one user on memory on each iteration.
// You can NOT return all users with return UserModel::cursor();
foreach (UserModel::cursor() as $user) {
$this->sendWellcomeMail($user);
}

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

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.

PHP: how to force property of method to be integer?

My current way:
class A {
public function function_b($myint) {
if (!is_numeric($myint)) return false;
// code ...
}
}
I would like to abandon the function is_numeric() like this:
public function function_b(Integer $myint) {
// code ...
}
It works with arrays like this:
public function function_c(Array $arr) {
// only executes following code if $arr is an array / instance of Array!
}
Note: the function has to return false if the value isn't a number (int)! I don't want to cast it.
How would you short my current code? Thanks in advance!
You can't force strict types in function prototypes in PHP inherently, because it's not a strictly typed language. PHP is a weakly typed language and trying to go against the grain will only hurt you in many situations. Also, is_numeric does not guarantee that your value is of type int (for what it's worth).
What you can do is analyze your need for why you think this approach is necessary in the first place and decide on how to best implement this without creating potential for bugs.
For example, take the following scenario where what your method expects is an ID for a database query.
class MyClass {
public function getUser($id) {
if (!is_int($id)) {
throw new Exception("Invalid argument supplied. Expecting (int), but argument is of type (" . gettype($id) . ").");
}
// Otherwise continue
$db = new PDO($dsn);
$stmt = $db->prepare("SELECT username FROM users WHERE user_id = ?");
$stmt->execute(array($id));
$result = $stmt->fetchAll(PDO::FETCH_ASSOC);
return $result;
}
}
$MyObject = new MyClass;
$result = $MyObject->getUser($_POST['id']);
/* The problem here is $_POST will always be of type string. */
What this should tell you is that it makes no sense to force type checking here since PHP will have done the right thing for you had you just let it alone.
The question you need to be asking yourself is not "How do I force strict typing?", but rather "Why would I need to force strict typing at all?".
You should look into typecasting:
http://php.net/manual/en/language.types.type-juggling.php#language.types.typecasting
Just use (int) when accessing the value to typecast it to an integer.
You could just typecast it:
public function function_b($myint) {
$myint = (int) $myint;
}
Or better yet add a public setter to class A which will do it for you every time you set the value:
class A
{
public function setMyInt($myInt)
{
$this->myInt = (int) $myInt;
}
}
-- Update (based on comment) --
class A
{
public function doSomethingWithAnArray(array $array)
{
....
}
}
Notice the keyword array in the signature of the doSomethingWithAnArray method, now if you don't pass an array to this function PHP will throw a fatal error and cease code execution. This is known as typehinting, and can be applied to objects as well.
function needsInteger($int) {
if (((int) $int) != $int) return false;
// ...
}
The advantage here is that you can still accept loosely typed parameters, but the non-strict equality check against the cast value will yield an acceptable result.

Return a loop in function php

Is it possible to return a loop? not the result but the loop it self.
I want to create a function in php. For example like this.
function myloop($sql){
$query = mysql_query($sql);
return while(mysql_fetch_assoc($query))
}
The reason i want to create this is for me to avoid repeating code. Anyone can help me? Thank you..
No, but you can simulate that with an Iterator for stable released PHP as of today. In PHP 5.5 there will be generators that is close, too.
$lazyQuery = new SqlResultItertor($sql);
foreach ($lazyQuery as $assoc) {
$assoc; # the result, one per row
}
BTW: PDO and MySqli offer this already out of the box (not lazy query, but the result is traversable), for mysql you need to write such an iterator-result object your own.
For some mysql_* functions related code, see this answer. However the general suggestion as of today is to use PDO or mysqli instead, those offer more out of the box. See How to successfully rewrite old mysql-php code with deprecated mysql_* functions?
No, you can't. You can pass a function to a function, though:
// The myloop function, with another name.
function query($sql, $rowcallback)
{
$query = mysqli_query($sql); // use mysqli, not mysql
while
( ($row = mysql_fetch_assoc($query)) &&
call_user_func($rowcallback, $row) );
}
// The code to process a row.
function processRow(array $row)
{
// Use your row here..
var_dump($row);
return true; // You can return false to break processing.
}
//calling:
query($yourSelf, 'processRow');
Instead of passing the function by name, you can also use anonymous functions, depending on your php version:
//calling:
query($yourSelf,
function(array $row)
{
var_dump($row);
return true; // You can return false to break processing.
});
The function which is called from the callee is often called a callback. call_user_func is the best way to call a callback, since it will also accept methods and static methods, not just functions, so you're more flexible.
No, it isn't.
You can return a function to does nothing but run a loop, but you can't return the loop itself.
No.
You could, for instance, return an anonymous function which may contain a loop, but you can only return values from functions, not language constructs.
You should turn it inside out!
Instead of returning the loop, you could do it this way using Variable functions :
function myloop($sql, $myFunction){
$query = mysql_query($sql);
while(mysql_fetch_assoc($query)) {
$myFunction($result);
}
}
function doSomethingWithTheResult($result) {
echo $result; // just to have something here...
}
//now the usage:
myloop("SELECT 1", 'doSomethingWithTheResult');
With a squint, this is similar to the concept of the Template method OOP design pattern.
No, but you could do
function loopdate($sql,$code)
{
$query=mysql_query($sql)
while (mysql_fetch_assoc($query))
{
eval($code);
}
}
However - eval is rediculously dangerous its really really discouraged.
function loopdate($sql,$function)
{
$query=mysql_query($sql)
while ($data=mysql_fetch_assoc($query))
{
$function($data);
}
}
would be better.
myfunc($data)
{
foreach ($data as $key->$value)
{
print "<tr><td>".$key."<td><td>".$value."</td></tr>\n";
}
}
So you can call
loopdate("select * from mytable","myfunc");
In PHP 5.5+ it is possible, using the yield keyword instead of return (this is called a generator):
function myloop($sql) {
$query = mysql_query($sql);
while (($row = mysql_fetch_assoc($query))) {
yield $row;
}
}
foreach (myloop('SELECT * FROM foo') as $row) {
// do something with $row
}
This is not much different from what you could do with iterators in older PHP, but the code is much cleaner.

Categories