Lumen 源码阅读记录 ---- 队列(3)

@liubb  October 23, 2018

此章节过于繁琐,只列出关键方法和步骤
命令执行

php artisan queue:work aaa --daemon --queue

看向 artisan 文件

$kernel = $app->make(
    'Illuminate\Contracts\Console\Kernel'
);

exit($kernel->handle(new ArgvInput, new ConsoleOutput));

make方法不讲,由于在app.php文件里

$app->singleton(
    Illuminate\Contracts\Console\Kernel::class,
    App\Console\Kernel::class
);

所以此处的 $kernel 为 AppConsoleKernel::class
看向handle方法

public function handle($input, $output = null)
{
    try {
        return $this->getArtisan()->run($input, $output);
    } catch (Exception $e) {
        $this->reportException($e);

        $this->renderException($output, $e);

        return 1;
    } catch (Throwable $e) {
        $e = new FatalThrowableError($e);

        $this->reportException($e);

        $this->renderException($output, $e);

        return 1;
    }
}

protected function getArtisan()
{
    if (is_null($this->artisan)) {
        return $this->artisan = (new Artisan($this->app, $this->app->make('events'), $this->app->version()))
                            ->resolveCommands($this->getCommands());
    }

    return $this->artisan;
}

代码最终会来到

public function add(Command $command)
{
    $this->init();

    $command->setApplication($this);

    if (!$command->isEnabled()) {
        $command->setApplication(null);

        return;
    }

    if (null === $command->getDefinition()) {
        throw new LogicException(sprintf('Command class "%s" is not correctly initialized. You probably forgot to call the parent constructor.', get_class($command)));
    }

    $this->commands[$command->getName()] = $command;

    foreach ($command->getAliases() as $alias) {
        $this->commands[$alias] = $command;
    }

    return $command;
}

此处的 $command->getName() 是个难点

public function getName()
{
    return $this->name;
}

$this->name的方法是在Command的构造方法里,根据每个文件的signature解析而来,例如

protected $signature = 'teacher:migrate
    {action} 
    {from?* : k12_teacher:333}
'
protected function configureUsingFluentDefinition()
{
    list($name, $arguments, $options) = Parser::parse($this->signature);

    parent::__construct($this->name = $name);

    // After parsing the signature we will spin through the arguments and options
    // and set them on this command. These will already be changed into proper
    // instances of these "InputArgument" and "InputOption" Symfony classes.
    foreach ($arguments as $argument) {
        $this->getDefinition()->addArgument($argument);
    }

    foreach ($options as $option) {
        $this->getDefinition()->addOption($option);
    }
}

接下来回到

$this->getArtisan()->run($input, $output);

public function run() {
···
    $exitCode = $this->doRun($input, $output);
···
}

public function doRun(InputInterface $input, OutputInterface $output)
{
    ···
    //本例子中此处返回 queue:work 较简单不展开讲
    $name = $this->getCommandName($input);

    try {
        $e = $this->runningCommand = null;
        //本例子中 此处返回的是 WorkCommand 类
        $command = $this->find($name);
    } catch (\Exception $e) {
    } catch (\Throwable $e) {
    }
    
    $this->runningCommand = $command;
    $exitCode = $this->doRunCommand($command, $input, $output);
    $this->runningCommand = null;

    return $exitCode;
}

重点在

$command = $this->find($name);

跟进find()方法之后,重点在

$command = $this->commands[$name];

那么就要回过头来看$this->commands在哪里赋值的。
本例子中需要找到 $this->commands['queue:work']
这个框架恶心的地方就是封装太多,太绕。

app.php
$app->register(WorkflowBusinessConsoleConsoleServiceProvider::class);

ConsoleServiceProvider.php
public function register()

{
    $this->registerCommands(array_merge(
        $this->commands, $this->devCommands
    ));
}

$this->commands 里有个 'QueueWork' => 'command.queue.work',看到registerCommands方法

protected function registerCommands(array $commands)
{
    foreach (array_keys($commands) as $command) {
        call_user_func_array([$this, "register{$command}Command"], []);
    }

    $this->commands(array_values($commands));
}

以本例子,调用registerQueueWork方法

protected function registerQueueWorkCommand()
{
    $this->app->singleton('command.queue.work', function ($app) {
        return new QueueWorkCommand($app['queue.worker']);
    });
}

接着调用

public function commands($commands)
{
    $commands = is_array($commands) ? $commands : func_get_args();

    Artisan::starting(function ($artisan) use ($commands) {
        $artisan->resolveCommands($commands);
    });
}
public static function starting(Closure $callback)
{
    static::$bootstrappers[] = $callback;
}

回到

$this->artisan = (new Artisan($this->app, $this->app->make('events'), $this->app->version()))
                            ->resolveCommands($this->getCommands());

在实例化 Artisan的时候,触发构造方法,构造方法里有个 bootstrap()方法

protected function bootstrap()
{
    foreach (static::$bootstrappers as $bootstrapper) {
        $bootstrapper($this);
    }
}

也就是执行

function ($artisan) use ($commands) {
    $artisan->resolveCommands($commands);
}

public function resolve($command)
{
    return $this->add($this->laravel->make($command));
}

这里的command是command.queue:work 上面singleton了,所以这里add的是QueueWorkCommand类
又由于

use Illuminate\Queue\Console\WorkCommand as QueueWorkCommand;

所以其实是WorkCommand类,这里的代码跟进去最后就是

$this->commands['queue.work'] = WorkCommand类;

回到上面的 $command = find('queue.work')

$exitCode = $this->doRunCommand($command, $input, $output);

进入doRunCommand()方法,一些其他非关键语句略过

$command->run($input, $output);

进入run()方法

$statusCode = $this->execute($input, $output);

进入 WorkCommand 类

protected function execute(InputInterface $input, OutputInterface $output)
{
    return $this->laravel->call([$this, 'handle']);
}

执行 WorkCommand类的 handle 方法
启动队列
到此为止,队列启动完毕。

队列的原理大概介绍下。作为守护进程时,有个while(true),将进程挂起,然后使用redis->pop 将任务取出。


添加新评论