数据库迁移很慢,可以加个进度条吗?怎么加呢?

有个数据迁移,要是一直不动,好难受,可以给他加个进度条吗?

我知道可以调用命令行, 但是这样不会显示运行进度

Artisan::call('update:xx');
Artisan::call('db:seed', ['--class=CustomerCountsSeeder'], $output); // 新问题,不能实时显示执行结果

1. 运行环境

1). 当前使用的 Laravel 版本?

8.5.x

2). 当前使用的 php/php-fpm 版本?

PHP 版本:

7.4

2. 问题描述?

数据迁移比较耗时,长时间看不到执行进度,怕挂了也不知道,浪费时间

3. 您期望得到的结果?

xx 迁移开始(原英文)
执行进度,进度条也行,数字也行,只要可以看到在执行就好
xx 迁移结束

4. 您实际得到的结果?

xx 迁移开始(原英文)
xx 迁移结束 (等待 xx 时间后)

执行的命令

php artisan migrate // 这里可以显示进度条吗?迁移的时候没有 output 对象
世界上最遥远的距离不是生与死,而是你亲手制造的BUG就在你眼前,你却怎么都找不到ta。
《L03 构架 API 服务器》
你将学到如 RESTFul 设计风格、PostMan 的使用、OAuth 流程,JWT 概念及使用 和 API 开发相关的进阶知识。
《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。
最佳答案

找到解决方法了

直接上代码

use Symfony\Component\Console\Input\ArgvInput;
use Symfony\Component\Console\Output\ConsoleOutput;

$output = new OutputStyle(new ArgvInput(), new ConsoleOutput());
Artisan::call('update:xx', [], $output); // 这样就可以看到进度条了

原因也找到了,挺简单的,上代码,直接在代码里面说明吧

Artisan::call('update:xx', [], $output) ; 

// 实际执行的是 `Illuminate\Console\Application::call()`
    public function call($command, array $parameters = [], $outputBuffer = null)
    {
        [$command, $input] = $this->parseCommand($command, $parameters);

        if (! $this->has($command)) {
            throw new CommandNotFoundException(sprintf('The command "%s" does not exist.', $command));
        }

        return $this->run(
            $input, $this->lastOutput = $outputBuffer ?: new BufferedOutput  // 使用的是这个输出
        );
    }

继续查看 Symfony\Component\Console\Output\BufferedOutput

    protected function doWrite(string $message, bool $newline)
    {
        $this->buffer .= $message;

        if ($newline) {
            $this->buffer .= \PHP_EOL; // 这里仅仅是把运行结果保存到了字符串中,所以没有输出到命令行
        }
    }

至此原因已经明确,那么直接替换 output 对象就行, 打印 app(),也没有找到合适的对象,那就只能自己创建了,也就是开头的这段话

$output = new OutputStyle(new ArgvInput(), new ConsoleOutput());

最终代码

// 数据库迁移
class TestMigration extends Migration
{
    public function up()
    {
        $output = new OutputStyle(new ArgvInput(), new ConsoleOutput());
        Artisan::call('t:t', [], $output);
        $output->writeln('');
    }
    public function down()
    {
        $output = new OutputStyle(new ArgvInput(), new ConsoleOutput());
        Artisan::call('t:t', [], $output);
        $output->writeln('');
    }
}

PS:如果需要考虑代码复用,可以把 $output 单独拎出去,创建 trait
·

// 未测试

use Illuminate\Console\OutputStyle;
use Symfony\Component\Console\Input\ArgvInput;
use Symfony\Component\Console\Output\ConsoleOutput;

trait Output
{
    /**
     * The output interface implementation.
     *
     * @var OutputStyle
     */
    protected $output;

    /**
     * Set the output interface implementation.
     *
     * @param OutputStyle $output
     * @return void
     */
    public function setOutput(OutputStyle $output)
    {
        $this->output = $output;
    }

    /**
     * Get the output implementation.
     *
     * @return OutputStyle
     */
    public function getOutput(): OutputStyle
    {
        if ($this->output instanceof OutputStyle) {
            return $this->output;
        }

        return $this->output = new OutputStyle(new ArgvInput(), new ConsoleOutput());
    }
}

PS:如果有其他更好的方案,最佳答案也会更换

2年前 评论
讨论数量: 19
chowjiawei

写在代码里面 你加了给谁看呢~~

2年前 评论
chowjiawei (作者) 2年前
kis龍 (楼主) 2年前
chowjiawei (作者) 2年前
kis龍 (楼主) 2年前
chowjiawei (作者) 2年前
chowjiawei (作者) 2年前
chowjiawei (作者) 2年前
chowjiawei (作者) 2年前
Epona

加进度条的话 是在 update:xx 具体处理逻辑的地方加的, 直接调用应该是不行的。

2年前 评论
kis龍 (楼主) 2年前
2年前 评论
kis龍 (楼主) 2年前
eddy8 (作者) 2年前

我这有个办法

  • 查询要迁移数据总量 查询迁移目录 Database\Migrations 的总文件数量

  •       $this->comment("迁移 {$count} 条数据...");
    
          $bar = $this->output->createProgressBar($count);
    
          foreach(迁移目录的文件) {
              执行迁移逻辑
               Artisan::call('migrate', [迁移文件]);
    
              $bar->advance();
          }
    
          $bar->finish();
          $this->comment('迁移完成!');
2年前 评论
kis龍 (楼主) 2年前
Mumujin
$this->withProgressBar($data, Closure $callback)
2年前 评论
kis龍 (楼主) 2年前

找到解决方法了

直接上代码

use Symfony\Component\Console\Input\ArgvInput;
use Symfony\Component\Console\Output\ConsoleOutput;

$output = new OutputStyle(new ArgvInput(), new ConsoleOutput());
Artisan::call('update:xx', [], $output); // 这样就可以看到进度条了

原因也找到了,挺简单的,上代码,直接在代码里面说明吧

Artisan::call('update:xx', [], $output) ; 

// 实际执行的是 `Illuminate\Console\Application::call()`
    public function call($command, array $parameters = [], $outputBuffer = null)
    {
        [$command, $input] = $this->parseCommand($command, $parameters);

        if (! $this->has($command)) {
            throw new CommandNotFoundException(sprintf('The command "%s" does not exist.', $command));
        }

        return $this->run(
            $input, $this->lastOutput = $outputBuffer ?: new BufferedOutput  // 使用的是这个输出
        );
    }

继续查看 Symfony\Component\Console\Output\BufferedOutput

    protected function doWrite(string $message, bool $newline)
    {
        $this->buffer .= $message;

        if ($newline) {
            $this->buffer .= \PHP_EOL; // 这里仅仅是把运行结果保存到了字符串中,所以没有输出到命令行
        }
    }

至此原因已经明确,那么直接替换 output 对象就行, 打印 app(),也没有找到合适的对象,那就只能自己创建了,也就是开头的这段话

$output = new OutputStyle(new ArgvInput(), new ConsoleOutput());

最终代码

// 数据库迁移
class TestMigration extends Migration
{
    public function up()
    {
        $output = new OutputStyle(new ArgvInput(), new ConsoleOutput());
        Artisan::call('t:t', [], $output);
        $output->writeln('');
    }
    public function down()
    {
        $output = new OutputStyle(new ArgvInput(), new ConsoleOutput());
        Artisan::call('t:t', [], $output);
        $output->writeln('');
    }
}

PS:如果需要考虑代码复用,可以把 $output 单独拎出去,创建 trait
·

// 未测试

use Illuminate\Console\OutputStyle;
use Symfony\Component\Console\Input\ArgvInput;
use Symfony\Component\Console\Output\ConsoleOutput;

trait Output
{
    /**
     * The output interface implementation.
     *
     * @var OutputStyle
     */
    protected $output;

    /**
     * Set the output interface implementation.
     *
     * @param OutputStyle $output
     * @return void
     */
    public function setOutput(OutputStyle $output)
    {
        $this->output = $output;
    }

    /**
     * Get the output implementation.
     *
     * @return OutputStyle
     */
    public function getOutput(): OutputStyle
    {
        if ($this->output instanceof OutputStyle) {
            return $this->output;
        }

        return $this->output = new OutputStyle(new ArgvInput(), new ConsoleOutput());
    }
}

PS:如果有其他更好的方案,最佳答案也会更换

2年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!