Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

The Sql query builder

A plain select(['id' => 5]) is not always enough: you need ordering, a search by part of a word, data from two tables. For that laminas-db has a query builder — objects from which a query is assembled with method calls, while the package writes the final SQL. No separate files are needed: the builder is used inside the table class.

Ordering and limiting

The table gateway hands out a query stub for its own table. Add a method to module/Blog/src/Model/PostTable.php:

// module/Blog/src/Model/PostTable.php
public function fetchLatest(int $limit = 10): array
{
    $select = $this->gateway->getSql()->select();
    $select->order('id DESC')->limit($limit);
 
    return iterator_to_array($this->gateway->selectWith($select));
}

getSql()->select() creates a Select object for the posts table, and selectWith() runs it. Calling $this->posts->fetchLatest(5) in the controller returns the five newest posts as Post objects.

Conditions

Simple conditions are passed as an array, more complex ones through methods of the where object:

public function search(string $text): array
{
    $select = $this->gateway->getSql()->select();
    $select->where->like('title', '%' . $text . '%');
    $select->order('title ASC');
 
    return iterator_to_array($this->gateway->selectWith($select));
}

The method finds posts whose title contains the text. The value goes to the database as a parameter instead of being pasted into the query.

where methodSQL condition
equalTo('status', 1)status = 1
notEqualTo('status', 0)status != 0
greaterThan('views', 100)views > 100
like('title', '%php%')title LIKE '%php%'
in('id', [1, 2, 3])id IN (1, 2, 3)
isNull('deleted_at')deleted_at IS NULL

Several calls in a row are joined with AND.

Joining tables

For a query across several tables the Sql object created from the adapter is more convenient. Suppose there is a users table and posts has a user_id column:

use Laminas\Db\Sql\Sql;
 
$sql = new Sql($adapter);
 
$select = $sql->select('posts')
    ->columns(['id', 'title'])
    ->join('users', 'users.id = posts.user_id', ['author' => 'name'])
    ->where(['users.id' => 1]);
 
$rows = $sql->prepareStatementForSqlObject($select)->execute();
 
foreach ($rows as $row) {
    echo $row['title'] . ' — ' . $row['author'];
}

The third argument of join() lists the columns of the second table; ['author' => 'name'] means "the name column under the name author". Each result row is a plain array.

Counting

SQL expressions such as COUNT(*) are wrapped in Expression:

use Laminas\Db\Sql\Expression;
 
$select = $sql->select('posts')->columns(['total' => new Expression('COUNT(*)')]);
 
$row = $sql->prepareStatementForSqlObject($select)->execute()->current();
 
$total = (int) $row['total'];

$total holds the number of posts.

Transactions

A transaction groups several queries: either all of them run, or none.

$connection = $adapter->getDriver()->getConnection();
$connection->beginTransaction();
 
try {
    $this->posts->save($post);
    $this->tags->saveFor($post, $tags);
 
    $connection->commit();
} catch (\Throwable $e) {
    $connection->rollback();
 
    throw $e;
}

If saving the tags fails, the post does not stay in the database either.

Common mistake

A string condition with a pasted value — $select->where("title = '$title'") — opens the door to SQL injection: a visitor can append their own code to the query. Pass conditions as an array ['title' => $title] or through where->equalTo(), where->like() — then the value travels to the database separately from the query.

Thanks for reading!

Donate to the site — we work hard for you.