Database Migrations in PHP With Phinx

1. Migration là gì

Database Migration là một trong những công việc thường gặp trong mỗi dự án. Chúng ta muốn chia sẻ cấu trúc dữ liệu, chia sẻ data? Nếu phải export dữ liệu, cấu trúc DB rồi chờ import thì chắc chắn sẽ tốn rất nhiều thời gian. Vì thế, migration trở nên thiết yếu trong các project php. Và đối với những project không sử dụng framework, đó là một giải pháp hữu hiệu để quản lý cập nhật database.

Bạn quản lí code bằng git như database, một thành phần quan trọng không kém là database thì không được như vậy. Vì vậy migration được tạo ra để quản lí sự thay đổi cấu trúc dữ liệu. Việc sử dụng migration sẽ giúp bạn giảm nhẹ công việc khi deploy ứng dụng.

Migration là một tính năng được "vay mượn" từ Rails - Ruby framework, rất hữu dụng khi chúng ta thay đổi database (thường là MySQL) và muốn quản lý version của những lần thay đổi đó.

Đa phần các framework lớn đều tích hợp hoặc dựng sẵn một migration chẳng hạn:

Hôm nay chúng ta sẽ tìm hiểu về Phinx Migration. Phinx được tạo ra bởi Rob Morgan. Hiện nay, Phinx được chuyển sang quyền sở hữu của CakePHP.

2. Mục tiêu của Phinx :

  • Dễ dàng, linh hoạt chuyển đổi.
  • Sử dụng độc lập.
  • Cài đặt đơn giản.
  • Dễ dàng sử dụng qua cửa sổ dòng lệnh.
  • Dễ dàng tích hợp với các công cụ khác như Phing, PHPUnit hoặc vào các framework khác.

3. Cài đặt phinx.

phinx có thể cài đặt dễ dàng bằng composer. Bạn có thể thêm những dòng sau vào composer.json và chạy composer install

{
  "require": {
    "robmorgan/phinx": "*"
  },
}

Hoặc gọi trực tiếp bằng lệnh:

composer require robmorgan/phinx --dev

Kiểm tra xem đã cài đặt thành công chưa nào:

php vendor/bin/phinx

Kết quả hiện lên như thế này là chúng ta đã cài đặt thành công rồi 😄

Phinx by Rob Morgan - https://phinx.org. 0.8.1

Usage:
  command [options] [arguments]

Options:
  -h, --help            Display this help message
  -q, --quiet           Do not output any message
  -V, --version         Display this application version
      --ansi            Force ANSI output
      --no-ansi         Disable ANSI output
  -n, --no-interaction  Do not ask any interactive question
  -v|vv|vvv, --verbose  Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug

Available commands:
  breakpoint   Manage breakpoints
  create       Create a new migration
  help         Displays help for a command
  init         Initialize the application for Phinx
  list         Lists commands
  migrate      Migrate the database
  rollback     Rollback the last or to a specific migration
  status       Show migration status
  test         Verify the configuration file
 seed
  seed:create  Create a new database seeder
  seed:run     Run database seeders

4. Cấu hình cho Phinx.

Phinx khá linh hoạt cho việc hỗ trợ các loại cơ sở dữ liệu khác nhau:

  • MySQL: specify the mysql adapter.
  • PostgreSQL: specify the pgsql adapter.
  • SQLite: specify the sqlite adapter.
  • SQL Server: specify the sqlsrv adapter.

Để có thể config cho phinx, ta gõ lệnh:

php vendor/bin/phinx init

mặc định phinx sẽ tạo ra file phinx.yml ở ngay root source. Thử mở file này lên xem nào:

paths:
    migrations: %%PHINX_CONFIG_DIR%%/db/migrations
    seeds: %%PHINX_CONFIG_DIR%%/db/seeds

environments:
    default_migration_table: phinxlog
    default_database: development
    production:
        adapter: mysql
        host: localhost
        name: production_db
        user: root
        pass: ''
        port: 3306
        charset: utf8

    development:
        adapter: mysql
        host: localhost
        name: development_db
        user: root
        pass: ''
        port: 3306
        charset: utf8

    testing:
        adapter: mysql
        host: localhost
        name: testing_db
        user: root
        pass: ''
        port: 3306
        charset: utf8

version_order: creation

File cấu hình của phinx sử dụng định dạng YAML, ngoài ra chúng ta có thể sử dụng định dạng là json hoặc php. Có vẻ khá linh hoạt cho chúng ta đúng không nào. Ở đây, chúng ta tập trung chủ yếu vào định dạng YAML thôi nhé 😃.

Bây giờ cùng tìm hiểu cấu trúc file cấu hình nào.

4.1. Paths

a. Migration Paths:

  • Tùy chọn đầu tiên là cấu hình đường dẫn đến nơi chứa file migrations: %%PHINX_CONFIG_DIR%%/db/migrations

%%PHINX_CONFIG_DIR%% được thay thế bằng đường dẫn thư mục gốc, hoặc nơi đặt tệp tin phinx.yml.

Để cấu hình đường dẫn, chúng ta có thể chọn đặt một đường dẫn duy nhất:

paths:
    migrations: /your/full/path

hoặc cấu hình nhiều đường dẫn:

paths:
    migrations:
        - application/module1/migrations
        - application/module2/migrations

b. Seed Paths

Tùy chọn này cho phép chúng ta đặt đường dẫn tới thư mục chứa các file seed. Tương tự như cấu hình path, chúng ta có thể cấu hình 1 hoặc nhiều đường dẫn chứa các file seed:

# cấu hình 1 đường dẫn.
paths:
    seeds: /your/full/path
    
# cấu hình nhiều đường dẫn.
paths:
    seeds:
        - /your/full/path1
        - /your/full/path2
        
# hoặc sử dụng config dir:
paths:
    seeds: %%PHINX_CONFIG_DIR%%/your/relative/path

4.2. Environments:

Một trong những điểm khác biệt của phinx đó là có thể cấu hình cho nhiều môi trường khác nhau.

production:
        adapter: mysql
        host: localhost
        name: production_db
        user: root
        pass: ''
        port: 3306
        charset: utf8

    development:
        adapter: mysql
        host: localhost
        name: development_db
        user: root
        pass: ''
        port: 3306
        charset: utf8

    testing:
        adapter: mysql
        host: localhost
        name: testing_db
        user: root
        pass: ''
        port: 3306
        charset: utf8

a. Tiền tố, hậu tố cho bảng

Nếu muốn đặt tiền tố, hậu tố cho các bảng thì sao? Phinx có thể đáp ứng dễ dàng ví dụ:

Table Prefix and Suffix
You can define a table prefix and table suffix:

environments:
    development:
        ....
        table_prefix: dev_
        table_suffix: _v1
    testing:
        ....
        table_prefix: test_
        table_suffix: _v2

b. Kết nối qua sockets

Để có thể kết nối qua sockets, chúng ta có thể config như sau:

production:
    adapter: mysql
    name: production_db
    user: root
    pass: ''
    unix_socket: /var/run/mysql/mysql.sock
    charset: utf8

Để tìm hiểu thêm những config đặc biệt hơn hay áp dụng cho các kiểu cơ sở dữ liệu khác nhau, chúng ta có thể tìm hiểu thêm tại Configurations.

#5. Viết file Migrations. Để kiểm tra xem chúng ta có config đúng hay không, chúng ta sử dụng lệnh:

php vendor/bin/phinx status

5.1. Tạo file migrations

Để tạo một file migration, chúng ta sử dụng lệnh create của phinx:

php vendor/bin/phinx create MyNewMigration

Chạy câu lệnh trên sẽ tạo một file migration theo định dạng: YYYYMMDDHHMMSS_my_new_migration.php. Trường hợp đặt nhiều đường dẫn, chúng ta cần chọn đường dẫn để có thể tiếp tục tạo file migration. Cấu trúc file migration được tạo ra:

<?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:
     * http://docs.phinx.org/en/latest/migrations.html#the-abstractmigration-class
     *
     * The following commands can be used in this method and Phinx will
     * automatically reverse them when rolling back:
     *
     *    createTable
     *    renameTable
     *    addColumn
     *    renameColumn
     *    addIndex
     *    addForeignKey
     *
     * Remember to call "create()" or "update()" and NOT "save()" when working
     * with the Table class.
     */
    public function change()
    {

    }
}

Tất cả các file migration đều được kế thừa từ class AbstractMigration. Chúng ta có thể sửa đổi thành class khác trong phần Configurations.

5.2. Các phương thức của file migration.

Một file migrations bao gồm các phương thức:

  • up(), dùng để chạy các thay đổi database lần đầu được thực hiện, như là thêm một cột hoàn toàn mới, bảng hoàn toàn mới ...
  • down(), dùng cho những lúc bạn muốn "undo", "rollback" lại các thay đổi lần trước.
  • change(), phinx sẽ ignore hết các đoạn code viết vào up hay down ở trên cùng một file.

5.3. Các phương thức của phinx.

a. sử dụng trong file migration để tương tác với DB.

  • createTable
  • renameTable
  • addColumn
  • renameColumn
  • addIndex
  • addForeignKey

Ví dụ:

<?php
use Phinx\Migration\AbstractMigration;

class CityMigration extends AbstractMigration {
    public function up() {
        $table = $this->table('city');
        $table
            ->addColumn('name', 'string', array('limit' => 20))
            ->addColumn('country', 'string', array('limit' => 20))
            ->addColumn('population', 'integer', ['limit'=>5000000])
            ->save();
    }
    public function down() {}
}

b. Thực thi câu lệnh SQL.

Chúng ta có thể thực thi câu lệnh truy vấn bằng các phương thức:

  • execute() trả về số hàng ảnh hưởng.
  • query() trả về kết quả của PDOStatement

Ví dụ:

<?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()
        $rows = $this->query('SELECT * FROM users'); // returns the result as an array
    }

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

    }
}

c. Fetching Rows.

Chúng ta có thể fetch database bằng các phương thức:

  • fetchRow() sẽ trả về một hàng duy nhất.
  • fetchAll() sẽ trả về tất cả các hàng.

Ví dụ:

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

    }
}

d. Chèn dữ liệu.

Chúng ta có thể dễ dàng chèn dữ liệu không chỉ ở file seed mà còn có thể chèn dữ liệu ngay trong file migration. Ví dụ:

<?php

use Phinx\Migration\AbstractMigration;

class NewStatus extends AbstractMigration
{
    /**
     * Migrate Up.
     */
    public function up()
    {
        // inserting only one row
        $singleRow = [
            'id'    => 1,
            'name'  => 'In Progress'
        ];

        $table = $this->table('status');
        $table->insert($singleRow);
        $table->saveData();

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

        // this is a handy shortcut
        $this->insert('status', $rows);
    }

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

5.4. Làm việc với bảng.

Phinx cung cấp hệ thống API tương đối dễ dàng để thao tác với các bảng:

<?php

use Phinx\Migration\AbstractMigration;

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

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

    }
}

Ở trên, chúng ta chỉ định rõ tên bảng cần làm việc để xác định đối tượng bảng trong cơ sở dữ liệu.

a. Các kiểu dữ liệu. Trong phinx, chúng ta cần chỉ định rõ kiểu dữ liệu cho các cột:

  • biginteger
  • binary
  • boolean
  • date
  • datetime
  • decimal
  • float
  • integer
  • string
  • text
  • time
  • timestamp
  • uuid

Ngoài ra, kiểu cơ sở dữ liệu MySQL hỗ trợ các kiểu cột enum, set, blob và json. (Json trong MySQL 5,7 và ở trên)

Ngoài ra, kiểu cơ sở dữ liệu Postgres hỗ trợ các loại cột nhỏ, json, jsonb và uuid (PostgreSQL 9.3 trở lên).

Chúng ta có thể tìm hiểu kỹ hơn về các thao tác ở tài liệu của phinx:

6. Database Seeding.

6.1 Tạo file seed.

Để tạo một file seeder ta sử dụng lệnh:

php vendor/bin/phinx seed:create UserSeeder

Nếu chúng ta đặt nhiều đường dẫn chứa các file seed, chúng ta cần xác định đường dẫn nào sẽ lưu các file seeder thì mới có thể tiếp tục.

File seeder được tạo ra có dạng:

<?php

use Phinx\Seed\AbstractSeed;

class MyNewSeeder extends AbstractSeed
{
    /**
     * Run Method.
     *
     * Write your database seeder using this method.
     *
     * More information on writing seeders is available here:
     * http://docs.phinx.org/en/latest/seeding.html
     */
    public function run()
    {

    }
}

6.2 Chèn dữ liệu.

Để chèn dữ liệu vào bảng, phinx có thể tích hợp linh hoạt vào các framework, các công cụ khác. Vì thế chúng ta có rất nhiều các để có thể chèn dữ liệu vào cơ sở dữ liệu.

  • Sử dụng đối tượng bảng:
<?php

use Phinx\Seed\AbstractSeed;

class PostsSeeder extends AbstractSeed
{
    public function run()
    {
        $data = array(
            array(
                'body'    => 'foo',
                'created' => date('Y-m-d H:i:s'),
            ),
            array(
                'body'    => 'bar',
                'created' => date('Y-m-d H:i:s'),
            )
        );

        $posts = $this->table('posts');
        $posts->insert($data)
              ->save();
    }
}
  • Sử dụng thư viện Faker.
<?php

use Phinx\Seed\AbstractSeed;

class UserSeeder extends AbstractSeed
{
    public function run()
    {
        $faker = Faker\Factory::create();
        $data = [];
        for ($i = 0; $i < 100; $i++) {
            $data[] = [
                'username'      => $faker->userName,
                'password'      => sha1($faker->password),
                'password_salt' => sha1('foo'),
                'email'         => $faker->email,
                'first_name'    => $faker->firstName,
                'last_name'     => $faker->lastName,
                'created'       => date('Y-m-d H:i:s'),
            ];
        }

        $this->insert('users', $data);
    }
}

6.3. Hay truncate dữ liệ của bảng.

<?php

use Phinx\Seed\AbstractSeed;

class UserSeeder extends AbstractSeed
{
    public function run()
    {
        $data = [
            [
                'body'    => 'foo',
                'created' => date('Y-m-d H:i:s'),
            ],
            [
                'body'    => 'bar',
                'created' => date('Y-m-d H:i:s'),
            ]
        ];

        $posts = $this->table('posts');
        $posts->insert($data)
              ->save();

        // empty the table
        $posts->truncate();
    }
}

6.4. Thực thi seeder.

Sau khi tạo xong migration, tạo các file seed, chúng ta cần seed để thao tác với dữ liệu bằng nhiều cách:

  • Thực thi tất cả các file seed:
php vendor/bin/phinx seed:run
  • Thực thi một file seed cụ thể:
php vendor/bin/phinx seed:run -s UserSeeder
  • Hay thực thi nhiều file seed:
php vendor/bin/phinx seed:run -s UserSeeder -s PermissionSeeder -s LogSeeder

Còn rất nhiều thứ hay ho mà chúng ta còn chưa khám phá hết với phinx. Các bạn có thể tìm hiểu thêm tại Phinx.

Kết luận.

Phinx rất dễ sử dụng. Với khả năng dễ dàng tích hợp với những hệ thống đã có sẵn, hay bắt đầu xây dựng, tích hợp vào framework hay dự án không theo framework đều khá dễ dạng. Với phinx, việc testing với cơ sở dữ liệu cũng khá dễ dàng phải không nào. Không những thế, việc thao tác với nhiều kiểu cơ sở dữ liệu khác nhau, chúng ta cần phải biết những câu lệnh khác nhau, với Phinx thì xử lý việc đó khá dễ dàng phải không nào.

Migration thực sự làm nhẹ gánh bớt nhức đầu rất nhiều cho lập trình backend bằng PHP. Vậy sao chúng ta không thử áp dụng vào dự án của mình đi nào

Với một ngôi nhà mới, Phinx gia nhập với gia đình CakePHP, hi vọng rằng Phinx sẽ ngày càng tối ưu, phát triển mạnh mẽ hơn nữa. Cảm ơn các bạn đã đọc tới đây. Hi vong bài viết hữu ích đối với các bạn nhé 😃

Tìm hiểu thêm: