Phinx Database Migrations: A Practical Reference Guide

Initial Setup

Installing Phinx

composer require robmorgan/phinx

Creating the Configuration File

Run the initialization command to generate the configuration:

vendor/bin/phinx init

This creates a phinx.php file in your project root. Within this configuration:

  • paths defines the directories for migration and seed files
  • environments contains database connection settings for different contexts (development, staging, production)
  • default_environment specifies which configuration to use when no environment flag is provided

Environment Variables Integration

Using .env files to centralize database credentials is a common practice. Since Phinx executes outside of framework contexts, Laravel's auto-loading won't work. Install the dotenv library separately:

composer require vlucas/phpdotenv

Configure phinx.php as follows:

<?php
$dotenv = Dotenv\Dotenv::createImmutable(__DIR__);
$dotenv->load();

return [
    "paths" => [
        "migrations" => "database/migrations",
        "seeds" => "database/seeds"
    ],
    "environments" => [
        "default_migration_table" => "phinxlog",
        "default_environment" => "production",
        "production" => [
            "adapter" => "mysql",
            "host" => $_SERVER['DB_HOST'],
            "name" => $_SERVER['DB_NAME'],
            "user" => $_SERVER['DB_USER'],
            "pass" => $_SERVER['DB_PASS'],
            "port" => $_SERVER['DB_PORT'],
            "charset" => $_SERVER['DB_CHARSET']
        ]
    ]
];

Command Reference

Generating Migration Files

vendor/bin/phinx create MigrationName

Phinx automatically generates filenames with timestamps. Follow these naming conventions:

  • New tables: Create + TableName → CreateUsersTable
  • Table modifications: Modify + TableName + Description → ModifyUsersAddStatus
  • Table deletion: Delete + TableName → DeleteOldLogsTable

Phinx converts PascalCase to snake_case for filenames. For instance, CreateUsersTable becomes 20251028123045_create_users_table.php.

Runing Migrations

vendor/bin/phinx migrate

Available flags:

  • -e: Target environment (defaults to production)
  • -t: Target version number—migrations run sequentially up to this version if not provided
  • --dry-run: Display SQL statements without executing them

Setting Breakpoints

After deploying migrations to production, set a breakpoint immediately to prevent accidental mass rollbacks:

vendor/bin/phinx breakpoint

Options:

  • -e: Environment name
  • -t: Version number
  • -r: Remove the breakpoint

Rolling Back Migrations

Warning: Rollbacks permanently delete database structures and all associated data.

vendor/bin/phinx rollback

Options:

  • -e: Environment name
  • -t: Specific version (or 0 to rollback everything)
  • -d: Rollback by date (YYYYmmddHHiiss)
  • -f: Force rollback ignoring breakpoints
  • --dry-run: Display SQL without executing

Checking Migration Status

vendor/bin/phinx status

The -e flag specifies the environment.

Creating and Running Seeders

Generate a seeder:

vendor/bin/phinx seed:create SeederName

Recommended naming pattern: table name followed by description, e.g., UsersAddDemoDataSeeder.

Execute seeders:

vendor/bin/phinx seed:run

Options:

  • -e: Target environment
  • -s: Specific seeder class (can be specified multiple times)

Migration Script Reference

Supported Column Types

Phinx Type MySQL Type Required Parameters Description
smallinteger SMALLINT — Small integer
integer INT — Standard integer
biginteger BIGINT — Large integer
float FLOAT — Single precision
double DOUBLE — Double precision
decimal DECIMAL(M,D) precision, scale Fixed-point (monetary values)
bit BIT — Bit field
boolean TINYINT(1) — Boolean (0/1)
char CHAR(n) limit Fixed-length string
string VARCHAR(n) limit Variable-length string
text TEXT — Large text
enum ENUM(...) values Enumerated values
set SET(...) values Set values
uuid CHAR(36) — UUID string
date DATE — Date only
time TIME — Time only
datetime DATETIME — Date and time
timestamp TIMESTAMP — Timestamp
binary BINARY(n) limit Fixed-length binary
blob BLOB — Binary large object
tinyblob TINYBLOB — Small binary
mediumblob MEDIUMBLOB — Medium binary
longblob LONGBLOB — Large binary
json JSON — JSON data

Common Column Parameters

Parameter Type Description
limit / length int Length or byte limit
default mixed Default value
null bool Allow NULL values
after string Position after column (or MysqlAdapter::FIRST)
comment string Column comment
precision int Total decimal digits
scale int Decimal places
signed bool Allow negative values
values array/string Enum/set options
identity bool Enable auto-increment (requires null: false)

Migration Code Examples

<?php

declare(strict_types=1);

use Phinx\Db\Adapter\MysqlAdapter;
use Phinx\Migration\AbstractMigration;

final class CreateProductsTable extends AbstractMigration
{
    public function change(): void
    {
        // create() applies to new tables
        // update() applies to existing tables
        // Always save the table object at the end

        // Check table existence: $this->hasTable('table_name');
        // Drop table: $this->table('table_name')->drop()->save();

        // Initialize table with primary key and metadata
        $products = $this->table('products', [
            'id' => 'product_id',
            'comment' => 'Products catalog',
            'engine' => 'InnoDB',
            'collation' => 'utf8mb4_unicode_ci'
        ]);

        // Configuration options:
        // id:            Primary key column name (string), false (no auto PK), or omit (default 'id')
        // comment:       Table description
        // row_format:     MySQL row format (DYNAMIC, COMPACT, REDUNDANT, COMPRESSED)
        // engine:        Storage engine (InnoDB, MyISAM, MEMORY)
        // collation:     Character set collation
        // signed:        Allow negative integers
        // limit:         Maximum primary key length

        // Modify table metadata
        $products->changeComment('Updated products catalog');
        $products->changePrimaryKey(['new_primary_id']);
        $products->rename('renamed_products');

        // Check column existence: $table->hasColumn('column_name');

        // Rename and remove columns
        $products->renameColumn('old_name', 'new_name');
        $products->removeColumn('deprecated_field');

        // Add columns with fluent interface
        $products
            ->addColumn('sku', 'string', ['limit' => 64, 'null' => false])
            ->addColumn('thumbnail', 'string', ['comment' => 'Product image', 'null' => true, 'after' => 'sku'])
            ->addColumn('registered_at', 'integer', [
                'comment' => 'Registration timestamp',
                'null' => false,
                'limit' => MysqlAdapter::INT_BIG
            ]);

        // Integer sizes via limit parameter:
        // smallinteger (SMALLINT): 2 bytes
        // integer (INT): 4 bytes
        // biginteger (BIGINT): 8 bytes
        // Use MysqlAdapter::INT_SMALL, MysqlAdapter::INT_REGULAR, MysqlAdapter::INT_BIG

        // Numeric types
        // float(FLOAT), double(DOUBLE)
        // decimal(DECIMAL): requires precision and scale
        // bit(BIT), boolean(TINYINT(1))

        // String types
        // char(CHAR): fixed length via limit
        // string(VARCHAR): variable length via limit
        // text(TEXT)
        // enum(...): requires values parameter
        // set(...): requires values parameter
        // uuid(CHAR(36))

        // Date/time types
        // date(DATE), time(TIME), datetime(DATETIME), timestamp(TIMESTAMP)

        // Binary types
        // binary(BINARY): fixed length
        // blob(BLOB), tinyblob(TINYBLOB), mediumblob(MEDIUMBLOB), longblob(LONGBLOB)
        // json(JSON)

        // Column modifiers
        // limit/length: int
        // default: mixed
        // null: bool (true/false)
        // after: column name or MysqlAdapter::FIRST
        // comment: string
        // precision: int (total digits)
        // scale: int (decimal places)
        // signed: bool
        // values: array or comma-separated string
        // identity: bool (auto-increment, requires null: false)

        // Modify existing columns
        $products->changeColumn('price', 'decimal', [
            'precision' => 10,
            'scale' => 2,
            'null' => false
        ]);

        // Index management
        $products->addIndex(['sku'], ['unique' => true]);
        $products->addIndex(['registered_at']);

        // Remove indexes
        $products->removeIndex(['sku']);
        $products->removeIndexByName('idx_registered_at');
    }
}

Seeder Implementation

<?php

declare(strict_types=1);

use Phinx\Seed\AbstractSeed;

class ProductsSeedData extends AbstractSeed
{
    public function run(): void
    {
        $dataset = [
            [
                'name' => 'Sample Product',
                'created_at' => date('Y-m-d H:i:s'),
            ],
            [
                'name' => 'Another Item',
                'created_at' => date('Y-m-d H:i:s'),
            ]
        ];

        $productsTable = $this->table('products');
        $productsTable->insert($dataset)->saveData();

        // Clear all rows from table
        $productsTable->truncate();
    }
}

Generating Migrations from Existing Databases

For projects transitioning to Phinx, the phinx-migrations-generator tool reverse-engineers migrations from an existing database:

composer require odan/phinx-migrations-generator --dev

The tool reads your existing phinx.php configurasion automatical. Generate migrations with:

vendor/bin/phinx-migrations generate

This produces migration files representing your current database structure, ready for version control and deployment to other environments.

Tags: phinx PHP database-migrations MySQL phinx-tutorial

Posted on Wed, 07 Oct 2026 16:27:11 +0000 by Joel.DNSVault