A modern schema-first migration engine for PHP.
PHP Schema Engine lets you define your database structure using a clean PHP DSL, compare it against the current database schema, generate SQL automatically, and apply changes through a lightweight CLI.
Current version:
0.3.0
- Schema-first database definition
- Fluent PHP DSL
- MySQL/MariaDB support
- Automatic schema introspection
- Table and column diffing
- Partial index diffing
- SQL generation
- Migration execution
- Migration history
- Model generation
- Dry-run mode
- Destructive operation protection
- Database reset support
- Snapshot generation
- Foreign key support
- Debug trace mode
composer require eril/schema-engineGenerate the default configuration file:
php bin/migrate --initThis creates:
schema-engine.php
database/schema.php
database/bootstrap.sql
database/snapshots/Generated config example:
<?php
return [
// Schema file path
'schema' => 'database/schema.php',
'bootstrap' => 'bootstrap.php',
// Database connection configuration
'database' => [
'driver' => 'mysql',
'host' => $_ENV['DB_HOST'] ?? '127.0.0.1',
'port' => $_ENV['DB_PORT'] ?? 3306,
'database' => $_ENV['DB_NAME'] ?? 'app',
'username' => $_ENV['DB_USER'] ?? 'root',
'password' => $_ENV['DB_PASS'] ?? '',
],
// Model generator setup
'generator' => [
'models' => [
'enabled' => false,
'namespace' => 'App\\Models',
'path' => '/app/Models',
'extends' => null,
],
],
];Optional bootstrap file:
bootstrap.phpUseful for:
- loading
.env - bootstrapping frameworks
- loading helpers
- custom runtime setup
Create a schema file:
<?php
use SchemaEngine\DSL\Schema;
use SchemaEngine\DSL\Table;
return function (Schema $schema) {
$schema->table('users', function (Table $t) {
$t->id();
$t->string('name');
$t->string('email')
->unique();
$t->timestamps();
});
};Run a dry-run first:
php bin/migrate --dry-runApply the migration:
php bin/migrate --yes$schema->table('users', function ($t) {
$t->id();
$t->string('name');
$t->string('email')
->unique();
$t->int('age')
->default(0);
$t->timestamps();
});$t->id();
$t->string('name');
$t->char('code', 2);
$t->text('body');
$t->longText('content');
$t->int('age');
$t->bigInt('views');
$t->float('rating');
$t->double('score');
$t->decimal('price', 10, 2);
$t->boolean('active');
$t->datetime('published_at');
$t->timestamp('created_at');
$t->json('meta');
$t->uuid('uuid');$t->string('email')
->unique();
$t->string('slug')
->index();
$t->string('name')
->nullable();
$t->int('age')
->default(0);
$t->timestamp('created_at')
->defaultCurrentTimestamp();
$t->timestamp('updated_at')
->defaultRaw('CURRENT_TIMESTAMP');$t->id();
$t->timestamps();
$t->createdAt();
$t->updatedAt();
$t->softDeletes();
$t->rememberToken();Single-column indexes:
$t->string('email')
->unique();
$t->string('slug')
->index();Composite indexes:
$t->unique(['email', 'tenant_id']);
$t->index(['first_name', 'last_name']);Indexes are automatically introspected and partially diffed.
Current V1 behavior:
- missing indexes are automatically added
- removed indexes are automatically dropped
- changed indexes are ignored intentionally with warnings
Example warning:
Index 'email_unique' on table 'users' differs from desired schema and was ignored.Simple inferred relation:
$t->foreign('user_id');Explicit references:
$t->foreign('user_id')
->constrained() // or references('users')
->cascadeOnDelete();Explicit references:
$t->foreign('user_id')
->constrained() // or references('users')
->cascadeOnDelete();ou:
$t->foreign('author_id')
->references('users');Custom referenced column:
$t->uuid('author_id')
->references('users', 'uuid');Available relation actions:
->cascadeOnDelete()
->cascadeOnUpdate()
->restrictOnDelete()
->restrictOnUpdate()
->nullOnDelete()
->nullOnUpdate()In V1, foreign keys are generated when creating new tables only. Foreign key changes after table creation are not automatically migrated yet.
Generate models from your schema:
php bin/migrate --generate-modelsExample generated model:
<?php
namespace App\Models;
class User
{
protected string $table = 'users';
}Generator configuration:
'generator' => [
'models' => [
'enabled' => true,
'namespace' => 'App\\Models',
'path' => '/app/Models',
'extends' => 'App\\Core\\Model',
],
],| Command | Description |
|---|---|
--init |
Initialize a new project |
--yes |
Don't ask for confirmation |
--dry-run |
Preview pending changes |
--force |
Execute dangerous SQL like delete |
--status |
Show migration history |
--sql |
Export SQL for pending schema changes |
--schema-sql |
Export SQL for the entire schema |
--rollback |
Rollback the latest batch |
--fresh |
Drop all tables |
--clear-history |
Clear histries |
--generate-models |
Generate model classes |
Dry-run:
php bin/migrate --dry-runApply migrations without confirmation:
php bin/migrate --yesAllow destructive operations:
php bin/migrate --forceShow migration history:
php bin/migrate --statusReset database:
php bin/migrate --fresh --force --yesReset database and clear history:
php bin/migrate --fresh --force --yes --clear-historyGenerate models:
php bin/migrate --generate-modelsShow stack trace on exceptions:
php bin/migrate --traceExecuted operations are stored in:
schema_migrationsThis table is managed internally by the engine and ignored during schema diffing.
PHP Schema Engine can generate SQL without executing migrations.
Generate the SQL required to migrate the current database schema:
php bin/migrate --sqlExample:
ALTER TABLE users
ADD COLUMN phone VARCHAR(20);
CREATE TABLE posts (...);This command uses schema introspection and the diff engine.
Generate SQL for the entire schema definition:
php bin/migrate --schema-sqlExample:
CREATE TABLE users (...);
CREATE TABLE posts (...);This command ignores the current database and generates SQL directly from database/schema.php.
bin/migrate --schema-sql > install.sqlor
bin/migrate --sql > migration.sql$ notice not to use php because > stdout is not a tty
This is useful for:
- Manual deployments
- Database reviews
- Backup scripts
- Installation packages
- CI/CD pipelines
By default, destructive operations are blocked.
This includes:
- dropping tables
- dropping columns
- dropping indexes
To allow destructive operations:
php bin/migrate --forceEnable stack traces during execution:
php bin/migrate --traceExample output:
[ERROR] Table users already exists
File: .../Migrator.php:62
Stack trace:
#0 .../Migrator.php:62 run()
#1 .../MigrateCommand.php:88 printPlannedSql()
#2 .../bin/migrate:24 run()PHP Schema Engine 0.3.0 is intentionally conservative.
Current V1 limitations:
- MySQL/MariaDB only
- Index diffing is partial and intentionally conservative
- Existing indexes are not automatically modified
- Foreign key changes after table creation are not automatically migrated
- Rename detection is disabled by default
- Table recreation is not implemented yet
- No PostgreSQL grammar yet
- No SQLite grammar yet
- No ORM/query builder layer yet
PHP Schema Engine follows a schema-first approach.
The schema file is the source of truth.
Schema DSL
↓
Schema Metadata
↓
Database Introspection
↓
Diff Engine
↓
SQL Generation
↓
Migration ExecutionThe DSL is designed to stay expressive, while the internal architecture keeps metadata, diffing, SQL generation, and execution separated.
- Schema DSL
- MySQL introspection
- Column diffing
- Partial index diffing
- SQL generator
- CLI
- Migration history
- Model generation
- Foreign keys on create
- migrate:fresh
- migrate:reset
- better CLI output
- schema snapshots
- foreign key diffing
- rollback support
- advanced index diffing
- table recreation mode
- explicit rename operations
- stable public API
- stronger type system
- advanced MySQL grammar
- multi-driver foundation
MIT