"php artisan serve"到底干了什么
2018-03-30 08:01
501 查看
最近看了一下 laravel 这个框架,写点东西当个笔记。跟着官网上的说明 install 好一个项目后,在项目根目录执行命令
php artisan serve就可以开启一个简易的服务器进行开发,这个命令到底做了什么,看了一下代码,在这里简要描述一下自己的看法。先说明一下,这里项目 install 的方法不是安装 laravel/installer,而是
composer create-project --prefer-dist laravel/laravel blog,写笔记的时候
laravel的版本还是 5.5,以后版本更新后可能就不一样了。artisan 实际上是项目根目录下的一个 php 脚本,而且默认是有执行权限的,所以命令其实可以简写成
artisan serve,脚本的代码行数很少,实际上就十几行:
#!/usr/bin/env php <?php define('LARAVEL_START', microtime(true)); require __DIR__.'/vendor/autoload.php'; $app = require_once __DIR__.'/bootstrap/app.php'; $kernel = $app->make(Illuminate\Contracts\Console\Kernel::class); $status = $kernel->handle( $input = new Symfony\Component\Console\Input\ArgvInput, new Symfony\Component\Console\Output\ConsoleOutput ); $kernel->terminate($input, $status); exit($status);代码里,
require __DIR__.'/vendor/autoload.php';的 autoload.php 文件是 composer 生成的文件,实际用处就是利用 php 提供
spl_autoload_register函数注册一个方法,让执行时遇到一个未声明的类时会自动将包含类定义的文件包含进来,举个例子就是脚本当中并没有包含任何文件,但却可以直接 new 一个
Symfony\Component\Console\Input\ArgvInput对象,就是这个 autoload.php 的功劳了。接下来的这一行,
$app = require_once __DIR__.'/bootstrap/app.php';,在脚本里实例化一个
Illuminate\Foundation\Application对象,将几个重要的接口和类绑定在一起,然后将 Application 对象返回,其中接下来用到的
Illuminate\Contracts\Console\Kernel::class就是在这里和
App\Console\Kernel::class绑定在一起的。
$kernel = $app->make(Illuminate\Contracts\Console\Kernel::class);,直观的解释就是让
$app制造出一个
App\Console\Kernel::class实例(虽然括号里是
Illuminate\Contracts\Console\Kernel::class,但由于跟这个接口绑定在一起的是
App\Console\Kernel::class所以实际上
$kernel实际上是
App\Console\Kernel::class)。之后的就是整个脚本中最重要的一行了,调用
$kernel的
handle方法,
App\Console\Kernel::class这个类在项目根目录下的
app/Console文件夹里,这个类并没有实现
handle方法,实际上调用的是它的父类的
handle方法:
<?php namespace App\Console; use Illuminate\Console\Scheduling\Schedule; use Illuminate\Foundation\Console\Kernel as ConsoleKernel; class Kernel extends ConsoleKernel { ...... }而
Illuminate\Foundation\Console\Kernel的
handler方法如下:
public function handle($input, $output = null) { try { $this->bootstrap(); 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; } }
bootstrap方法如下:
public function bootstrap() { if (! $this->app->hasBeenBootstrapped()) { $this->app->bootstrapWith($this->bootstrappers()); } $this->app->loadDeferredProviders(); if (! $this->commandsLoaded) { $this->commands(); $this->commandsLoaded = true; } }先从
bootstrap方法说起,
$kernel对象里的成员
$app实际上就是之前实例化的
Illuminate\Foundation\Application,所以调用的
bootstrapWith方法是这样的:
public function bootstrapWith(array $bootstrappers) { $this->hasBeenBootstrapped = true; foreach ($bootstrappers as $bootstrapper) { $this['events']->fire('bootstrapping: '.$bootstrapper, [$this]); $this->make($bootstrapper)->bootstrap($this); $this['events']->fire('bootstrapped: '.$bootstrapper, [$this]); } }那么串联起来实际上
bootstrap方法里的这一句
$this->app->bootstrapWith($this->bootstrappers());就是实例化了
$kernel里
$bootstrappers包含的所有类并且调用了这些对象里的
bootstrap方法:
protected $bootstrappers = [ \Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables::class, \Illuminate\Foundation\Bootstrap\LoadConfiguration::class, \Illuminate\Foundation\Bootstrap\HandleExceptions::class, \Illuminate\Foundation\Bootstrap\RegisterFacades::class, \Illuminate\Foundation\Bootstrap\SetRequestForConsole::class, \Illuminate\Foundation\Bootstrap\RegisterProviders::class, \Illuminate\Foundation\Bootstrap\BootProviders::class, ];其中
\Illuminate\Foundation\Bootstrap\RegisterProviders::class的
bootstrap会调用
Illuminate\Foundation\Application实例的
registerConfiguredProviders方法,这个方法会将读取到的项目配置里的配置项(项目根目录下的
config/app.php文件里的
providers)放入一个
Illuminate\Support\Collection对象中,然后和缓存合并并且排除掉其中的重复项作为一个
ProviderRepository实例的
load方法的参数,这个
load方法里会将
$defer属性不为 true 的
Provider类使用
Illuminate\Foundation\Application的
register方法注册(最简单理解就是 new 一个该
Provider对象然后调用该对象的
register方法)。对
artisan十分重要的一个
Provider(
ArtisanServiceProvider)的注册过程非常绕。项目根目录下的
config/app.php里有个
ConsoleSupportServiceProvider,
$defer属性为 true ,所以不会在上面提到的过程中马上注册,而会在
bootstrap中的这句
$this->app->loadDeferredProviders();里注册。
loadDeferredProviders函数会迭代
$defer属性为 true 的
Provider,逐一将其注册,
ConsoleSupportServiceProvider的
register方法继承自父类
AggregateServiceProvider,关键的
ArtisanServiceProvider就是在这个
register里注册的。
ArtisanServiceProvider的
register方法如下:
public function register() { $this->registerCommands(array_merge( $this->commands, $this->devCommands )); } protected function registerCommands(array $commands) { foreach (array_keys($commands) as $command) { call_user_func_array([$this, "register{$command}Command"], []); } $this->commands(array_values($commands)); }这个方法会调用自身的方法
registerCommands,
registerCommands会调用
ArtisanServiceProvider里所有名字类似
"register{$command}Command"的方法,这些方法会在
Illuminate\Foundation\Application这个容器(即
Illuminate\Foundation\Application实例,这个类继承了
Illuminate\Container\Container)中注册命令,当需要使用这些命令时就会返回一个这些命令的实例:
protected function registerServeCommand() { $this->app->singleton('command.serve', function () { return new ServeCommand; }); }以 serve 这个命令为例,这个方法的用处就是当需要从容器里取出
command.serve时就会得到一个
ServeCommand实例。
registerCommands方法里还有一个重要的方法调用,
$this->commands(array_values($commands));,
ArtisanServiceProvider里并没有这个方法的声明,所以这个方法其实是在其父类
ServiceProvider实现的:
use Illuminate\Console\Application as Artisan; ...... public function commands($commands) { $commands = is_array($commands) ? $commands : func_get_args(); Artisan::starting(function ($artisan) use ($commands) { $artisan->resolveCommands($commands); }); }
Artisan::starting这个静态方法的调用会将括号里的匿名函数添加到
Artisan类(实际上是
Illuminate\Console\Application类,不过引入时起了个别名)的静态成员
$bootstrappers里,这个会在接下来再提及到。接下来回到
Illuminate\Foundation\Console\Kernel的
handler方法,
return $this->getArtisan()->run($input, $output);,
getArtisan方法如下:
protected function getArtisan() { if (is_null($this->artisan)) { return $this->artisan = (new Artisan($this->app, $this->events, $this->app->version())) ->resolveCommands($this->commands); } return $this->artisan; }该方法会 new 出一个
Artisan对象, 而这个类会在自己的构造函数调用
bootstrap方法:
protected function bootstrap() { foreach (static::$bootstrappers as $bootstrapper) { $bootstrapper($this); } }这时候刚才被提及到的匿名函数就是在这里发挥作用,该匿名函数的作用就是调用
Artisan对象的
resolveCommands方法:
public function resolve($command) { return $this->add($this->laravel->make($command)); } public function resolveCommands($commands) { $commands = is_array($commands) ? $commands : func_get_args(); foreach ($commands as $command) { $this->resolve($command); } return $this; }
resolveCommands方法中迭代的
$commands参数实际上是
ArtisanServiceProvider里的两个属性
$commands和
$devCommandsmerge 在一起后取出值的数组(merge 发生在
ArtisanServiceProvider的
register方法,
registerCommands中使用
array_values取出其中的值),所以对于 serve 这个命令,实际上发生的是
$this->resolve('command.serve');,而在之前已经提到过,
ArtisanServiceProvider的
"register{$command}Command"的方法会在容器里注册命令,那么
resolve方法的结果将会是将一个 new 出来
ServeCommand对象作为参数被传递到
add方法:
public function add(SymfonyCommand $command) { if ($command instanceof Command) { $command->setLaravel($this->laravel); } return $this->addToParent($command); } protected function addToParent(SymfonyCommand $command) { return parent::add($command); }
add方法实际上还是调用了父类(
Symfony\Component\Console\Application)的
add:
public function add(Command $command) { ...... $this->commands[$command->getName()] = $command; ...... return $command; }关键在
$this->commands[$command->getName()] = $command;,参数
$command已经知道是一个
ServeCommand对象,所以这一句的作用就是在
Artisan对象的
$commands属性添加了一个键为
serve、值为
ServeCommand对象的成员。
getArtisan方法执行完后就会调用其返回的
Artisan对象的
run方法:
public function run(InputInterface $input = null, OutputInterface $output = null) { $commandName = $this->getCommandName( $input = $input ?: new ArgvInput ); $this->events->fire( new Events\CommandStarting( $commandName, $input, $output = $output ?: new ConsoleOutput ) ); $exitCode = parent::run($input, $output); $this->events->fire( new Events\CommandFinished($commandName, $input, $output, $exitCode) ); return $exitCode; }
$input参数是在
artisan脚本里 new 出来的
Symfony\Component\Console\Input\ArgvInput对象,
getCommandName是继承自父类的方法:
protected function getCommandName(InputInterface $input) { return $this->singleCommand ? $this->defaultCommand : $input->getFirstArgument(); }也就是说这个方法的返回结果就是
Symfony\Component\Console\Input\ArgvInput对象的
getFirstArgument方法的返回值:
public function __construct(array $argv = null, InputDefinition $definition = null) { if (null === $argv) { $argv = $_SERVER['argv']; } // strip the application name array_shift($argv); $this->tokens = $argv; parent::__construct($definition); } ...... public function getFirstArgument() { foreach ($this->tokens as $token) { if ($token && '-' === $token[0]) { continue; } return $token; } }
getFirstArgument方法会将属性
$tokens里第一个不包含
'-'的成员返回,而
$tokens属性的值是在构造函数里生成的,所以可以知道
getCommandName的结果就是 serve 。接下来
Artisan对象调用了父类的
run方法(篇幅太长,省略掉一点):
public function run(InputInterface $input = null, OutputInterface $output = null) { ...... try { $exitCode = $this->doRun($input, $output); } catch (\Exception $e) { if (!$this->catchExceptions) { throw $e; ...... } public function doRun(InputInterface $input, OutputInterface $output) { ...... $name = $this->getCommandName($input); ...... try { $e = $this->runningCommand = null; // the command name MUST be the first element of the input $command = $this->find($name); ...... $this->runningCommand = $command; $exitCode = $this->doRunCommand($command, $input, $output); $this->runningCommand = null; return $exitCode; } protected function doRunCommand(Command $command, InputInterface $input, OutputInterface $output) { ...... if (null === $this->dispatcher) { return $command->run($input, $output); } ...... }
run方法又会调用
doRun,而该方法会先使用
getCommandName获取到命令的名字(
'serve'),然后使用
find方法找出与该命令对应的
Command对象(在
$commands属性中查找,该属性的结构类似
'serve' => 'ServeCommand'),被找出来的
Command对象会被作为参数传递到
doRunCommand方法,最后在其中调用该对象的
run方法(
ServeCommand没有实现该方法,所以其实是调用父类
Illuminate\Console\Command的
run,但父类的方法实际也只有一行,那就是调用其父类的
run,所以贴出来的其实是
Symfony\Component\Console\Command\Command的
run):
public function run(InputInterface $input, OutputInterface $output) { ...... if ($this->code) { $statusCode = call_user_func($this->code, $input, $output); } else { $statusCode = $this->execute($input, $output); } return is_numeric($statusCode) ? (int) $statusCode : 0; }
$code并没有赋值过,所以执行的是
$this->execute($input, $output);,
ServeCommand没有实现该方法,
Illuminate\Console\Command的
execute方法如下:
protected function execute(InputInterface $input, OutputInterface $output) { return $this->laravel->call([$this, 'handle']); }也就是调用了
ServeCommand的
handle方法:
public function handle() { chdir($this->laravel->publicPath()); $this->line("<info>Laravel development server started:</info> <http://{$this->host()}:{$this->port()}>"); passthru($this->serverCommand()); } protected function serverCommand() { return sprintf('%s -S %s:%s %s/server.php', ProcessUtils::escapeArgument((new PhpExecutableFinder)->find(false)), $this->host(), $this->port(), ProcessUtils::escapeArgument($this->laravel->basePath()) ); }所以如果想打开一个简易的服务器做开发,把目录切换到根目录的
public目录下,敲一下这个命令,效果是差不多的,
php -S 127.0.0.1:8000 ../server.php。
相关文章推荐
- "架构"到底是个什么东西? 转
- “面向对象"和"面向过程"到底有什么区别?
- php 正则中的"i,m,s,x,e"分别表示什么
- 惠普中国CEO退休感言"我们工作到底为了什么"
- php 正则中的"i,m,s,x,e"分别表示什么
- php 正则中的"i,m,s,x,e"分别表示什么
- C语言中的"NULL"到底是什么?又不是什么?有什么用?怎么用?
- PHP "松散比较"
- [php5.2.4] explode函数不能按照"\r\n"切割字符串
- "int?" 是什么类型?和"int"有何区别
- "超级计算机"到底有多快?
- PHP的文件加载机制到底是什么目录
- PHP正则中的"i,m,s,x,e"分别表示什么
- "=="和equals方法究竟有什么区别
- 对于php.ini中常会触及到的"限制"总结
- 什么是 "asmlinkage"?
- C语言中的"NULL"到底是什么?又不是什么?有什么用?怎么用?
- "=="和equals方法究竟有什么区别?
- 许可方式 到底"非商业用途"意味着什么?
- vPro这个"v"字代表什么意思