编写迁移

Phinx 依靠迁移来转换您的数据库。每个迁移都由一个位于唯一文件中的 PHP 类表示。建议使用 Phinx PHP API 编写迁移,但也支持使用原始 SQL。

创建新的迁移

生成迁移文件骨架

让我们开始创建新的 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 Method.
     *
     * Write your reversible migrations using this method.
     *
     * More information on writing migrations is available here:
     * https://book.cakephp.com.cn/phinx/0/en/migrations.html#the-change-method
     *
     * Remember to call "create()" or "update()" and NOT "save()" when working
     * with the Table class.
     *
     */
    public function change()
    {

    }
}

所有 Phinx 迁移都扩展自 AbstractMigration 类。此类提供了创建数据库迁移所需的必要支持。数据库迁移可以以多种方式转换您的数据库,例如创建新表、插入行、添加索引和修改列。

Change 方法

Phinx 0.2.0 引入了一项名为可逆迁移的新功能。此功能现已成为默认的迁移方法。使用可逆迁移,您只需定义 up 逻辑,Phinx 就可以自动计算出如何进行反向迁移。例如

<?php

use Phinx\Migration\AbstractMigration;

class CreateUserLoginsTable extends AbstractMigration
{
    public function change()
    {
        // create the table
        $table = $this->table('user_logins');
        $table->addColumn('user_id', 'integer')
              ->addColumn('created', 'datetime')
              ->create();
    }
}

执行此迁移时,Phinx 将在向上迁移时创建 user_logins 表,并在向下迁移时自动计算出如何删除该表。请注意,当存在 change 方法时,Phinx 将自动忽略 updown 方法。如果您需要使用这些方法,建议创建一个单独的迁移文件。

注意

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()
    {
        // create the table
        $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 方法

Phinx 在执行迁移之前会运行 shouldExecute() 方法。这可用于阻止在此时执行迁移。它默认始终返回 true。您可以在自定义 AbstractMigration 实现中重写它。

执行查询

可以使用 execute()query() 方法执行查询。 execute() 方法返回受影响的行数,而 query() 方法返回结果作为 PDOStatement。这两种方法都接受一个可选的第二个参数 $params,它是一个元素数组,如果使用它,将导致底层连接使用准备好的语句。

<?php

use Phinx\Migration\AbstractMigration;

class MyNewMigration extends AbstractMigration
{
    /**
     * Migrate Up.
     */
    public function up()
    {
        // execute()
        $count = $this->execute('DELETE FROM users'); // returns the number of affected rows

        // query()
        $stmt = $this->query('SELECT * FROM users'); // returns PDOStatement
        $rows = $stmt->fetchAll(); // returns the result as an array

        // using prepared queries
        $count = $this->execute('DELETE FROM users WHERE id = ?', [5]);
        $stmt = $this->query('SELECT * FROM users WHERE id > ?', [5]); // returns PDOStatement
        $rows = $stmt->fetchAll();
    }

    /**
     * Migrate Down.
     */
    public function down()
    {

    }
}

注意

这些命令使用 PHP 数据对象 (PDO) 扩展运行,该扩展定义了用于访问 PHP 中数据库的轻量级一致接口。在使用 execute() 命令之前,请始终确保您的查询符合 PDO 规范。这在使用 DELIMITER 时尤其重要,因为在插入存储过程或触发器时,DELIMITER 不支持。

注意

如果您希望一次执行多个查询,您可能也不应使用这些函数的准备好的变体。使用准备好的查询时,PDO 只能一次执行一个查询。

警告

在对 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
{
    /**
     * Migrate Up.
     */
    public function up()
    {
        // fetch a user
        $row = $this->fetchRow('SELECT * FROM users');

        // fetch an array of messages
        $rows = $this->fetchAll('SELECT * FROM messages');
    }

    /**
     * Migrate Down.
     */
    public function down()
    {

    }
}

插入数据

Phinx 使得将数据插入到表中变得十分容易。 虽然此功能用于 种子功能,但您也可以在迁移中自由使用插入方法。

<?php

use Phinx\Migration\AbstractMigration;

class NewStatus extends AbstractMigration
{
    /**
     * Migrate Up.
     */
    public function up()
    {
        $table = $this->table('status');

        // inserting only one row
        $singleRow = [
            'id'    => 1,
            'name'  => 'In Progress'
        ];

        $table->insert($singleRow)->saveData();

        // inserting multiple rows
        $rows = [
            [
              'id'    => 2,
              'name'  => 'Stopped'
            ],
            [
              'id'    => 3,
              'name'  => 'Queued'
            ]
        ];

        $table->insert($rows)->saveData();
    }

    /**
     * Migrate Down.
     */
    public function down()
    {
        $this->execute('DELETE FROM status');
    }
}

注意

您不能在 change() 方法中使用插入方法。 请使用 up()down() 方法。

使用表

表对象

表对象是 Phinx 提供的最有用的 API 之一。 它允许您使用 PHP 代码轻松地操作数据库表。 您可以在数据库迁移中调用 table() 方法来获取表对象的实例。

<?php

use Phinx\Migration\AbstractMigration;

class MyNewMigration extends AbstractMigration
{
    /**
     * Migrate Up.
     */
    public function up()
    {
        $table = $this->table('tableName');
    }

    /**
     * Migrate Down.
     */
    public function down()
    {

    }
}

然后,您可以使用表对象提供的方法来操作此表。

保存更改

在使用表对象时,Phinx 会将某些操作存储在待处理更改缓存中。 完成对表的更改后,您必须保存它们。 为了执行此操作,Phinx 提供了三种方法,create()update()save()create() 将首先创建表,然后运行待处理更改。 update() 将仅运行待处理更改,应在表已存在时使用。 save() 是一个辅助函数,它首先检查表是否存在,如果不存在,则运行 create(),否则运行 update()

如上所述,在使用 change() 迁移方法时,您应始终使用 create()update(),切勿使用 save(),因为否则迁移和回滚可能会导致不同的状态,这是因为 save() 在运行迁移时调用 create(),而在回滚时调用 update()。 在使用 up()/down() 方法时,可以使用 save() 或更明确的方法。

如果对使用表有任何疑问,建议始终调用相应的功能并将所有待处理更改提交到数据库。

创建表

使用表对象创建表非常容易。 让我们创建一个表来存储用户集合。

<?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() 方法为用户名和电子邮件列创建唯一索引。 最后,调用 create() 将更改提交到数据库。

注意

Phinx 会自动为每个表创建一个名为 id 的自动递增主键列。

id 选项设置自动创建的身份字段的名称,而 primary_key 选项选择用作主键的字段或字段。 id 将始终覆盖 primary_key 选项,除非它被设置为 false。 如果不需要主键,请将 id 设置为 false,而不要指定 primary_key,这样就不会创建主键。

要指定备用主键,您可以在访问表对象时指定 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

主键是否为 signed (默认为 ``false``)

limit

设置主键的最大长度

默认情况下,主键是 unsigned。 要简单地将其设置为 signed,只需使用 true 值传递 signed 选项

<?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
{
    /**
     * Migrate Up.
     */
    public function up()
    {
        $exists = $this->hasTable('users');
        if ($exists) {
            // do something
        }
    }

    /**
     * Migrate Down.
     */
    public function down()
    {

    }
}

删除表

可以使用 drop() 方法轻松删除表。 建议在 down() 方法中重新创建表。

请注意,与 Table 类中的其他方法一样,drop 也需要在最后调用 save() 才能执行。 这使 Phinx 能够在涉及多个表时智能地规划迁移。

<?php

use Phinx\Migration\AbstractMigration;

class MyNewMigration extends AbstractMigration
{
    /**
     * Migrate Up.
     */
    public function up()
    {
        $this->table('users')->drop()->save();
    }

    /**
     * Migrate Down.
     */
    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();
    }
}

重命名表

要重命名表,请访问表对象的实例,然后调用 rename() 方法。

<?php

use Phinx\Migration\AbstractMigration;

class MyNewMigration extends AbstractMigration
{
    /**
     * Migrate Up.
     */
    public function up()
    {
        $table = $this->table('users');
        $table
            ->rename('legacy_users')
            ->update();
    }

    /**
     * Migrate Down.
     */
    public function down()
    {
        $table = $this->table('legacy_users');
        $table
            ->rename('users')
            ->update();
    }
}

更改主键

要更改现有表的主键,请使用 changePrimaryKey() 方法。 传入要包含在主键中的列名或列名数组,或传入 null 以删除主键。 请注意,提到的列必须添加到表中,不会隐式添加。

<?php

use Phinx\Migration\AbstractMigration;

class MyNewMigration extends AbstractMigration
{
    /**
     * Migrate Up.
     */
    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();
    }

    /**
     * Migrate Down.
     */
    public function down()
    {

    }
}

更改表注释

要更改现有表的注释,请使用 changeComment() 方法。 传入一个字符串作为新的表注释,或传入 null 以删除现有注释。

<?php

use Phinx\Migration\AbstractMigration;

class MyNewMigration extends AbstractMigration
{
    /**
     * Migrate Up.
     */
    public function up()
    {
        $users = $this->table('users');
        $users
            ->addColumn('username', 'string', ['limit' => 20])
            ->addColumn('password', 'string', ['limit' => 40])
            ->save();

        $users
            ->changeComment('This is the table with users auth information, password should be encrypted')
            ->save();
    }

    /**
     * Migrate Down.
     */
    public function down()
    {

    }
}

使用列

有效列类型

列类型以字符串形式指定,可以是以下之一

  • binary

  • boolean

  • char

  • date

  • datetime

  • decimal

  • float

  • double

  • smallinteger

  • integer

  • biginteger

  • string

  • text

  • time

  • timestamp

  • uuid

此外,MySQL 适配器支持 enumsetblobtinyblobmediumbloblongblobbitjson 列类型 (MySQL 5.7 及更高版本中的 json)。 当提供限制值并使用 binaryvarbinaryblob 及其子类型时,保留的列类型将基于所需的长度 (有关详细信息,请参阅 限制选项和 MySQL

此外,Postgres 适配器支持 intervaljsonjsonbuuidcidrinetmacaddr 列类型 (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)*

对于 enumset

选项

描述

values

可以是逗号分隔的列表或值数组

对于 smallintegerintegerbiginteger

选项

描述

identity

启用或禁用自动递增(如果启用,如果未设置 null 选项,将设置 null: false

signed

启用或禁用 unsigned 选项 *(仅适用于 MySQL)*

对于 Postgres,当使用 identity 时,它将使用适合整数大小的 serial 类型,因此 smallinteger 将为您提供 smallserialinteger 提供 serial,而 biginteger 提供 bigserial

对于 timestamp

选项

描述

default

设置默认值(与 CURRENT_TIMESTAMP 一起使用)

update

设置在更新行时触发的操作(与 CURRENT_TIMESTAMP 一起使用) *(仅适用于 MySQL)*

timezone

timetimestamp 列启用或禁用 with time zone 选项 *(仅适用于 Postgres)*

您可以使用 addTimestamps() 方法将 created_atupdated_at 时间戳添加到表中。此方法接受三个参数,前两个参数允许为列设置替代名称,第三个参数允许您为列启用 timezone 选项。这些参数的默认值分别是 created_atupdated_atfalse。对于第一个和第二个参数,如果您提供 null,则将使用默认名称,如果您提供 false,则不会创建该列。请注意,尝试将两者都设置为 false 将抛出 \RuntimeException。此外,您可以使用 addTimestampsWithTimezone() 方法,它是 addTimestamps() 的别名,它将始终将第三个参数设置为 true(请参阅下面的示例)。created_at 列将具有设置为 CURRENT_TIMESTAMP 的默认值。仅对于 MySQL,update_at 列将具有设置为 CURRENT_TIMESTAMP 的更新。

<?php

use Phinx\Migration\AbstractMigration;

class MyNewMigration extends AbstractMigration
{
    /**
     * Migrate Change.
     */
    public function change()
    {
        // Use defaults (without timezones)
        $table = $this->table('users')->addTimestamps()->create();
        // Use defaults (with timezones)
        $table = $this->table('users')->addTimestampsWithTimezone()->create();

        // Override the 'created_at' column name with 'recorded_at'.
        $table = $this->table('books')->addTimestamps('recorded_at')->create();

        // Override the 'updated_at' column name with 'amended_at', preserving timezones.
        // The two lines below do the same, the second one is simply cleaner.
        $table = $this->table('books')->addTimestamps(null, 'amended_at', true)->create();
        $table = $this->table('users')->addTimestampsWithTimezone(null, 'amended_at')->create();

        // Only add the created_at column to the table
        $table = $this->table('books')->addTimestamps(null, false);
        // Only add the updated_at column to the table
        $table = $this->table('users')->addTimestamps(false);
        // Note, setting both false will throw a \RuntimeError
    }
}

对于 boolean

选项

描述

signed

启用或禁用 unsigned 选项 *(仅适用于 MySQL)*

对于 stringtext

选项

描述

collation

设置与表默认值不同的排序规则 *(仅适用于 MySQL)*

encoding

设置与表默认值不同的字符集 *(仅适用于 MySQL)*

对于外键定义

选项

描述

update

设置在更新行时触发的操作

delete

设置在删除行时触发的操作

constraint

设置外键约束使用的名称

您可以将一个或多个这些选项传递给任何带有可选第三个参数数组的列。

Limit 选项和 MySQL

使用 MySQL 适配器时,在处理限制时需要考虑几件事

  • 在 MySQL 5.7 或更低版本或 MyISAM 存储引擎上使用 string 主键或索引时,如果 utf8mb4_unicode_ci 是默认字符集,则必须指定小于或等于 191 的限制,或者使用其他字符集。

  • 可以对 integertextblobtinyblobmediumbloblongblob 列进行数据库列类型的额外提示。使用以下选项之一的 limit 将相应地修改列类型

Limit

Column Type

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

对于 binaryvarbinary 类型,如果限制设置为大于允许的 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 系统提供特定于它们的附加列类型和默认值。如果您不想让迁移与 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 列、不匹配的整数类型(长 vs. 大整数等)。

注意

对于 integertextblob 列,您可以使用 MySQL 和 Postgress 适配器类中的特殊常量。

您甚至可以自定义某些内部类型以添加您自己的默认选项,但某些列选项无法在数据模型中覆盖(某些选项是固定的,例如 limit 用于 uuid 特殊数据类型)。

# Some examples of custom data types
data_domain:
    file:
        type: blob
        limit: BLOB_LONG    # For MySQL DB. Uses MysqlAdapter::BLOB_LONG
    boolean:
        type: boolean       # Customization of the boolean to be unsigned
        signed: false
    image_type:
        type: enum          # Enums can use YAML lists or a comma separated string
        values:
            - gif
            - jpg
            - png

获取列列表

要检索所有表列,只需创建一个 table 对象并调用 getColumns() 方法。此方法将返回包含基本信息的 Column 类数组。下面的示例

<?php

use Phinx\Migration\AbstractMigration;

class ColumnListMigration extends AbstractMigration
{
    /**
     * Migrate Up.
     */
    public function up()
    {
        $columns = $this->table('users')->getColumns();
        ...
    }

    /**
     * Migrate Down.
     */
    public function down()
    {
        ...
    }
}

按名称获取列

要检索一个表列,只需创建一个 table 对象并调用 getColumn() 方法。此方法将返回一个包含基本信息的 Column 类,或者在列不存在时返回 NULL。下面的示例

<?php

use Phinx\Migration\AbstractMigration;

class ColumnListMigration extends AbstractMigration
{
    /**
     * Migrate Up.
     */
    public function up()
    {
        $column = $this->table('users')->getColumn('email');
        ...
    }

    /**
     * Migrate Down.
     */
    public function down()
    {
        ...
    }
}

检查列是否存在

您可以使用 hasColumn() 方法检查表中是否已经存在某个列。

<?php

use Phinx\Migration\AbstractMigration;

class MyNewMigration extends AbstractMigration
{
    /**
     * Change Method.
     */
    public function change()
    {
        $table = $this->table('user');
        $column = $table->hasColumn('username');

        if ($column) {
            // do something
        }

    }
}

重命名列

要重命名列,请访问 Table 对象的实例,然后调用 renameColumn() 方法。

<?php

use Phinx\Migration\AbstractMigration;

class MyNewMigration extends AbstractMigration
{
    /**
     * Migrate Up.
     */
    public function up()
    {
        $table = $this->table('users');
        $table->renameColumn('bio', 'biography')
              ->save();
    }

    /**
     * Migrate Down.
     */
    public function down()
    {
        $table = $this->table('users');
        $table->renameColumn('biography', 'bio')
               ->save();
    }
}

在另一个列之后添加列

使用 MySQL 适配器添加列时,您可以使用 after 选项指定其位置,其中其值为要将其放置在后面的列的名称。

<?php

use Phinx\Migration\AbstractMigration;

class MyNewMigration extends AbstractMigration
{
    /**
     * Change Method.
     */
    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
{
    /**
     * Migrate up.
     */
    public function up()
    {
        $table = $this->table('users');
        $table->removeColumn('short_name')
              ->save();
    }
}

指定列限制

您可以使用 limit 选项限制列的最大长度。

<?php

use Phinx\Migration\AbstractMigration;

class MyNewMigration extends AbstractMigration
{
    /**
     * Change Method.
     */
    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
{
    /**
     * Migrate Up.
     */
    public function up()
    {
        $table = $this->table('users');
        $table->addColumn('city', 'string')
              ->addIndex(['city'])
              ->save();
    }

    /**
     * Migrate Down.
     */
    public function down()
    {

    }
}

默认情况下,Phinx 指示数据库适配器创建普通索引。我们可以将一个额外的参数 unique 传递给 addIndex() 方法来指定唯一索引。我们还可以使用 name 参数显式地指定索引的名称,索引列的排序顺序也可以使用 order 参数来指定。order 参数接受一个包含列名称和排序顺序键值对的数组。

<?php

use Phinx\Migration\AbstractMigration;

class MyNewMigration extends AbstractMigration
{
    /**
     * Migrate Up.
     */
    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();
    }

    /**
     * Migrate Down.
     */
    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
{
    /**
     * Migrate Up.
     */
    public function up()
    {
        $table = $this->table('users');
        $table->removeIndex(['email'])
            ->save();

        // alternatively, you can delete an index by its name, ie:
        $table->removeIndexByName('idx_users_email')
            ->save();
    }

    /**
     * Migrate Down.
     */
    public function down()
    {

    }
}

使用外键

Phinx 支持在您的数据库表上创建外键约束。让我们向一个示例表添加一个外键

<?php

use Phinx\Migration\AbstractMigration;

class MyNewMigration extends AbstractMigration
{
    /**
     * Migrate Up.
     */
    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();

    }

    /**
     * Migrate Down.
     */
    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
{
    /**
     * Migrate Up.
     */
    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();
    }

    /**
     * Migrate Down.
     */
    public function down()
    {

    }
}

我们可以使用 constraint 参数添加命名外键。此功能自 Phinx 0.6.5 版本起支持。

<?php

use Phinx\Migration\AbstractMigration;

class MyNewMigration extends AbstractMigration
{
    /**
     * Migrate Up.
     */
    public function up()
    {
        $table = $this->table('your_table');
        $table->addForeignKey('foreign_id', 'reference_table', ['id'],
                            ['constraint' => 'your_foreign_key_name']);
              ->save();
    }

    /**
     * Migrate Down.
     */
    public function down()
    {

    }
}

我们也可以轻松地检查外键是否存在。

<?php

use Phinx\Migration\AbstractMigration;

class MyNewMigration extends AbstractMigration
{
    /**
     * Migrate Up.
     */
    public function up()
    {
        $table = $this->table('tag_relationships');
        $exists = $table->hasForeignKey('tag_id');
        if ($exists) {
            // do something
        }
    }

    /**
     * Migrate Down.
     */
    public function down()
    {

    }
}

最后,要删除外键,请使用 dropForeignKey 方法。

请注意,与 Table 类中的其他方法一样,dropForeignKey 也需要在最后调用 save() 才能执行。这使 phinx 能够在涉及多个表时智能地计划迁移。

<?php

use Phinx\Migration\AbstractMigration;

class MyNewMigration extends AbstractMigration
{
    /**
     * Migrate Up.
     */
    public function up()
    {
        $table = $this->table('tag_relationships');
        $table->dropForeignKey('tag_id')->save();
    }

    /**
     * Migrate Down.
     */
    public function down()
    {

    }
}

使用查询构建器

将数据库结构更改与数据更改配对并不少见。例如,您可能希望将 users 表中的两个列中的数据迁移到新创建的表中。对于这类场景,Phinx 提供对 Query builder 对象的访问权限,您可以使用该对象执行复杂的 SELECTUPDATEINSERTDELETE 语句。

Query builder 由 cakephp/database 项目提供,并且应该很容易使用,因为它非常类似于普通 SQL。访问查询构建器是通过调用 getQueryBuilder(string $type) 函数完成的。 string $type 选项为 ‘select’‘insert’‘update’‘delete’

<?php

use Phinx\Migration\AbstractMigration;

class MyNewMigration extends AbstractMigration
{
    /**
     * Migrate Up.
     */
    public function up()
    {
        $builder = $this->getQueryBuilder('select');
        $statement = $builder->select('*')->from('users')->execute();
        var_dump($statement->fetchAll());
    }
}

选择字段

将字段添加到 SELECT 子句

<?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 条件

生成条件

// WHERE id = 1
$builder->where(['id' => 1]);

// WHERE id > 1
$builder->where(['id >' => 1]);

如您所见,您可以通过在字段名称后添加一个空格来使用任何运算符。添加多个条件也很容易

<?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']]);

对于更复杂的条件,您可以使用闭包和表达式对象

<?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);
    });

这将产生

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
// Results in 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
// Generates:
// SELECT CONCAT(title, ' NEW') ...;
$concat = $builder->func()->concat([
    'title' => 'literal',
    ' NEW'
]);
$query->select(['title' => $concat]);

从查询中获取结果

创建查询后,您需要从查询中检索行。有几种方法可以做到这一点

<?php
// Iterate the query
foreach ($builder as $row) {
    echo $row['title'];
}

// Get the statement and fetch all results
$results = $builder->execute()->fetchAll('assoc');

创建 Insert 查询

创建插入查询也是可能的

<?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)

创建 Update 查询

创建更新查询类似于插入和选择

<?php
$builder = $this->getQueryBuilder('update');
$builder
    ->update('users')
    ->set('fname', 'Snow')
    ->where(['fname' => 'Jon'])
    ->execute();

创建 Delete 查询

最后,删除查询

<?php
$builder = $this->getQueryBuilder('delete');
$builder
    ->delete('users')
    ->where(['accepted_gdpr' => false])
    ->execute();