Search by

rajtik76 / nette-migrations

rajtik76

Laravel-like migrations, schema builder, seeders and factories for Nette Database (SQLite, MySQL, PostgreSQL)

Package info

github.com/rajtik76/nette-migrations

pkg:composer/rajtik76/nette-migrations

Statistics

Installs: 12

Dependents: 1

Suggesters: 0

Stars: 0

Open Issues: 0

v0.2.0 2026-11-03 14:52 UTC

This package is auto-updated.

Last update: 2026-11-03 14:55:41 UTC


README

Laravel-like database migrations, schema builder, seeders and factories for Nette Database. No third-party migration library.

  • SQLite, MySQL, MariaDB and PostgreSQL from one migration file
  • batches with rollback, reset, fresh and status
  • seeders resolved from the DI container, factories with Faker
  • make:migration, make:seeder, make:factory generators
  • SQLite foreign keys enabled on every connection

Commands run through rajtik76/nette-console (php nette); the extension registers itself via package discovery.

Installation

composer require rajtik76/nette-migrations
composer require --dev fakerphp/faker

Map the database namespaces in composer.json:

"autoload": {
    "psr-4": {
        "Database\\Factories\\": "database/factories/",
        "Database\\Seeders\\": "database/seeders/"
    }
}

Register factories as services, so seeders can get them injected:

search:
    - in: %rootDir%/database/factories
      classes:
        - *Factory

Commands

Command Description
migrate [--seed] Run pending migrations as a new batch
migrate:rollback [--step=N] Roll back the last batch, or the last N migrations
migrate:reset Roll back all migrations
migrate:fresh [--seed] Drop all tables and run all migrations
migrate:status List migrations with their batch
db:seed [--class=UserSeeder] Run Database\Seeders\DatabaseSeeder or the given seeder
make:migration create_posts_table --create=table / --table=table, otherwise inferred from the name
make:seeder User database/seeders/UserSeeder.php
make:factory Post database/factories/PostFactory.php for table posts (--table to override)

In production mode (NETTE_DEBUG=0) destructive commands ask for confirmation; --force skips it.

Migrations

<?php declare(strict_types=1);

use Rajtik\Migrations\Migration\Migration;
use Rajtik\Migrations\Schema\Blueprint;
use Rajtik\Migrations\Schema\Schema;


return new class extends Migration {
    public function up(Schema $schema): void
    {
        $schema->create('posts', function (Blueprint $table): void {
            $table->id();
            $table->foreignId('user_id')->constrained()->cascadeOnDelete();
            $table->string('title')->unique();
            $table->text('body')->nullable();
            $table->boolean('published')->default(false);
            $table->timestamps();
        });
    }


    public function down(Schema $schema): void
    {
        $schema->dropIfExists('posts');
    }
};

Columns: id, integer, bigInteger, foreignId, string, text, boolean, timestamp, timestamps. Modifiers: nullable, default, useCurrent, primary, unique, index. Table: primary(...), unique(...), index(...), foreign(...)->references()->on(), dropColumn, dropIndex, dropUnique, dropForeign. Schema: create, table, drop, dropIfExists, hasTable, statement, connection.

Each migration runs in a transaction on SQLite and PostgreSQL. MySQL/MariaDB commit DDL implicitly. SQLite can add a foreign key only together with a new column and cannot drop one.

SQLite timestamps follow the Nette formatDateTime driver option. With the default U they are stored as unix timestamps, and a date string default is converted when the migration compiles. With any other format, useCurrent() falls back to CURRENT_TIMESTAMP, which SQLite fills in UTC. SQLite cannot add a useCurrent() column to an existing table; add it nullable and fill it with an update.

Upgrading from 0.1.0

SQLite timestamp columns created by 0.1.0 are declared as lowercase datetime, which Nette does not read as a unix timestamp. Recreate each one in a migration:

alter table users add column seen_at_new DATETIME null;
update users set seen_at_new = case typeof(seen_at) when 'text' then cast(strftime('%s', seen_at) as integer) else seen_at end;
alter table users drop column seen_at;
alter table users rename column seen_at_new to seen_at;

The recreated column is nullable and has no default. Drop indexes on the column before dropping it.

Seeders and factories

final class UserFactory extends Factory
{
    public function __construct(Explorer $database, private readonly Passwords $passwords)
    {
        parent::__construct($database);
    }


    protected function table(): string
    {
        return 'users';
    }


    protected function definition(): array
    {
        return [
            'name' => $this->faker()->name(),
            'email' => $this->faker()->unique()->safeEmail(),
            'password' => $this->passwords->hash('password'),
        ];
    }
}


final class DatabaseSeeder extends Seeder
{
    public function __construct(private readonly UserFactory $users)
    {
    }


    public function run(): void
    {
        $this->users->create(['email' => 'test@example.com']);
        $this->users->state(['name' => 'Admin'])->createMany(3);
        $this->call(PostSeeder::class);
    }
}

Factory: make, makeMany, create, createMany, state(array|Closure).

Configuration

migrations:
    table: migrations
    directory: %rootDir%/database/migrations
    stubs: %rootDir%/stubs              # overrides migration*.stub, seeder.stub, factory.stub
    sqliteForeignKeys: true
    seeders:
        namespace: Database\Seeders
        directory: %rootDir%/database/seeders
        default: DatabaseSeeder
    factories:
        namespace: Database\Factories
        directory: %rootDir%/database/factories

Checks

composer run ci:check    # PHPStan level 8 + Nette Tester

Integration tests use SQLite and, when configured, other databases (dsn|user|password):

MIGRATIONS_TEST_MYSQL='mysql:host=127.0.0.1;port=33306;dbname=migrations_test|root|secret' \
MIGRATIONS_TEST_MARIADB='mysql:host=127.0.0.1;port=33307;dbname=migrations_test|root|secret' \
MIGRATIONS_TEST_PGSQL='pgsql:host=127.0.0.1;port=54329;dbname=migrations_test|postgres|secret' \
composer run tester