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());
}
}
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'];
});
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 > 10
Combining 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 <= 10
When using the expression objects you can use the following methods to create conditions:
eq()
Creates an equality condition.
notEq()
Create an inequality condition
like()
Create a condition using the LIKE
operator.
notLike()
Create a negated LIKE
condition.
in()
Create a condition using IN
.
notIn()
Create a negated condition using IN
.
gt()
Create a >
condition.
gte()
Create a >=
condition.
lt()
Create a <
condition.
lte()
Create a <=
condition.
isNull()
Create an IS NULL
condition.
isNotNull()
Create a negated IS NULL
condition.
<?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]);
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 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 update queries is similar to both inserting and selecting:
<?php
$builder = $this->getQueryBuilder('update');
$builder
->update('users')
->set('fname', 'Snow')
->where(['fname' => 'Jon'])
->execute();
Finally, delete queries:
<?php
$builder = $this->getQueryBuilder('delete');
$builder
->delete('users')
->where(['accepted_gdpr' => false])
->execute();