国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

"php artisan serve"到底干了什么

TANKING / 2117人閱讀

摘要:最近看了一下這個框架,寫點東西當個筆記。函數會迭代屬性為的,逐一將其注冊,的方法繼承自父類,關鍵的就是在這個里注冊的。

最近看了一下 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
make(IlluminateContractsConsoleKernel::class);

$status = $kernel->handle(
    $input = new SymfonyComponentConsoleInputArgvInput,
    new SymfonyComponentConsoleOutputConsoleOutput
);

$kernel->terminate($input, $status);

exit($status);

代碼里,require __DIR__."/vendor/autoload.php";的 autoload.php 文件是 composer 生成的文件,實際用處就是利用 php 提供 spl_autoload_register 函數注冊一個方法,讓執行時遇到一個未聲明的類時會自動將包含類定義的文件包含進來,舉個例子就是腳本當中并沒有包含任何文件,但卻可以直接 new 一個 SymfonyComponentConsoleInputArgvInput 對象,就是這個 autoload.php 的功勞了。

接下來的這一行,$app = require_once __DIR__."/bootstrap/app.php";,在腳本里實例化一個 IlluminateFoundationApplication 對象,將幾個重要的接口和類綁定在一起,然后將 Application 對象返回,其中接下來用到的 IlluminateContractsConsoleKernel::class 就是在這里和 AppConsoleKernel::class 綁定在一起的。

$kernel = $app->make(IlluminateContractsConsoleKernel::class);,直觀的解釋就是讓 $app 制造出一個 AppConsoleKernel::class 實例(雖然括號里是 IlluminateContractsConsoleKernel::class,但由于跟這個接口綁定在一起的是 AppConsoleKernel::class 所以實際上 $kernel 實際上是 AppConsoleKernel::class)。

之后的就是整個腳本中最重要的一行了,調用 $kernelhandle 方法,AppConsoleKernel::class這個類在項目根目錄下的 app/Console 文件夾里,這個類并沒有實現 handle 方法,實際上調用的是它的父類的 handle方法:


IlluminateFoundationConsoleKernelhandler 方法如下:

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 實際上就是之前實例化的 IlluminateFoundationApplication ,所以調用的 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 = [
    IlluminateFoundationBootstrapLoadEnvironmentVariables::class,
    IlluminateFoundationBootstrapLoadConfiguration::class,
    IlluminateFoundationBootstrapHandleExceptions::class,
    IlluminateFoundationBootstrapRegisterFacades::class,
    IlluminateFoundationBootstrapSetRequestForConsole::class,
    IlluminateFoundationBootstrapRegisterProviders::class,
    IlluminateFoundationBootstrapBootProviders::class,
];

其中 IlluminateFoundationBootstrapRegisterProviders::classbootstrap 會調用 IlluminateFoundationApplication 實例的 registerConfiguredProviders 方法,這個方法會將讀取到的項目配置里的配置項(項目根目錄下的 config/app.php 文件里的 providers)放入一個 IlluminateSupportCollection 對象中,然后和緩存合并并且排除掉其中的重復項作為一個 ProviderRepository 實例的 load 方法的參數,這個 load 方法里會將 $defer 屬性不為 true 的 Provider 類使用 IlluminateFoundationApplicationregister 方法注冊(最簡單理解就是 new 一個該 Provider 對象然后調用該對象的 register 方法)。

artisan 十分重要的一個 ProviderArtisanServiceProvider)的注冊過程非常繞。

項目根目錄下的 config/app.php 里有個 ConsoleSupportServiceProvider$defer 屬性為 true ,所以不會在上面提到的過程中馬上注冊,而會在 bootstrap 中的這句 $this->app->loadDeferredProviders(); 里注冊。

loadDeferredProviders 函數會迭代 $defer 屬性為 true 的 Provider,逐一將其注冊,ConsoleSupportServiceProviderregister 方法繼承自父類 AggregateServiceProvider ,關鍵的 ArtisanServiceProvider 就是在這個 register 里注冊的。

ArtisanServiceProviderregister 方法如下:

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));
}

這個方法會調用自身的方法 registerCommandsregisterCommands 會調用 ArtisanServiceProvider 里所有名字類似 "register{$command}Command" 的方法,這些方法會在 IlluminateFoundationApplication 這個容器(即 IlluminateFoundationApplication 實例,這個類繼承了 IlluminateContainerContainer)中注冊命令,當需要使用這些命令時就會返回一個這些命令的實例:

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 IlluminateConsoleApplication 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 類(實際上是 IlluminateConsoleApplication 類,不過引入時起了個別名)的靜態成員 $bootstrappers 里,這個會在接下來再提及到。

接下來回到 IlluminateFoundationConsoleKernelhandler 方法,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$devCommands merge 在一起后取出值的數組(merge 發生在 ArtisanServiceProviderregister 方法, 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 方法實際上還是調用了父類(SymfonyComponentConsoleApplication)的 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 EventsCommandStarting(
            $commandName, $input, $output = $output ?: new ConsoleOutput
        )
    );

    $exitCode = parent::run($input, $output);

    $this->events->fire(
        new EventsCommandFinished($commandName, $input, $output, $exitCode)
    );

    return $exitCode;
}

$input 參數是在 artisan 腳本里 new 出來的 SymfonyComponentConsoleInputArgvInput 對象,getCommandName 是繼承自父類的方法:

protected function getCommandName(InputInterface $input)
{
    return $this->singleCommand ? $this->defaultCommand : $input->getFirstArgument();
}

也就是說這個方法的返回結果就是 SymfonyComponentConsoleInputArgvInput 對象的 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 沒有實現該方法,所以其實是調用父類 IlluminateConsoleCommandrun,但父類的方法實際也只有一行,那就是調用其父類的 run,所以貼出來的其實是 SymfonyComponentConsoleCommandCommandrun):

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 沒有實現該方法,IlluminateConsoleCommandexecute 方法如下:

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

也就是調用了 ServeCommandhandle 方法:

public function handle()
{
    chdir($this->laravel->publicPath());

    $this->line("Laravel development server started: 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

文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。

轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/28181.html

相關文章

  • Laravel Artisan 命令

    摘要:顯示幫助信息強制輸出禁用輸出 Laravel Framework version 5.1.3 (LTS) Usage: command [options] [arguments] Options: -h, --help 顯示幫助信息 -q, --quiet Do not output any message -V, --ve...

    txgcwm 評論0 收藏0
  • Vue報錯SyntaxError:TypeError:this.getOptionsisnotafunction的解決方法

      一、簡單介紹  Vue 開發中會出現一些問題,比如:Vue報錯SyntaxError:TypeError:this.getOptionsisnotafunction,要如何解決?  二、報錯現象  ERROR Failed to compile with 1 error 上午10:39:05  error in ./src/views/Login.vue?vue&type=style&...

    3403771864 評論0 收藏0
  • Laravel 5.4 入門系列 1. 安裝

    摘要:的安裝與使用是什么是的一個依賴管理工具。它以項目為單位進行管理,你只需要聲明項目所依賴的代碼庫,會自動幫你安裝這些代碼庫。 Composer 的安裝與使用 Composer 是什么 Composer 是 PHP 的一個依賴管理工具。它以項目為單位進行管理,你只需要聲明項目所依賴的代碼庫,Composer 會自動幫你安裝這些代碼庫。 安裝 Composer Mac 下的安裝只需要在命令行...

    hqman 評論0 收藏0
  • 源碼解讀:php artisan serve

    摘要:原文來自在學習的時候,可能很多人接觸的第一個的命令就是,這樣我們就可以跑起第一個的應用。本文來嘗試解讀一下這個命令行的源碼。 原文來自:https://www.codecasts.com/blo... 在學習 Laravel 的時候,可能很多人接觸的第一個 artisan 的命令就是:php artisan serve,這樣我們就可以跑起第一個 Laravel 的應用。本文來嘗試解讀一...

    Loong_T 評論0 收藏0
  • 降低vue-router版本的2種解決方法實例

      在Vue.js官方的路由插件中,vue-router和vue.js是深度集成的,這類頁面適合用于構建單頁面應用。但要注意是由于無法注明版本,一般就默認安裝router4.X,但我們創建的是vue2,只能結合 vue-router 3.x 版本才能使用。現在需要降低版本。  方法  我們知道vue-router 4.x 只能結合 vue3 進行使用,vue-router 3.x 只能結合 vue...

    3403771864 評論0 收藏0

發表評論

0條評論

TANKING

|高級講師

TA的文章

閱讀更多
最新活動
閱讀需要支付1元查看
<