The DbSelect adapter

The DbSelect adapter allows you to provide a Select statement for pulling a dataset, and optionally a Select statement for pulling a count of results, and an optional Select statement for providing an overall count of items.

The adapter does not fetch all records from the database in order to count them, nor does it run any queries immediately. If no Select instance was provided for counting results, the adapter manipulates the original Select to produce a corresponding COUNT query, and uses the new query to get the number of rows. While this approach requires an extra round-trip to the database, doing so is still many times faster than fetching an entire result set and using count(), especially with large collections of data.

Creating An Instance

The DbSelect constructor has the following signature:

public function __construct(
    \Laminas\Db\Sql\Select $select,
    \Laminas\Db\Adapter\AdapterInterface|\Laminas\Db\Sql\Sql $adapterOrSqlObject,
    ?\Laminas\Db\ResultSet\ResultSetInterface $resultSetPrototype = null,
    ?\Laminas\Db\Sql\Select $countSelect = null
)

The first argument is the Select to use when retrieving results to paginate. The next argument, $adapterOrSqlObject, provides access to the adapter so it can execute the Select statement against the actual database. The third argument is a specific result set type to use on results returned from the Select operation; these allow you to customize the items returned, if desired. (See the laminas-db ResultSet documentation for more details.) The fourth argument allows you to specify a specific Select instance to use to provide a total count of results.

Using the AdapterPluginManager

By default, when pulling the Laminas\Paginator\AdapterPluginManager from the application DI container, it is aware of the DbSelect adapter. You can retrieve an instance from the plugin manager via its get() method, passing any constructor arguments you want to provide via an array as the second argument:

use Laminas\Paginator\AdapterPluginManager;
use Laminas\Paginator\Adapter\LaminasDb\DbSelect;

// $container is the PSR-11 container associated with the application.
$pluginManager = $container->get(AdapterPluginManager::class);

// $select is the laminas-db Select instance for retrieving items
// $dbAdapter is the laminas-db adapter you want to use
$adapter = $pluginManager->get(DbSelect::class, [
    $select,
    $dbAdapter
]);

All required arguments to the constructor must be passed in the array, and they will be passed in the same order to the constructor.

Modifying Result Items

The default Laminas\Db\ResultSet\ResultSet used when iterating over items returns each item as an associative array. If you wish to filter out specific fields, modify the column names, or return something other than an associative array, you will need to provide a different Laminas\Db\ResultSet\ResultSetInterface implementation to the constructor, or extend the adapter and override the getItems() method.

Providing an Alternate ResultSet

You can override the default ResultSet implementation by passing an object implementing Laminas\Db\ResultSet\ResultSetInterface as the third constructor argument to the DbSelect adapter:

use Laminas\Db\ResultSet\HydratingResultSet;
use Laminas\Paginator\Adapter\LaminasDb\DbSelect;
use Laminas\Paginator\Paginator;

// $objectPrototype is an instance of our custom entity
// $hydrator is a custom hydrator for our entity
// (implementing Laminas\Hydrator\HydratorInterface)
$resultSet = new HydratingResultSet($hydrator, $objectPrototype);

// $query is our Select statement
// $dbAdapter is the laminas-db adapter instance
$adapter   = new DbSelect($query, $dbAdapter, $resultSet)
$paginator = new Laminas\Paginator\Paginator($adapter);

Now when we iterate over $paginator, we will get instances of our custom entity instead of associative arrays.

Overriding getItems

If you want to manipulate the results manually, you can extend the adapter and override the getItems() method directly. The following example demonstrates using an array_map() operation on results in order to cast the rows to an object. It assumes the class App\Fuzz exists, and defines a static method fromArray() that will allow casting an array to an App\Fuzz instance.

namespace App;

use Laminas\Paginator\Adapter\LaminasDb\DbSelect;

class FuzzDbSelect extends DbSelect
{
    public function getItems($offset, $itemCountPerPage)
    {
        return array_map(
            function (array $row): Fuzz {
              return Fuzz::fromArray($row);
            },
            parent::getItems($offset, $itemCountPerPage)
        );
    }
}

Counting Total Items

The database adapter will try and build the most efficient query that will execute on pretty much any modern database. However, depending on your database or even your own schema setup, there might be more efficient ways to get a rowcount.

There are two approaches for doing this: providing an additional Select instance for retrieving a count to the constructor, or overriding the count() method.

Providing a Select for Counting

You can pass an additional Laminas\Db\Sql\Select object as the fourth constructor argument to the DbSelect adapter to implement a custom count query.

For example, if you keep track of the count of blog posts in a separate table, you could achieve a faster count query with the following setup:

use Laminas\Db\Sql\Select;
use Laminas\Paginator\Adapter\LaminasDb\DbSelect;
use Laminas\Paginator\Paginator;

$countQuery = new Select();
$countQuery
    ->from('item_counts')
    ->columns([DbSelect::ROW_COUNT_COLUMN_NAME => 'post_count']);

// $query is the Select for retrieving items
// $dbAdapter is the laminas-db adapter
$adapter   = new DbSelect($query, $dbAdapter, null, $countQuery);
$paginator = new Paginator($adapter);

This approach will probably not give you a huge performance gain on small collections and/or simple select queries. However, with complex queries and large collections, a similar approach could give you a significant performance boost.

Overriding the Count Method

The following example demonstrates extending the DbSelect adapter to override the count() method.

namespace App;

use Laminas\Db\Sql\Select;
use Laminas\Paginator\Adapter\LaminasDb\DbSelect;

class MyDbSelect extends DbSelect
{
    public function count()
    {
        if ($this->rowCount) {
            return $this->rowCount;
        }

        $select = new Select();
        $select
            ->from('item_counts')
            ->columns(['c'=>'post_count']);

        $statement = $this->sql->prepareStatementForSqlObject($select);
        $result    = $statement->execute();
        $row       = $result->current();
        $this->rowCount = $row['c'];

        return $this->rowCount;
    }
}

// $query is the Select for retrieving items
// $dbAdapter is the laminas-db adapter
$adapter = new MyDbSelect($query, $dbAdapter);