Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to get database resource instead of array from Laravel Query Builder?

When I execute a PDO statement, internally a result set is stored, and I can use ->fetch() to get a row from the result.

If I wanted to convert the entire result to an array, I could do ->fetchAll().

With Laravel, in the Query Builder docs, I only see a way to get an array result from executing the query.

// example query, ~30,000 rows

$scores = DB::table("highscores")
            ->select("player_id", "score")
            ->orderBy("score", "desc")
            ->get();

var_dump($scores);
// array of 30,000 items...
// unbelievable ...

Is there any way to get a result set from Query Builder like PDO would return? Or am I forced to wait for Query Builder to build an entire array before it returns a value ?

Perhaps some sort of ->lazyGet(), or ->getCursor() ?


If this is the case, I can't help but see Query Builder is an extremely short-sighted tool. Imagine a query that selects 30,000 rows. With PDO I can step through row by row, one ->fetch() at a time, and handle the data with very little additional memory consumption.

Laravel Query Builder on the other hand? "Memory management, huh? It's fine, just load 30,000 rows into one big array!"


PS yes, I know I can use ->skip() and ->take() to offset and limit the result set. In most cases, this would work fine, as presenting a user with 30,000 rows is not even usable. If I want to generate large reports, I can see PHP running out of memory easily.

like image 291
Mulan Avatar asked Dec 26 '22 06:12

Mulan


2 Answers

After @deczo pointed out an undocumented function ->chunk(), I dug around in the source code a bit. What I found is that ->chunk() is a convenience wrapper around multiplying my query into several queries queries but automatically populating the ->step($m)->take($n) parameters. If I wanted to build my own iterator, using ->chunk with my data set, I'd end up with 30,000 queries on my DB instead of 1.

This doesn't really help, too, because ->chunk() takes a callback which forces me to couple my looping logic at the time I'm building the query. Even if the function was defined somewhere else, the query is going to happen in the controller, which should have little interest in the intricacies of my View or Presenter.

Digging a little further, I found that all Query Builder queries inevitably pass through \Illuminate\Database\Connection#run.

// https://github.com/laravel/framework/blob/3d1b38557afe0d09326d0b5a9ff6b5705bc67d29/src/Illuminate/Database/Connection.php#L262-L284

/**
 * Run a select statement against the database.
 *
 * @param  string  $query
 * @param  array   $bindings
 * @return array
 */
public function select($query, $bindings = array())
{
  return $this->run($query, $bindings, function($me, $query, $bindings)
  {
    if ($me->pretending()) return array();

    // For select statements, we'll simply execute the query and return an array
    // of the database result set. Each element in the array will be a single
    // row from the database table, and will either be an array or objects.
    $statement = $me->getReadPdo()->prepare($query);

    $statement->execute($me->prepareBindings($bindings));

    return $statement->fetchAll($me->getFetchMode());
  });
}

See that nasty $statement->fetchAll near the bottom ?

That means arrays for everyone, always and forever; your wishes and dreams abstracted away into an unusable tool Laravel Query Builder.

I can't express the valley of my depression right now.


One thing I will say though is that the Laravel source code was at least organized and formatted nicely. Now let's get some good code in there!

like image 80
Mulan Avatar answered Dec 27 '22 19:12

Mulan


Use chunk:

DB::table('highscores')
   ->select(...)
   ->orderBy(...)
   ->chunk($rowsNumber, function ($portion) {
      foreach ($portion as $row) { // do whatever you like }
   });

Obviously returned result will be just the same as calling get, so:

$portion; // array of stdObjects

// and for Eloquent models:
Model::chunk(100, function ($portion) {
    $portion; // Collection of Models
});
like image 43
Jarek Tkaczyk Avatar answered Dec 27 '22 20:12

Jarek Tkaczyk