Using the Query Builder
It is not uncommon to pair database structure changes with data changes. For example, you may want to migrate the data in a couple columns from the users to a newly created table. For this type of scenarios, Migrations provides access to a Query builder object, that you may use to execute complex SELECT, UPDATE, INSERT or DELETE statements.
The Query builder is provided by the cakephp/database project, and should be easy to work with as it resembles very closely plain SQL. Accesing the query builder is done by calling the getQueryBuilder(string $type) function. The string $type options are 'select', 'insert', 'update' and `'delete'`:
<?php
use Migrations\BaseMigration;
class MyNewMigration extends BaseMigration
{
/**
* Migrate Up.
*/
public function up(): void
{
$builder = $this->getQueryBuilder('select');
$statement = $builder->select('*')->from('users')->execute();
var_dump($statement->fetchAll());
}
}Selecting Fields
Adding fields to the SELECT clause:
<?php
$builder->select(['id', 'title', 'body']);
// Results in SELECT id AS pk, title AS aliased_title, body ...
$builder->select(['pk' => 'id', 'aliased_title' => 'title', 'body']);
// Use a closure
$builder->select(function ($builder) {
return ['id', 'title', 'body'];
});Where Conditions
Generating conditions:
// WHERE id = 1
$builder->where(['id' => 1]);
// WHERE id > 1
$builder->where(['id >' => 1]);As you can see you can use any operator by placing it with a space after the field name. Adding multiple conditions is easy as well:
<?php
$builder->where(['id >' => 1])->andWhere(['title' => 'My Title']);
// Equivalent to
$builder->where(['id >' => 1, 'title' => 'My title']);
// WHERE id > 1 OR title = 'My title'
$builder->where(['OR' => ['id >' => 1, 'title' => 'My title']]);For even more complex conditions you can use closures and expression objects:
<?php
// Coditions are tied together with AND by default
$builder
->select('*')
->from('articles')
->where(function ($exp) {
return $exp
->eq('author_id', 2)
->eq('published', true)
->notEq('spam', true)
->gt('view_count', 10);
});Which results in:
SELECT * FROM articles
WHERE
author_id = 2
AND published = 1
AND spam != 1
AND view_count > 10Combining expressions is also possible:
<?php
$builder
->select('*')
->from('articles')
->where(function ($exp) {
$orConditions = $exp->or_(['author_id' => 2])
->eq('author_id', 5);
return $exp
->not($orConditions)
->lte('view_count', 10);
});It generates:
SELECT *
FROM articles
WHERE
NOT (author_id = 2 OR author_id = 5)
AND view_count <= 10When using the expression objects you can use the following methods to create conditions:
eq()Creates an equality condition.notEq()Create an inequality conditionlike()Create a condition using theLIKEoperator.notLike()Create a negatedLIKEcondition.in()Create a condition usingIN.notIn()Create a negated condition usingIN.gt()Create a>condition.gte()Create a>=condition.lt()Create a<condition.lte()Create a<=condition.isNull()Create anIS NULLcondition.isNotNull()Create a negatedIS NULLcondition.
Aggregates and SQL Functions
<?php
// Results in SELECT COUNT(*) count FROM ...
$builder->select(['count' => $builder->func()->count('*')]);A number of commonly used functions can be created with the func() method:
sum()Calculate a sum. The arguments will be treated as literal values.avg()Calculate an average. The arguments will be treated as literal values.min()Calculate the min of a column. The arguments will be treated as literal values.max()Calculate the max of a column. The arguments will be treated as literal values.count()Calculate the count. The arguments will be treated as literal values.concat()Concatenate two values together. The arguments are treated as bound parameters unless marked as literal.coalesce()Coalesce values. The arguments are treated as bound parameters unless marked as literal.dateDiff()Get the difference between two dates/times. The arguments are treated as bound parameters unless marked as literal.now()Take either 'time' or 'date' as an argument allowing you to get either the current time, or current date.
When providing arguments for SQL functions, there are two kinds of parameters you can use, literal arguments and bound parameters. Literal parameters allow you to reference columns or other SQL literals. Bound parameters can be used to safely add user data to SQL functions. For example:
<?php
// Generates:
// SELECT CONCAT(title, ' NEW') ...;
$concat = $builder->func()->concat([
'title' => 'literal',
' NEW'
]);
$query->select(['title' => $concat]);Getting Results out of a Query
Once you’ve made your query, you’ll want to retrieve rows from it. There are a few ways of doing this:
<?php
// Iterate the query
foreach ($builder as $row) {
echo $row['title'];
}
// Get the statement and fetch all results
$results = $builder->execute()->fetchAll('assoc');Creating an Insert Query
Creating insert queries is also possible:
<?php
$builder = $this->getQueryBuilder('insert');
$builder
->insert(['first_name', 'last_name'])
->into('users')
->values(['first_name' => 'Steve', 'last_name' => 'Jobs'])
->values(['first_name' => 'Jon', 'last_name' => 'Snow'])
->execute();For increased performance, you can use another builder object as the values for an insert query:
<?php
$namesQuery = $this->getQueryBuilder('select');
$namesQuery
->select(['fname', 'lname'])
->from('users')
->where(['is_active' => true]);
$builder = $this->getQueryBuilder('insert');
$st = $builder
->insert(['first_name', 'last_name'])
->into('names')
->values($namesQuery)
->execute();
var_dump($st->lastInsertId('names', 'id'));The above code will generate:
INSERT INTO names (first_name, last_name)
(SELECT fname, lname FROM USERS where is_active = 1)Creating an update Query
Creating update queries is similar to both inserting and selecting:
<?php
$builder = $this->getQueryBuilder('update');
$builder
->update('users')
->set('fname', 'Snow')
->where(['fname' => 'Jon'])
->execute();Creating a Delete Query
Finally, delete queries:
<?php
$builder = $this->getQueryBuilder('delete');
$builder
->delete('users')
->where(['accepted_gdpr' => false])
->execute();