3
\$\begingroup\$

The most nauseous problem with pagination is the fact that you have to write two almost identical queries. For the various reasons I am not inclined to use the SQL-only way (such as select count(*) from (original query goes here)). Hence I am trying to modify the original query. Being aware that this method is not fully reliable, I am doing this in a semi-automatic way, offering the automated method for the regular queries with the occasional fallback to setting the count query automatically.

The goal was to make the application code simple and at the same time explicit. The full compatibility with PDO's fetchAll() was a requirement.

The code works perfectly for me so far, but obviously I may have overlooked some edge cases. Also I am not sure about the code distribution between methods.

Here is the code

class Pagination
{
 protected PDO $pdo;
 protected string $sql;
 protected array $params;
 protected string $countSql;
 protected int $limit = 10;
 public function __construct(PDO $pdo, string $sql, array $params = []) {
 $this->pdo = $pdo;
 $this->sql = $sql;
 $this->params = $params;
 }
 public function setCountQuery(string $sql) {
 $this->countSql = $sql;
 return $this;
 }
 public function setLimit(int $limit) {
 $this->limit = $limit;
 return $this;
 }
 
 public function getPageCount():int {
 return (int)ceil($this->getNumRecords() / $this->limit);
 }
 
 public function getNumRecords() {
 $this->countSql = $this->countSql ?? $this->getAutoCountQuery();
 $stmt = $this->pdo->prepare($this->countSql);
 $stmt->execute($this->params);
 return $stmt->fetchColumn();
 }
 
 public function getPageData(int $page, $mode = null, ...$fetchAllParams) {
 $offset = ($page - 1) * $this->limit;
 $limit = (int)$this->limit;
 $mode = $mode ?? $this->pdo->getAttribute(PDO::ATTR_DEFAULT_FETCH_MODE);
 
 $sql = "$this->sql LIMIT $offset, $limit";
 $stmt = $this->pdo->prepare($sql);
 $stmt->execute($this->params);
 return $stmt->fetchAll($mode, ...$fetchAllParams);
 }
 public function getAutoCountQuery() {
 $pat = '~^(select)(.*)(\s+from\s+)~i';
 return preg_replace($pat, '1ドル count(*)3ドル', $this->sql);
 }
}

And here is a simple testing snippet, assuming you have a working PDO instance

$pdo->query("create temporary table test(i int)");
$pdo->query("insert into test (i) values (1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(11),(12)");
$pagination = new Pagination($pdo, "select * from test");
$pagination->setLimit(5);
#$pagination->setCountQuery("select count(*) from test");
$pageCount = $pagination->getPageCount();
$data = $pagination->getPageData(1);
$data2 = $pagination->getPageData(2, PDO::FETCH_COLUMN);
$data3 = $pagination->getPageData(3, PDO::FETCH_CLASS, 'stdClass');
var_dump($pageCount, json_encode($data), json_encode($data2), json_encode($data3));
asked Jul 1, 2021 at 10:35
\$\endgroup\$
2
  • 1
    \$\begingroup\$ If someone ever feeds in a multi-line sql string, then the dot in the regex might not work as expected. I might suggest preg_replace('~^select\K.*?(?=\s+from\s)~is', ' COUNT(*)', $this->sql) Is it too soon to expect the null coalescing assignment operator? \$\endgroup\$ Commented Jul 1, 2021 at 16:47
  • 1
    \$\begingroup\$ Your methods are missing return type hints. Even if you are still on PHP 7.4 I would add them. If that's not possible then at least docblock comments. \$\endgroup\$ Commented Jul 9, 2021 at 13:40

1 Answer 1

2
\$\begingroup\$

Someone using your class might not be aware that every time the getPageCount() method is used a SQL query is executed. Ideally it should only be executed once. This can be achieved with code like this:

class Pagination
{
 protected int $numRecords = -1;
 public function getNumRecords() {
 if ($this->numRecords < 0) {
 $this->countSql = $this->countSql ?? $this->getAutoCountQuery();
 $stmt = $this->pdo->prepare($this->countSql);
 $stmt->execute($this->params);
 $this->numRecords = $stmt->fetchColumn();
 }
 return $this->numRecords;
 } 
}

I left out all the properties and methods irrelevant to this example.

Question: How can you bind PHP parameters to your queries?

answered Jul 5, 2021 at 14:41
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.