1. Go to this page and download the library: Download dakujem/migrun library. Choose the download type require.
2. Extract the ZIP file and open the index.php.
3. Add this code to the index.php.
<?php
require_once('vendor/autoload.php');
/* Start to develop here. Best regards https://php-download.com/ */
dakujem / migrun example snippets
// migrations/20240101_120000_create_users.php
use PDO;
return function (PDO $db): void {
$db->exec('CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT NOT NULL)');
};
// migrations/20240115_093000_add_email_index.php
use Dakujem\Migrun\ReversibleMigration;
use PDO;
return new class implements ReversibleMigration
{
public function up(?PDO $db = null): void
{
$db->exec('CREATE INDEX idx_users_email ON users (email)');
}
public function down(?PDO $db = null): void
{
$db->exec('DROP INDEX idx_users_email');
}
};
akujem\Migrun\MigrunBuilder;
$container = ame database being migrated
$pdo = $container->get(PDO::class);
$orchestrator = (new MigrunBuilder())
->directory(__DIR__ . '/migrations')
->container($container) // enables autowiring of migration parameters
->pdoStorage($pdo) // history stored in the same DB, table: migrun_migrations
->build();
$executed = $orchestrator->run();
foreach ($executed as $migration) {
echo "Migrated: {$migration->id()}" . PHP_EOL;
}
$orchestrator = (new MigrunBuilder())
->directory(__DIR__ . '/migrations')
->build();
use Dakujem\Migrun\MigrunBuilder;
$orchestrator = (new MigrunBuilder())
->directory(__DIR__ . '/migrations') // // recommended: history in the same DB as migrations
->build();
// Any PDO connection (MySQL, PostgreSQL, SQLite, …) — recommended
->pdoStorage($pdo) // default table name (migrun_migrations)
->pdoStorage($pdo, table: 'schema_history') // custom table name
// mysqli connection (MySQL/MariaDB only)
->mysqliStorage($mysqli) // default table name (migrun_migrations)
->mysqliStorage($mysqli, table: 'schema_history') // custom table name
// SQLite database file
->sqliteStorage() // {migrations-dir}/.migrun/migrun.sqlite
->sqliteStorage(__DIR__ . '/var/history.sqlite') // explicit path
->sqliteStorage(table: 'schema_history') // default path, custom table name
// JSON file — default when nothing is set
->fileStorage(__DIR__ . '/var/migrun') // directory → appends /migrun.json
// file path → used as-is
// omit → {migrations-dir}/.migrun/migrun.json
akujem\Migrun\Executor\ContainerInvoker;
use Dakujem\Migrun\Executor\Executor;
use Dakujem\Migrun\Executor\TrivialInvoker;
use Dakujem\Migrun\Finder\DirectoryFinder;
use Dakujem\Migrun\Orchestrator;
use Dakujem\Migrun\Storage\JsonFileStorage;
$container = iner / no autowiring:
// executor: new Executor(new TrivialInvoker()),
$executed = $orchestrator->run();
foreach ($executed as $migration) {
echo "Migrated: {$migration->id()}" . PHP_EOL;
}
// Roll back the last migration
// $reverted = $orchestrator->rollback(1);
use Dakujem\Migrun\MigrationState;
use Dakujem\Migrun\Orchestrator;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
final class MigrateCommand extends Command
{
public function __construct(private Orchestrator $runner)
{
parent::__construct('db:migrate');
}
protected function configure(): void
{
$this->addArgument('command', InputArgument::OPTIONAL, 'run | rollback | status', 'run');
$this->addArgument('steps', InputArgument::OPTIONAL, 'Number of migrations to roll back', 1);
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
return match ($input->getArgument('command')) {
'run' => $this->runMigrations($output),
'rollback' => $this->rollback($output, (int) $input->getArgument('steps')),
'status' => $this->status($output),
default => (function () use ($input, $output) {
$output->writeln("<error>Unknown command: {$input->getArgument('command')}</error>");
return Command::FAILURE;
})(),
};
}
private function runMigrations(OutputInterface $output): int
{
$executed = $this->runner->run();
if (empty($executed)) {
$output->writeln('Nothing to run.');
}
foreach ($executed as $m) {
$output->writeln("Migrated: {$m->id()}");
}
return Command::SUCCESS;
}
private function rollback(OutputInterface $output, int $steps): int
{
$reverted = $this->runner->rollback($steps);
foreach ($reverted as $m) {
$output->writeln("Reverted: {$m->id()}");
}
return Command::SUCCESS;
}
private function status(OutputInterface $output): int
{
$entries = array_filter(
iterator_to_array($this->runner->status()),
fn($e) => $e->state !== MigrationState::Missing,
);
if (empty($entries)) {
$output->writeln('No migrations found.');
return Command::SUCCESS;
}
$idWidth = max(array_map(fn($e) => strlen($e->id), $entries));
$idWidth = max($idWidth, 2);
$output->writeln(sprintf("%-{$idWidth}s %-7s %s", 'ID', 'Status', 'Applied at'));
$output->writeln(str_repeat('-', $idWidth + 22));
foreach ($entries as $entry) {
$output->writeln(sprintf(
"%-{$idWidth}s %-7s %s",
$entry->id,
match ($entry->state) {
MigrationState::Applied => 'up',
MigrationState::Pending => 'down',
MigrationState::Missing => 'MISSING',
},
$entry->appliedAt?->format('Y-m-d H:i:s') ?? '-',
));
}
return Command::SUCCESS;
}
}
use Dakujem\Migrun\Storage\PdoStorage;
use Dakujem\Migrun\Storage\SqliteStorage;
// Any PDO connection — table is created automatically
$storage = new PdoStorage($pdo);
$storage = new PdoStorage($pdo, 'schema_history'); // custom table name
// SQLite convenience wrapper
$storage = new SqliteStorage(__DIR__ . '/var/migrun.sqlite');
(new MigrunBuilder())
->directory(__DIR__ . '/migrations')
->pdoStorage($pdo) // or ->sqliteStorage()
->build();
use Dakujem\Migrun\MigrationFile;
use Dakujem\Migrun\MigrationHistoryEntry;
use Dakujem\Migrun\TracksMigrations;
final class RedisStorage implements TracksMigrations
{
public function __construct(private \Redis $redis, private string $key = 'migrations') {}
public function getApplied(): iterable
{
$entries = [];
foreach ($this->redis->hGetAll($this->key) as $id => $at) {
$entries[] = new MigrationHistoryEntry($id, new \DateTimeImmutable($at));
}
usort($entries, fn($a, $b) => $b->at() <=> $a->at());
return $entries;
}
public function isApplied(MigrationFile $migration): bool
{
return (bool) $this->redis->hExists($this->key, $migration->id());
}
public function markApplied(MigrationFile $migration, ?\DateTimeImmutable $at = null): void
{
$this->redis->hSet($this->key, $migration->id(), ($at ?? new \DateTimeImmutable())->format(\DateTimeImmutable::ATOM));
}
public function markReverted(MigrationFile $migration, ?\DateTimeImmutable $at = null): void
{
$this->redis->hDel($this->key, $migration->id());
}
}
use Dakujem\Migrun\Executor\InvokesCallable;
use Invoker\InvokerInterface;
final readonly class PhpDiInvoker implements InvokesCallable
{
public function __construct(private InvokerInterface $invoker) {}
public function invoke(callable $fn): mixed
{
return $this->invoker->call($fn);
}
}
use Invoker\Invoker;
use Invoker\ParameterResolver\Container\TypeHintContainerResolver;
use Invoker\ParameterResolver\DefaultValueResolver;
use Invoker\ParameterResolver\ResolverChain;
$invoker = new Invoker(
new ResolverChain([
new TypeHintContainerResolver($container),
new DefaultValueResolver(),
]),
);
$orchestrator = new Orchestrator(
storage: new JsonFileStorage(__DIR__ . '/storage/migrations.json'),
finder: new DirectoryFinder(__DIR__ . '/migrations'),
executor: new Executor(new PhpDiInvoker($invoker)),
);
use Dakujem\Migrun\Executor\InvokesCallable;
use Dakujem\Wire\Invoker as GenieInvoker;
final readonly class WireGenieInvoker implements InvokesCallable
{
public function __construct(private GenieInvoker $invoker) {}
public function invoke(callable $fn): mixed
{
return $this->invoker->invoke($fn);
}
}
use Dakujem\Wire\Genie;
$orchestrator = new Orchestrator(
storage: new JsonFileStorage(__DIR__ . '/storage/migrations.json'),
finder: new DirectoryFinder(__DIR__ . '/migrations'),
executor: new Executor(new WireGenieInvoker(new Genie($container))),
);
use Dakujem\Migrun\Direction;
use Dakujem\Migrun\ExecutesMigrations;
use Dakujem\Migrun\Executor\Executor;
use Dakujem\Migrun\MigrationFile;
final class TransactionalExecutor implements ExecutesMigrations
{
public function __construct(
private Executor $inner,
private \PDO $db,
) {}
public function execute(MigrationFile $migration, Direction $direction): void
{
$this->db->beginTransaction();
try {
$this->inner->execute($migration, $direction);
$this->db->commit();
} catch (\Throwable $e) {
$this->db->rollBack();
throw $e;
}
}
}
$migrations = (new MigrunBuilder())
->directory(__DIR__ . '/migrations')
->pdoStorage($pdo) // table: migrun_migrations
->container($container)
->build();
$seeders = (new MigrunBuilder())
->directory(__DIR__ . '/seeds')
->pdoStorage($pdo, table: 'migrun_seeds') // separate table, same database
->container($container)
->build();
$migrations->run();
$seeders->run();
use Dakujem\Migrun\Storage\JsonFileStorage;
use Dakujem\Migrun\Storage\PdoStorage;
$old = new JsonFileStorage(__DIR__ . '/migrations/.migrun/migrun.json');
$new = new PdoStorage($pdo);
$applied = $old->getApplied(); // newest first
$migrationOrder = array_reverse( // oldest first
is_array($applied) ? $applied : iterator_to_array($applied),
);
foreach ($migrationOrder as $entry) {
$new->markApplied($entry->id(), $entry->at());
}
YYYYMMDD_HHMMSS_<name>.php
bash
php bin/migrate.php run
php bin/migrate.php rollback
php bin/migrate.php rollback 3
php bin/migrate.php status