此章节过于繁琐,只列出关键方法和步骤
命令执行
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 将任务取出。