编写迁移文件
Phinx 依赖迁移(migrations)来转换你的数据库。每个迁移都由一个唯一文件中的 PHP 类表示。虽然也支持原生 SQL,但首选使用 Phinx PHP API 来编写迁移。
创建新迁移
生成骨架迁移文件
我们先来创建一个新的 Phinx 迁移。使用 create 命令运行 Phinx:
$ vendor/bin/phinx create MyNewMigration
这会创建一个格式为 YYYYMMDDHHMMSS_my_new_migration.php 的新迁移文件,其中前 14 个字符会被替换为精确到秒的当前时间戳。
如果你指定了多个迁移路径,系统会提示你选择在哪个路径下创建新的迁移文件。
Phinx 会自动创建一个带有一个方法的骨架迁移文件:
<?php
use Phinx\Migration\AbstractMigration;
class MyNewMigration extends AbstractMigration
{
/**
* Change 方法。
*
* 在此方法中编写可逆迁移。
*
* 有关编写迁移的更多信息,请参阅:
* https://book.cakephp.org/phinx/0/en/migrations.html#the-change-method
*
* 使用 Table 类时,请记得调用 "create()" 或 "update()",而不是 "save()"。
*/
public function change()
{
}
}
所有的 Phinx 迁移都继承自 AbstractMigration 类。这个类为创建数据库迁移提供了必要的支持。数据库迁移可以通过多种方式转换你的数据库,例如创建新表、插入行、添加索引和修改列。
Change 方法
Phinx 0.2.0 引入了一个名为 可逆迁移(reversible migrations) 的新功能。这个功能现在已成为默认的迁移方法。通过可逆迁移,你只需要定义 up 的逻辑,Phinx 就能自动推断出如何进行 down 迁移。例如:
<?php
use Phinx\Migration\AbstractMigration;
class CreateUserLoginsTable extends AbstractMigration
{
public function change()
{
// 创建表
$table = $this->table('user_logins');
$table->addColumn('user_id', 'integer')
->addColumn('created', 'datetime')
->create();
}
}
执行此迁移时,Phinx 会在向上迁移(up)时创建 user_logins 表,并在向下迁移(down)时自动推断出如何删除该表。请注意,当 change 方法存在时,Phinx 会自动忽略 up 和 down 方法。如果你需要使用这些方法,建议创建一个单独的迁移文件。
change() 方法中创建或更新表时,你 必须 使用 Table 的 create() 和 update() 方法。Phinx 无法自动判断一个 save() 调用是在创建新表还是在修改现有表。通过 Phinx 的 Table API 完成的以下操作是可逆的,并且会被自动反向执行:
- 创建表
- 重命名表
- 添加列
- 重命名列
- 添加索引
- 添加外键
如果一个命令无法被反转,Phinx 会在向下迁移时抛出 IrreversibleMigrationException 异常。如果你希望在 change 函数中使用无法反转的命令,可以使用带有 $this->isMigratingUp() 的 if 语句,来让代码只在向上或向下迁移时运行。例如:
<?php
use Phinx\Migration\AbstractMigration;
class CreateUserLoginsTable extends AbstractMigration
{
public function change()
{
// 创建表
$table = $this->table('user_logins');
$table->addColumn('user_id', 'integer')
->addColumn('created', 'datetime')
->create();
if ($this->isMigratingUp()) {
$table->insert([['user_id' => 1, 'created' => '2020-01-19 03:14:07']])
->save();
}
}
}
Up 方法
当你向上迁移且 Phinx 检测到给定的迁移之前未被执行过时,up 方法会被自动运行。你应该使用 up 方法来执行你预期的数据库转换。
Down 方法
当你向下迁移且 Phinx 检测到给定的迁移过去曾被执行过时,down 方法会被自动运行。你应该使用 down 方法来反转/撤销 up 方法中描述的转换。
Init 方法
如果 init() 方法存在,Phinx 会在执行迁移方法之前运行它。这可以用于设置迁移方法中会用到的公共类属性。
Should Execute 方法
shouldExecute() 方法在执行迁移之前由 Phinx 运行。这可以用来阻止迁移在此时执行。默认情况下,它总是返回 true。你可以在你的自定义 AbstractMigration 实现中覆盖它。
执行查询
查询可以通过 execute() 和 query() 方法执行。execute() 方法返回受影响的行数,而 query() 方法返回结果类型为 PDOStatement。这两个方法都接受一个可选的第二个参数 $params,它是一个元素数组,如果使用该参数,底层的连接将使用预处理语句。
<?php
use Phinx\Migration\AbstractMigration;
class MyNewMigration extends AbstractMigration
{
/**
* 向上迁移。
*/
public function up()
{
// execute()
$count = $this->execute('DELETE FROM users'); // 返回受影响的行数
// query()
$stmt = $this->query('SELECT * FROM users'); // 返回 PDOStatement
$rows = $stmt->fetchAll(); // 以数组形式返回结果
// 使用预处理查询
$count = $this->execute('DELETE FROM users WHERE id = ?', [5]);
$stmt = $this->query('SELECT * FROM users WHERE id > ?', [5]); // 返回 PDOStatement
$rows = $stmt->fetchAll();
}
/**
* 向下迁移。
*/
public function down()
{
}
}
execute() 命令之前,请始终确保你的查询符合 PDO 的规范。这在插入存储过程或触发器时使用 DELIMITER 时尤其重要,因为它们不支持 DELIMITER。execute() 或 query() 执行一批查询时,如果批处理中的一个或多个查询有问题,PDO 不会抛出异常。因此,整个批处理被假定为已无问题地通过。如果 Phinx 迭代任何可能的结果集以查看是否有错误,那么 Phinx 将拒绝访问所有结果,因为 PDO 中没有获取前一个结果集的机制(有 nextRowset() - 但没有 previousSet())。因此,由于 PDO 中对批处理查询不抛出异常的设计决策,当提供一批查询时,Phinx 无法为错误处理提供最全面的支持。幸运的是,PDO 的所有功能都可用,因此可以通过调用 nextRowset() 和检查 errorInfo 来在迁移中控制多个批处理。获取行
有两个方法可用于获取行。fetchRow() 方法将获取单行,而 fetchAll() 方法将返回多行。这两个方法都接受原生 SQL 作为其唯一参数。
<?php
use Phinx\Migration\AbstractMigration;
class MyNewMigration extends AbstractMigration
{
/**
* 向上迁移。
*/
public function up()
{
// 获取一个用户
$row = $this->fetchRow('SELECT * FROM users');
// 获取一个消息数组
$rows = $this->fetchAll('SELECT * FROM messages');
}
/**
* 向下迁移。
*/
public function down()
{
}
}
插入数据
Phinx 使向表中插入数据变得容易。虽然此功能主要用于 seed 功能,但你也可以在迁移中使用插入方法。
<?php
use Phinx\Migration\AbstractMigration;
class NewStatus extends AbstractMigration
{
/**
* 向上迁移。
*/
public function up()
{
$table = $this->table('status');
// 只插入一行
$singleRow = [
'id' => 1,
'name' => 'In Progress'
];
$table->insert($singleRow)->saveData();
// 插入多行
$rows = [
[
'id' => 2,
'name' => 'Stopped'
],
[
'id' => 3,
'name' => 'Queued'
]
];
$table->insert($rows)->saveData();
}
/**
* 向下迁移。
*/
public function down()
{
$this->execute('DELETE FROM status');
}
}
change() 方法中使用插入方法。请使用 up() 和 down() 方法。使用表
Table 对象
Table 对象是 Phinx 提供的最有用的 API 之一。它允许你使用 PHP 代码轻松地操作数据库表。你可以通过在数据库迁移中调用 table() 方法来检索 Table 对象的实例。
<?php
use Phinx\Migration\AbstractMigration;
class MyNewMigration extends AbstractMigration
{
/**
* 向上迁移。
*/
public function up()
{
$table = $this->table('tableName');
}
/**
* 向下迁移。
*/
public function down()
{
}
}
然后,你可以使用 Table 对象提供的方法来操作这张表。
保存更改
在使用 Table 对象时,Phinx 会将某些操作存储在待处理的更改缓存中。一旦你对表完成了所需的更改,你必须保存它们。为了执行此操作,Phinx 提供了三个方法:create()、update() 和 save()。create() 会首先创建表,然后运行待处理的更改。update() 只会运行待处理的更改,应该在表已存在时使用。save() 是一个辅助函数,它首先检查表是否存在,如果不存在则运行 create(),否则运行 update()。
如上所述,在使用 change() 迁移方法时,你应该始终使用 create() 或 update(),绝不要使用 save(),否则迁移和回滚可能会导致不同的状态,因为 save() 在运行迁移时调用 create(),然后在回滚时调用 update()。在使用 up()/down() 方法时,使用 save() 或更明确的方法都是安全的。
在处理表时不确定时,总是建议调用适当的函数并将任何待处理的更改提交到数据库。
创建表
使用 Table 对象创建表非常容易。让我们创建一个表来存储用户集合。
<?php
use Phinx\Migration\AbstractMigration;
class MyNewMigration extends AbstractMigration
{
public function change()
{
$users = $this->table('users');
$users->addColumn('username', 'string', ['limit' => 20])
->addColumn('password', 'string', ['limit' => 40])
->addColumn('password_salt', 'string', ['limit' => 40])
->addColumn('email', 'string', ['limit' => 100])
->addColumn('first_name', 'string', ['limit' => 30])
->addColumn('last_name', 'string', ['limit' => 30])
->addColumn('created', 'datetime')
->addColumn('updated', 'datetime', ['null' => true])
->addIndex(['username', 'email'], ['unique' => true])
->create();
}
}
列是使用 addColumn() 方法添加的。我们使用 addIndex() 方法为 username 和 email 列创建了一个唯一索引。最后调用 create() 将更改提交到数据库。
id 的自增主键列。id 选项设置自动创建的标识字段的名称,而 primary_key 选项选择用作主键的一个或多个字段。id 总是会覆盖 primary_key 选项,除非它被设置为 false。如果你不需要主键,请将 id 设置为 false 而不指定 primary_key,这样就不会创建主键。
要指定一个备用主键,你可以在访问 Table 对象时指定 primary_key 选项。让我们禁用自动的 id 列,并改用两个列创建主键:
<?php
use Phinx\Migration\AbstractMigration;
class MyNewMigration extends AbstractMigration
{
public function change()
{
$table = $this->table('followers', ['id' => false, 'primary_key' => ['user_id', 'follower_id']]);
$table->addColumn('user_id', 'integer')
->addColumn('follower_id', 'integer')
->addColumn('created', 'datetime')
->create();
}
}
设置单个 primary_key 不会启用 AUTO_INCREMENT 选项。要简单地更改主键的名称,我们需要覆盖默认的 id 字段名称:
<?php
use Phinx\Migration\AbstractMigration;
class MyNewMigration extends AbstractMigration
{
public function up()
{
$table = $this->table('followers', ['id' => 'user_id']);
$table->addColumn('follower_id', 'integer')
->addColumn('created', 'timestamp', ['default' => 'CURRENT_TIMESTAMP'])
->create();
}
}
此外,MySQL 适配器支持以下选项:
| 选项 | 描述 |
|---|---|
comment | 为表设置文本注释 |
row_format | 设置表行格式 |
engine | 定义表引擎(默认为 InnoDB) |
collation | 定义表排序规则(默认为 utf8mb4_unicode_ci) |
signed | 主键是否有符号(默认为 false) |
limit | 设置主键的最大长度 |
默认情况下,主键是 unsigned(无符号)的。要将其设置为有符号,只需传递 signed 选项并设其值为 true:
<?php
use Phinx\Migration\AbstractMigration;
class MyNewMigration extends AbstractMigration
{
public function change()
{
$table = $this->table('followers', ['signed' => false]);
$table->addColumn('follower_id', 'integer')
->addColumn('created', 'timestamp', ['default' => 'CURRENT_TIMESTAMP'])
->create();
}
}
PostgreSQL 适配器支持以下选项:
| 选项 | 描述 |
|---|---|
comment | 为表设置文本注释 |
要查看可用的列类型和选项,请参阅有效列类型部分。
判断表是否存在
你可以使用 hasTable() 方法来判断一个表是否存在。
<?php
use Phinx\Migration\AbstractMigration;
class MyNewMigration extends AbstractMigration
{
/**
* 向上迁移。
*/
public function up()
{
$exists = $this->hasTable('users');
if ($exists) {
// 做些什么
}
}
/**
* 向下迁移。
*/
public function down()
{
}
}
删除表
使用 drop() 方法可以很轻松地删除表。在 down() 方法中重新创建该表是一个好主意。
请注意,与 Table 类中的其他方法一样,drop 也需要在末尾调用 save() 才能执行。这允许 phinx 在涉及多个表时智能地规划迁移。
<?php
use Phinx\Migration\AbstractMigration;
class MyNewMigration extends AbstractMigration
{
/**
* 向上迁移。
*/
public function up()
{
$this->table('users')->drop()->save();
}
/**
* 向下迁移。
*/
public function down()
{
$users = $this->table('users');
$users->addColumn('username', 'string', ['limit' => 20])
->addColumn('password', 'string', ['limit' => 40])
->addColumn('password_salt', 'string', ['limit' => 40])
->addColumn('email', 'string', ['limit' => 100])
->addColumn('first_name', 'string', ['limit' => 30])
->addColumn('last_name', 'string', ['limit' => 30])
->addColumn('created', 'datetime')
->addColumn('updated', 'datetime', ['null' => true])
->addIndex(['username', 'email'], ['unique' => true])
->save();
}
}
重命名表
要重命名一个表,请访问 Table 对象的实例,然后调用 rename() 方法。
<?php
use Phinx\Migration\AbstractMigration;
class MyNewMigration extends AbstractMigration
{
/**
* 向上迁移。
*/
public function up()
{
$table = $this->table('users');
$table
->rename('legacy_users')
->update();
}
/**
* 向下迁移。
*/
public function down()
{
$table = $this->table('legacy_users');
$table
->rename('users')
->update();
}
}
更改主键
要在现有表上更改主键,请使用 changePrimaryKey() 方法。传入一个列名或列名数组以包含在主键中,或传入 null 以删除主键。请注意,提到的列必须已添加到表中,它们不会被隐式添加。
<?php
use Phinx\Migration\AbstractMigration;
class MyNewMigration extends AbstractMigration
{
/**
* 向上迁移。
*/
public function up()
{
$users = $this->table('users');
$users
->addColumn('username', 'string', ['limit' => 20, 'null' => false])
->addColumn('password', 'string', ['limit' => 40])
->save();
$users
->addColumn('new_id', 'integer', ['null' => false])
->changePrimaryKey(['new_id', 'username'])
->save();
}
/**
* 向下迁移。
*/
public function down()
{
}
}
更改表注释
要更改现有表的注释,请使用 changeComment() 方法。传入一个字符串作为新的表注释,或传入 null 以删除现有注释。
<?php
use Phinx\Migration\AbstractMigration;
class MyNewMigration extends AbstractMigration
{
/**
* 向上迁移。
*/
public function up()
{
$users = $this->table('users');
$users
->addColumn('username', 'string', ['limit' => 20])
->addColumn('password', 'string', ['limit' => 40])
->save();
$users
->changeComment('这是包含用户认证信息的表,密码应加密')
->save();
}
/**
* 向下迁移。
*/
public function down()
{
}
}
使用列
有效列类型
列类型以字符串形式指定,可以是以下之一:
binarybooleanchardatedatetimedecimalfloatdoublesmallintegerintegerbigintegerstringtexttimetimestampuuid
此外,MySQL 适配器支持 enum, set, blob, tinyblob, mediumblob, longblob, bit 和 json 列类型(json 在 MySQL 5.7 及以上版本)。当提供 limit 值并使用 binary, varbinary 或 blob 及其子类型时,保留的列类型将基于所需长度(详见 Limit 选项和 MySQL)。
此外,Postgres 适配器支持 interval, json, jsonb, uuid, cidr, inet 和 macaddr 列类型(PostgreSQL 9.3 及以上版本)。
有效列选项
以下是有效的列选项:
适用于任何列类型:
| 选项 | 描述 |
|---|---|
limit | 设置字符串的最大长度,也在适配器中提示列类型(见下文注释) |
length | limit 的别名 |
default | 设置默认值或操作 |
null | 允许 NULL 值,默认为 true(设置 identity 将覆盖默认值为 false) |
after | 指定新列应放置在哪一列之后,或使用 \Phinx\Db\Adapter\MysqlAdapter::FIRST 将列放在表的开头(仅适用于 MySQL) |
comment | 为列设置文本注释 |
对于 decimal 列:
| 选项 | 描述 |
|---|---|
precision | 与 scale 结合设置小数精度 |
scale | 与 precision 结合设置小数精度 |
signed | 启用或禁用 unsigned 选项(仅适用于 MySQL) |
对于 enum 和 set 列:
| 选项 | 描述 |
|---|---|
values | 可以是逗号分隔的列表或值的数组 |
对于 smallinteger, integer 和 biginteger 列:
| 选项 | 描述 |
|---|---|
identity | 启用或禁用自动递增(如果启用,并且未设置 null 选项,则将设置 null: false) |
signed | 启用或禁用 unsigned 选项(仅适用于 MySQL) |
对于 Postgres,使用 identity 时,它将利用适合整数大小的 serial 类型,因此 smallinteger 会得到 smallserial,integer 得到 serial,biginteger 得到 bigserial。
对于 timestamp 列:
| 选项 | 描述 |
|---|---|
default | 设置默认值(与 CURRENT_TIMESTAMP 一起使用) |
update | 设置行更新时触发的操作(与 CURRENT_TIMESTAMP 一起使用)(仅适用于 MySQL) |
timezone | 为 time 和 timestamp 列启用或禁用 with time zone 选项(仅适用于 Postgres) |
你可以使用 addTimestamps() 方法向表中添加 created_at 和 updated_at 时间戳。此方法接受三个参数,其中前两个允许为列设置备用名称,而第三个参数允许你为列启用 timezone 选项。这些参数的默认值分别为 created_at、updated_at 和 false。对于第一个和第二个参数,如果提供 null,则将使用默认名称;如果提供 false,则不会创建该列。请注意,尝试将两者都设置为 false 将抛出 \RuntimeException。此外,你可以使用 addTimestampsWithTimezone() 方法,它是 addTimestamps() 的别名,它总是将第三个参数设置为 true(见下例)。created_at 列将有一个默认设置为 CURRENT_TIMESTAMP。仅对于 MySQL,update_at 列将有 update 设置为 CURRENT_TIMESTAMP。
<?php
use Phinx\Migration\AbstractMigration;
class MyNewMigration extends AbstractMigration
{
/**
* Change 迁移。
*/
public function change()
{
// 使用默认值(不带时区)
$table = $this->table('users')->addTimestamps()->create();
// 使用默认值(带时区)
$table = $this->table('users')->addTimestampsWithTimezone()->create();
// 将 'created_at' 列名覆盖为 'recorded_at'。
$table = $this->table('books')->addTimestamps('recorded_at')->create();
// 将 'updated_at' 列名覆盖为 'amended_at',并保留时区。
// 下面两行代码作用相同,第二行更简洁。
$table = $this->table('books')->addTimestamps(null, 'amended_at', true)->create();
$table = $this->table('users')->addTimestampsWithTimezone(null, 'amended_at')->create();
// 只向表中添加 created_at 列
$table = $this->table('books')->addTimestamps(null, false);
// 只向表中添加 updated_at 列
$table = $this->table('users')->addTimestamps(false);
// 注意,将两者都设置为 false 会抛出 \RuntimeError
}
}
对于 boolean 列:
| 选项 | 描述 |
|---|---|
signed | 启用或禁用 unsigned 选项(仅适用于 MySQL) |
对于 string 和 text 列:
| 选项 | 描述 |
|---|---|
collation | 设置与表默认值不同的排序规则(仅适用于 MySQL) |
encoding | 设置与表默认值不同的字符集(仅适用于 MySQL) |
对于外键定义:
| 选项 | 描述 |
|---|---|
update | 设置行更新时触发的操作 |
delete | 设置行删除时触发的操作 |
constraint | 设置外键约束要使用的名称 |
deferrable | 设置外键约束为可延迟(仅适用于 PostgreSQL) |
你可以通过可选的第三个参数数组将这些选项中的一个或多个传递给任何列。
Limit 选项和 MySQL
使用 MySQL 适配器时,在使用 limit 时需要考虑几点:
- 在 MySQL 5.7 或更低版本,或 MyISAM 存储引擎上,当使用
string主键或索引,且默认字符集为utf8mb4_unicode_ci时,你必须指定一个小于或等于 191 的limit,或者使用不同的字符集。 - 可以为
integer、text、blob、tinyblob、mediumblob、longblob列提供额外的数据库列类型提示。将limit与以下选项之一一起使用将相应地修改列类型:
| Limit | 列类型 |
|---|---|
BLOB_TINY | TINYBLOB |
BLOB_REGULAR | BLOB |
BLOB_MEDIUM | MEDIUMBLOB |
BLOB_LONG | LONGBLOB |
TEXT_TINY | TINYTEXT |
TEXT_REGULAR | TEXT |
TEXT_MEDIUM | MEDIUMTEXT |
TEXT_LONG | LONGTEXT |
INT_TINY | TINYINT |
INT_SMALL | SMALLINT |
INT_MEDIUM | MEDIUMINT |
INT_REGULAR | INT |
INT_BIG | BIGINT |
对于 binary 或 varbinary 类型,如果 limit 设置大于允许的 255 字节,类型将被更改为与给定长度最匹配的 blob 类型。
<?php
use Phinx\Db\Adapter\MysqlAdapter;
//...
$table = $this->table('cart_items');
$table->addColumn('user_id', 'integer')
->addColumn('product_id', 'integer', ['limit' => MysqlAdapter::INT_BIG])
->addColumn('subtype_id', 'integer', ['limit' => MysqlAdapter::INT_SMALL])
->addColumn('quantity', 'integer', ['limit' => MysqlAdapter::INT_TINY])
->create();
自定义列类型和默认值
一些数据库管理系统提供了它们特有的额外列类型和默认值。如果你不想让你的迁移保持 DBMS 无关性,你可以在迁移中通过 \Phinx\Util\Literal::from 方法使用这些自定义类型。该方法接受一个字符串作为其唯一参数,并返回一个 \Phinx\Util\Literal 的实例。当 Phinx 遇到这个值作为列的类型时,它知道不对其进行任何验证,并按原样使用它而不进行转义。这也适用于 default 值。
下面的示例展示了如何在 PostgreSQL 中添加一个 citext 列以及一个默认值为函数的列。所有适配器都支持这种阻止内置转义的方法。
<?php
use Phinx\Migration\AbstractMigration;
use Phinx\Util\Literal;
class AddSomeColumns extends AbstractMigration
{
public function change()
{
$this->table('users')
->addColumn('username', Literal::from('citext'))
->addColumn('uniqid', 'uuid', [
'default' => Literal::from('uuid_generate_v4()')
])
->addColumn('creation', 'timestamp', [
'timezone' => true,
'default' => Literal::from('now()')
])
->create();
}
}
用户定义类型(自定义数据域)
在基本类型和列选项的基础上,你可以定义自己的用户定义类型。用户定义类型在 data_domain 根配置选项中配置。
data_domain:
phone_number:
type: string
length: 20
address_line:
type: string
length: 150
每个用户定义类型可以包含任何有效的类型和列选项,它们仅用作“宏”,并在迁移时被替换。
<?php
//...
$table = $this->table('user_data');
$table->addColumn('user_phone_number', 'phone_number')
->addColumn('user_address_line_1', 'address_line')
->addColumn('user_address_line_2', 'address_line', ['null' => true])
->create();
在项目开始时指定数据域对于拥有一个同质的数据模型至关重要。它避免了诸如拥有许多不同长度的 contact_name 列、整数类型不匹配(long vs. bigint 等)之类的错误。
integer、text 和 blob 列,你可以使用来自 MySQL 和 Postgres 适配器类的特殊常量。你甚至可以自定义一些内部类型以添加自己的默认选项,但某些列选项不能在数据模型中被覆盖(某些选项是固定的,如 uuid 特殊数据类型的 limit)。# 一些自定义数据类型的例子
data_domain:
file:
type: blob
limit: BLOB_LONG # 对于 MySQL 数据库。使用 MysqlAdapter::BLOB_LONG
boolean:
type: boolean # 自定义 boolean 为 unsigned
signed: false
image_type:
type: enum # 枚举可以使用 YAML 列表或逗号分隔的字符串
values:
- gif
- jpg
- png
获取列列表
要检索所有表列,只需创建一个 table 对象并调用 getColumns() 方法。此方法将返回一个包含基本信息的 Column 类数组。示例如下:
<?php
use Phinx\Migration\AbstractMigration;
class ColumnListMigration extends AbstractMigration
{
/**
* 向上迁移。
*/
public function up()
{
$columns = $this->table('users')->getColumns();
...
}
/**
* 向下迁移。
*/
public function down()
{
...
}
}
按名称获取列
要检索一个表列,只需创建一个 table 对象并调用 getColumn() 方法。此方法将返回一个包含基本信息的 Column 类,如果列不存在则返回 NULL。示例如下:
<?php
use Phinx\Migration\AbstractMigration;
class ColumnListMigration extends AbstractMigration
{
/**
* 向上迁移。
*/
public function up()
{
$column = $this->table('users')->getColumn('email');
...
}
/**
* 向下迁移。
*/
public function down()
{
...
}
}
检查列是否存在
你可以使用 hasColumn() 方法检查表是否已存在某个列。
<?php
use Phinx\Migration\AbstractMigration;
class MyNewMigration extends AbstractMigration
{
/**
* Change 方法。
*/
public function change()
{
$table = $this->table('user');
$column = $table->hasColumn('username');
if ($column) {
// 做些什么
}
}
}
重命名列
要重命名列,请访问 Table 对象的实例,然后调用 renameColumn() 方法。
<?php
use Phinx\Migration\AbstractMigration;
class MyNewMigration extends AbstractMigration
{
/**
* 向上迁移。
*/
public function up()
{
$table = $this->table('users');
$table->renameColumn('bio', 'biography')
->save();
}
/**
* 向下迁移。
*/
public function down()
{
$table = $this->table('users');
$table->renameColumn('biography', 'bio')
->save();
}
}
在另一列之后添加列
当使用 MySQL 适配器添加列时,你可以使用 after 选项来指定其位置,其值为要放置在其后的列的名称。
<?php
use Phinx\Migration\AbstractMigration;
class MyNewMigration extends AbstractMigration
{
/**
* Change 方法。
*/
public function change()
{
$table = $this->table('users');
$table->addColumn('city', 'string', ['after' => 'email'])
->update();
}
}
这会创建新列 city 并将其放置在 email 列之后。\Phinx\Db\Adapter\MysqlAdapter::FIRST 常量可用于指定新列应创建为该表的第一列。
删除列
要删除列,请使用 removeColumn() 方法。
<?php
use Phinx\Migration\AbstractMigration;
class MyNewMigration extends AbstractMigration
{
/**
* 向上迁移。
*/
public function up()
{
$table = $this->table('users');
$table->removeColumn('short_name')
->save();
}
}
指定列长度限制
你可以使用 limit 选项来限制列的最大长度。
<?php
use Phinx\Migration\AbstractMigration;
class MyNewMigration extends AbstractMigration
{
/**
* Change 方法。
*/
public function change()
{
$table = $this->table('tags');
$table->addColumn('short_name', 'string', ['limit' => 30])
->update();
}
}
更改列属性
要更改现有列的类型或选项,请使用 changeColumn() 方法。有关允许的值,请参阅有效列类型和有效列选项。
<?php
use Phinx\Migration\AbstractMigration;
class MyNewMigration extends AbstractMigration
{
/**
* Migrate Up.
*/
public function up()
{
$users = $this->table('users');
$users->changeColumn('email', 'string', ['limit' => 255])
->save();
}
/**
* Migrate Down.
*/
public function down()
{
}
}
使用索引
要向表中添加索引,你只需在表对象上调用 addIndex() 方法。
<?php
use Phinx\Migration\AbstractMigration;
class MyNewMigration extends AbstractMigration
{
/**
* 向上迁移。
*/
public function up()
{
$table = $this->table('users');
$table->addColumn('city', 'string')
->addIndex(['city'])
->save();
}
/**
* 向下迁移。
*/
public function down()
{
}
}
默认情况下,Phinx 会指示数据库适配器创建一个普通索引。我们可以向 addIndex() 方法传递一个额外的参数 unique 来指定唯一索引。我们还可以使用 name 参数为索引明确指定一个名称,索引列的排序顺序也可以使用 order 参数来指定。order 参数接受一个由列名和排序顺序键/值对组成的数组。
<?php
use Phinx\Migration\AbstractMigration;
class MyNewMigration extends AbstractMigration
{
/**
* 向上迁移。
*/
public function up()
{
$table = $this->table('users');
$table->addColumn('email', 'string')
->addColumn('username','string')
->addIndex(['email', 'username'], [
'unique' => true,
'name' => 'idx_users_email',
'order' => ['email' => 'DESC', 'username' => 'ASC']]
)
->save();
}
/**
* 向下迁移。
*/
public function down()
{
}
}
MySQL 适配器还支持 fulltext(全文)索引。如果你使用的版本低于 5.6,你必须确保表使用 MyISAM 引擎。
<?php
use Phinx\Migration\AbstractMigration;
class MyNewMigration extends AbstractMigration
{
public function change()
{
$table = $this->table('users', ['engine' => 'MyISAM']);
$table->addColumn('email', 'string')
->addIndex('email', ['type' => 'fulltext'])
->create();
}
}
此外,MySQL 适配器还支持设置由 limit 选项定义的索引长度。当使用多列索引时,你可以定义每个列的索引长度。单列索引可以通过 limit 选项定义其索引长度,无论是否在选项中定义列名。
<?php
use Phinx\Migration\AbstractMigration;
class MyNewMigration extends AbstractMigration
{
public function change()
{
$table = $this->table('users');
$table->addColumn('email', 'string')
->addColumn('username','string')
->addColumn('user_guid', 'string', ['limit' => 36])
->addIndex(['email','username'], ['limit' => ['email' => 5, 'username' => 2]])
->addIndex('user_guid', ['limit' => 6])
->create();
}
}
SQL Server 和 PostgreSQL 适配器还支持在索引中包含(非键)列(include)。
<?php
use Phinx\Migration\AbstractMigration;
class MyNewMigration extends AbstractMigration
{
public function change()
{
$table = $this->table('users');
$table->addColumn('email', 'string')
->addColumn('firstname','string')
->addColumn('lastname','string')
->addIndex(['email'], ['include' => ['firstname', 'lastname']])
->create();
}
}
此外,PostgreSQL 适配器还支持广义倒排索引 gin。
<?php
use Phinx\Migration\AbstractMigration;
class MyNewMigration extends AbstractMigration
{
public function change()
{
$table = $this->table('users');
$table->addColumn('address', 'string')
->addIndex('address', ['type' => 'gin'])
->create();
}
}
删除索引就像调用 removeIndex() 方法一样简单。你必须为每个索引调用此方法。
<?php
use Phinx\Migration\AbstractMigration;
class MyNewMigration extends AbstractMigration
{
/**
* 向上迁移。
*/
public function up()
{
$table = $this->table('users');
$table->removeIndex(['email'])
->save();
// 或者,你也可以通过索引名称删除索引,例如:
$table->removeIndexByName('idx_users_email')
->save();
}
/**
* 向下迁移。
*/
public function down()
{
}
}
使用外键
Phinx 支持在你的数据库表上创建外键约束。让我们给一个示例表添加一个外键:
<?php
use Phinx\Migration\AbstractMigration;
class MyNewMigration extends AbstractMigration
{
/**
* 向上迁移。
*/
public function up()
{
$table = $this->table('tags');
$table->addColumn('tag_name', 'string')
->save();
$refTable = $this->table('tag_relationships');
$refTable->addColumn('tag_id', 'integer', ['null' => true])
->addForeignKey('tag_id', 'tags', 'id', ['delete'=> 'SET_NULL', 'update'=> 'NO_ACTION'])
->save();
}
/**
* 向下迁移。
*/
public function down()
{
}
}
“On delete” 和 “On update” 的行为是通过一个 delete 和 update 选项数组来定义的。可能的值是 SET_NULL、NO_ACTION、CASCADE 和 RESTRICT。如果使用 SET_NULL,那么该列必须通过选项 ['null' => true] 创建为可为空。约束名称可以通过 ‘constraint’ 选项来更改。
也可以向 addForeignKey() 传递一个列的数组。这允许我们与使用组合键的表建立外键关系。
<?php
use Phinx\Migration\AbstractMigration;
class MyNewMigration extends AbstractMigration
{
/**
* 向上迁移。
*/
public function up()
{
$table = $this->table('follower_events');
$table->addColumn('user_id', 'integer')
->addColumn('follower_id', 'integer')
->addColumn('event_id', 'integer')
->addForeignKey(['user_id', 'follower_id'],
'followers',
['user_id', 'follower_id'],
['delete'=> 'NO_ACTION', 'update'=> 'NO_ACTION', 'constraint' => 'user_follower_id'])
->save();
}
/**
* 向下迁移。
*/
public function down()
{
}
}
我们可以使用 constraint 参数添加命名的外键。此功能从 Phinx 0.6.5 版本开始支持。
<?php
use Phinx\Migration\AbstractMigration;
class MyNewMigration extends AbstractMigration
{
/**
* 向上迁移。
*/
public function up()
{
$table = $this->table('your_table');
$table->addForeignKey('foreign_id', 'reference_table', ['id'],
['constraint' => 'your_foreign_key_name']);
->save();
}
/**
* 向下迁移。
*/
public function down()
{
}
}
对于 PostgreSQL,你可以设置外键是否可延迟(deferrable)。可用的选项有 DEFERRED(对应于 DEFERRABLE INITIALLY DEFERRED)、IMMEDIATE(对应于 DEFERRABLE INITIALLY IMMEDIATE)和 NOT_DEFERRED(对应于 NOT DEFERRABLE)。
<?php
use Phinx\Migration\AbstractMigration;
class MyNewMigration extends AbstractMigration
{
public function change()
{
$table = $this->table('phones');
$table->addColumn('name', 'string')
->addColumn('manufacturer_name', 'string')
->addForeignKey('manufacturer_name',
'manufacturers ',
'name',
['deferrable' => 'DEFERRED'])
->save();
}
}
我们也可以轻松地检查一个外键是否存在:
<?php
use Phinx\Migration\AbstractMigration;
class MyNewMigration extends AbstractMigration
{
/**
* 向上迁移。
*/
public function up()
{
$table = $this->table('tag_relationships');
$exists = $table->hasForeignKey('tag_id');
if ($exists) {
// 做些什么
}
}
/**
* 向下迁移。
*/
public function down()
{
}
}
最后,要删除一个外键,使用 dropForeignKey 方法。
注意,与 Table 类中的其他方法一样,dropForeignKey 也需要在末尾调用 save() 才能执行。这使得 phinx 在涉及多个表时能够智能地规划迁移。
<?php
use Phinx\Migration\AbstractMigration;
class MyNewMigration extends AbstractMigration
{
/**
* 向上迁移。
*/
public function up()
{
$table = $this->table('tag_relationships');
$table->dropForeignKey('tag_id')->save();
}
/**
* 向下迁移。
*/
public function down()
{
}
}
使用查询构建器
将数据库结构更改与数据更改配对是很常见的。例如,你可能想将几列中的数据从 users 表迁移到一个新创建的表中。对于这类场景,Phinx 提供了一个查询构建器对象的访问权限,你可以用它来执行复杂的 SELECT、UPDATE、INSERT 或 DELETE 语句。
该查询构建器由 cakephp/database 项目提供,并且应该很容易使用,因为它与普通 SQL 非常相似。通过调用 getQueryBuilder(string $type) 函数来访问查询构建器。string $type 的选项是 select、insert、update 和 delete:
<?php
use Phinx\Migration\AbstractMigration;
class MyNewMigration extends AbstractMigration
{
/**
* 向上迁移。
*/
public function up()
{
$builder = $this->getQueryBuilder('select');
$statement = $builder->select('*')->from('users')->execute();
var_dump($statement->fetchAll());
}
}
或者,可以使用以下方法来增强代码组织并提高清晰度:
getSelectBuilder(): 返回一个用于构建 SELECT 语句的 SelectQuery 对象。getInsertBuilder(): 返回一个用于构建 INSERT 语句的 InsertQuery 对象。getUpdateBuilder(): 返回一个用于构建 UPDATE 语句的 UpdateQuery 对象。getDeleteBuilder(): 返回一个用于构建 DELETE 语句的 DeleteQuery 对象。
<?php
use Phinx\Migration\AbstractMigration;
class MyNewMigration extends AbstractMigration
{
/**
* 向上迁移。
*/
public function up()
{
$builder = $this->getSelectBuilder();
$statement = $builder->select('*')->from('users')->execute();
var_dump($statement->fetchAll());
}
}
选择字段
向 SELECT 子句添加字段:
<?php
$builder->select(['id', 'title', 'body']);
// 结果为 SELECT id AS pk, title AS aliased_title, body ...
$builder->select(['pk' => 'id', 'aliased_title' => 'title', 'body']);
// 使用闭包
$builder->select(function ($builder) {
return ['id', 'title', 'body'];
});
Where 条件
生成条件:
// WHERE id = 1
$builder->where(['id' => 1]);
// WHERE id > 1
$builder->where(['id >' => 1]);
如你所见,你可以在字段名后加一个空格来使用任何运算符。添加多个条件也很容易:
<?php
$builder->where(['id >' => 1])->andWhere(['title' => 'My Title']);
// 等同于
$builder->where(['id >' => 1, 'title' => 'My title']);
// WHERE id > 1 OR title = 'My title'
$builder->where(['OR' => ['id >' => 1, 'title' => 'My title']]);
对于更复杂的条件,你可以使用闭包和表达式对象:
<?php
// 条件默认通过 AND 连接
$builder
->select('*')
->from('articles')
->where(function ($exp) {
return $exp
->eq('author_id', 2)
->eq('published', true)
->notEq('spam', true)
->gt('view_count', 10);
});
结果为:
SELECT * FROM articles
WHERE
author_id = 2
AND published = 1
AND spam != 1
AND view_count > 10
组合表达式也是可能的:
<?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);
});
它会生成:
SELECT *
FROM articles
WHERE
NOT (author_id = 2 OR author_id = 5)
AND view_count <= 10
当使用表达式对象时,你可以使用以下方法来创建条件:
eq()创建一个相等条件。notEq()创建一个不相等条件。like()创建一个使用LIKE运算符的条件。notLike()创建一个否定的LIKE条件。in()创建一个使用IN的条件。notIn()创建一个使用IN的否定条件。gt()创建一个>条件。gte()创建一个>=条件。lt()创建一个<条件。lte()创建一个<=条件。isNull()创建一个IS NULL条件。isNotNull()创建一个否定的IS NULL条件。
聚合和 SQL 函数
<?php
// 结果为 SELECT COUNT(*) count FROM ...
$builder->select(['count' => $builder->func()->count('*')]);
许多常用函数可以通过 func() 方法创建:
sum()计算总和。参数将被视为字面值。avg()计算平均值。参数将被视为字面值。min()计算列的最小值。参数将被视为字面值。max()计算列的最大值。参数将被视为字面值。count()计算数量。参数将被视为字面值。concat()将两个值连接在一起。除非标记为字面量,否则参数将被视为绑定参数。coalesce()合并值。除非标记为字面量,否则参数将被视为绑定参数。dateDiff()获取两个日期/时间之间的差异。除非标记为字面量,否则参数将被视为绑定参数。now()接受 ‘time’ 或 ‘date’ 作为参数,允许你获取当前时间或当前日期。
为 SQL 函数提供参数时,有两种参数类型可用:字面量参数和绑定参数。字面量参数允许你引用列或其他 SQL 字面量。绑定参数可用于安全地将用户数据添加到 SQL 函数中。例如:
<?php
// 生成:
// SELECT CONCAT(title, ' NEW') ...
$concat = $builder->func()->concat([
'title' => 'literal',
' NEW'
]);
$query->select(['title' => $concat]);
从查询中获取结果
一旦你创建了查询,你会想从中检索行。有几种方法可以做到这一点:
<?php
// 遍历查询
foreach ($builder as $row) {
echo $row['title'];
}
// 获取语句并获取所有结果
$results = $builder->execute()->fetchAll('assoc');
创建插入查询
创建插入查询也是可能的:
<?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();
为了提高性能,你可以使用另一个构建器对象作为插入查询的值:
<?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'));
上面的代码将生成:
INSERT INTO names (first_name, last_name)
(SELECT fname, lname FROM USERS where is_active = 1)
创建更新查询
创建更新查询与插入和选择都相似:
<?php
$builder = $this->getQueryBuilder('update');
$builder
->update('users')
->set('fname', 'Snow')
->where(['fname' => 'Jon'])
->execute();
创建删除查询
最后,是删除查询:
<?php
$builder = $this->getQueryBuilder('delete');
$builder
->delete('users')
->where(['accepted_gdpr' => false])
->execute();