简单实现数据库迁移命令

caylof

实现基于 laravel db,只有一个类文件。

需要在 webman 项目的根目录中创建用于存储数据库迁移文件的目录 database/migrations

迁移记录存储在文件中,位置位于 runtime/logs 目录下。

命令说明:

# 生成数据库迁移文件
php webman migrate make t1
# 执行迁移
php webman migrate
# 执行回滚
php webman migrate rollback

迁移文件类似 laravel:

<?php

use Illuminate\Database\Schema\Builder;
use Illuminate\Database\Schema\Blueprint;

return new class {

    public function up(Builder $schema): void
    {
        $schema->create('t1', function(Blueprint $table) {
            $table->id();
            $table->string('title');
        });
    }

    public function down(Builder $schema): void
    {
        $schema->drop('t1');
    }
};

该命令类文件 app/command/Migrate.php

<?php

namespace app\command;

use support\Db;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

class Migrate extends Command
{
    protected static $defaultName = 'migrate';
    protected static $defaultDescription = 'migrate';
    protected ?string $connection = null;

    /**
     * @return void
     */
    protected function configure()
    {
        $this->addArgument('operate', InputArgument::OPTIONAL, 'make/up/rollback', 'up', ['make', 'up', 'rollback']);
        $this->addArgument('name', InputArgument::OPTIONAL, 'migration name of make');
        $this->addOption('connection', null, InputOption::VALUE_OPTIONAL, 'db connection, [default: "default"]');
    }

    /**
     * @param InputInterface $input
     * @param OutputInterface $output
     * @return int
     */
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $operate = strtolower($input->getArgument('operate'));
        $name = $input->getArgument('name');
        $this->connection = $input->getOption('connection');

        if (! in_array($operate, ['make', 'up', 'rollback'])) {
            $output->writeln('<error>operate should is [ make/up/rollback ].</error>');
            return self::INVALID;
        }
        if (strcmp($operate, 'make') === 0 && empty($name)) {
            $output->writeln('<error>name is required.</error>');
            return self::INVALID;
        }

        match ($operate) {
            'make' => $this->runMakeMigration($name),
            'up' => $this->runUp(),
            'rollback' => $this->runRollback(),
        };
        $output->writeln('<info>success!</info>');
        return self::SUCCESS;
    }

    protected function getMigrationLogFile(): string
    {
        return runtime_path('logs/'.($this->connection ?? 'default').'-migrations.log');
    }

    protected function fetchMigrationRows(string $migrationLogFile): array
    {
        if (! file_exists($migrationLogFile)) {
            touch($migrationLogFile);
        }
        $logContent = trim(file_get_contents($migrationLogFile));
        return empty($logContent) ? [] : array_map(fn($row) => explode(',', $row), explode(\PHP_EOL, $logContent));
    }

    protected function getSchemaBuilder(): \Illuminate\Database\Schema\Builder
    {
        return Db::connection($this->connection)->getSchemaBuilder();
    }

    protected function runUp()
    {
        $migrationLogFile = $this->getMigrationLogFile();
        $rows = $this->fetchMigrationRows($migrationLogFile);
        $latestBatchNum = empty($rows) ? 0 : (int)$rows[count($rows) -1][0];
        $existMigrations = array_column($rows, 1);
        $needMigrations = [];

        $schema = $this->getSchemaBuilder();

        $dir = new \DirectoryIterator(base_path('database/migrations'));
        $dir->rewind();
        foreach ($dir as $fileInfo) {
            /* @var $fileInfo \SplFileInfo */
            if (!$fileInfo->isDot()
                && $fileInfo->isFile()
                && ($basename = $fileInfo->getBasename('.php'))
                && !in_array($basename, $existMigrations)
            ) {
                $needMigrations[] = [$fileInfo->getRealPath(), $basename];
            }
        }

        if (! empty($needMigrations)) {
            sort($needMigrations);
            foreach ($needMigrations as $file) {
                $obj = require $file[0];
                $obj->up($schema);
            }
            $appendContent = join(\PHP_EOL,
                array_map(
                    fn($item) => join(',', [
                        $latestBatchNum + 1,
                        $item[1]
                    ]),
                    $needMigrations
                )
            );
            if ($latestBatchNum > 0) {
                $appendContent = \PHP_EOL.$appendContent;
            }
            file_put_contents($migrationLogFile, $appendContent, \FILE_APPEND | \LOCK_EX);
        }
    }

    protected function runRollback()
    {
        $migrationLogFile = $this->getMigrationLogFile();
        $rows = $this->fetchMigrationRows($migrationLogFile);
        $latestBatchNum = empty($rows) ? 1 : (int)$rows[count($rows) -1][0];
        $needRollbacks = empty($rows) ? [] : array_values(array_filter($rows, fn($row) => intval($row[0]) === $latestBatchNum));
        $needRollbacks = array_reverse($needRollbacks);

        if (! empty($needRollbacks)) {
            $schema = $this->getSchemaBuilder();
            foreach ($needRollbacks as $needRollback) {
                $obj = require base_path('database/migrations/'.$needRollback[1].'.php');
                $obj->down($schema);
            }

            $logRows = array_filter($rows, fn($row) => intval($row[0]) < $latestBatchNum);
            $content = join(\PHP_EOL, array_map(fn($row) => join(',', $row), $logRows));
            file_put_contents($migrationLogFile, $content);
        }
    }

    protected function runMakeMigration(string $name)
    {
        $file = base_path('database/migrations/'.date('Y_m_d_His_').$name.'.php');
        touch($file);
        file_put_contents($file, <<<EOF
<?php

use Illuminate\Database\Schema\Builder;
use Illuminate\Database\Schema\Blueprint;

return new class {

    public function up(Builder \$schema): void
    {
        \$schema->create('', function(Blueprint \$table) {

        });
    }

    public function down(Builder \$schema): void
    {

    }
};
EOF
);
    }

}
919 0 1
0个评论

caylof

240
积分
0
获赞数
0
粉丝数
2022-08-13 加入
🔝