Heim  >  Artikel  >  PHP-Framework  >  Analysieren der ThinkPHP6-Anwendungsinitialisierung

Analysieren der ThinkPHP6-Anwendungsinitialisierung

藏色散人
藏色散人nach vorne
2021-07-13 13:51:433153Durchsuche

runWithRequest ()-Methode

In der run()-Methode der Http-Klasse erhalten Sie think\Request Danach Wenn Sie eine Instanz der Code>-Klasse erstellen, führt das Programm dann <code>$response = $this->runWithRequest(request); aus. Darunter lauten die ersten Zeilen der Methode runWithRequest() wie folgt: Http 类的 run() 方法中,得到 think\Request 类的实例后,程序接着执行 $response = $this->runWithRequest(request); 。其中,runWithRequest() 方法前面几行如下:

protected function runWithRequest(Request $request)
{
    $this->initialize();

    // 加载全局中间件
    $this->loadMiddleware();
    .
    .
    .

该方法第一行执行 $this->initialize();,对应用进行初始化,接下来详细分析这一初始化操作。  
Http 类的 initialize() 方法:

protected function initialize()
{
    //如果还未初始化,则初始化之
    if (!$this->app->initialized()) {
        $this->app->initialize();
    }
}

实际上是调用 App 类的 initialize() 方法。该方法代码:

public function initialize()
{
    // 设置应用状态为已经初始化
    $this->initialized = true;

    //记录开始时间
    $this->beginTime = microtime(true);
    //记录起始内存使用量
    $this->beginMem  = memory_get_usage();

    // ==( A )== 加载环境变量
    // $this->env跟前面的(new App())->http和$this->config都是同样的套路
    if (is_file($this->rootPath . '.env')) {
        $this->env->load($this->rootPath . '.env');
    }
    //设置配置文件后缀
    $this->configExt = $this->env->get('config_ext', '.php');
    // ==( B )== 设置调试模式
    $this->debugModeInit();

    // ==( C )== 加载应用文件和配置等操作
    $this->load();

    // 加载框架默认语言包
    $langSet = $this->lang->defaultLangSet();
    // 框架目录下对应的语言包
    // 比如:D:\dev\tp6\vendor\topthink\framework\src\lang\zh-cn.php
    $this->lang->load($this->thinkPath . 'lang' . DIRECTORY_SEPARATOR . $langSet . '.php');

    // 加载应用默认语言包
    // 这个会扫描「app/lang」里面,对应语言包文件夹的所有「.php」文件
    // 比如,app/lang/zh-cn/* 下的所有文件
    // 然后加载解析
    $this->loadLangPack($langSet);

    // 监听AppInit
    $this->event->trigger('AppInit');

    // 设置时区
    date_default_timezone_set($this->config->get('app.default_timezone', 'Asia/Shanghai'));

    // ==( D )== 初始化
    // 初始化错误和异常处理、注册系统服务和初始化系统服务
    foreach ($this->initializers as $initializer) {
        $this->make($initializer)->init($this);
    }

    return $this;
}

应用的初始化做了大量的操作,其主要的操作有:加载环境变量、加载配置文件,加载语言包、监听 AppInit、initializers 数组包含的类的初始化。

(A) 加载环境变量

对应语句:$this->env->load($this->rootPath . ‘.env’);,其中,$this->env,与前面的 (new App())->http 原理是一样的(参见第一篇),它可以取得 thinkEnv 类的实例。取得 Env 类实例后,调用 load() 方法,传入的参数是.env 文件所在地址。load() 方法实现如下:

public function load(string $file): void
{
    $env = parse_ini_file($file, true) ?: [];
    $this->set($env);
}

该方法读取.env 文件的值后,调用 set() 方法,将配置保存在 Env 类的 $data 成员变量。set() 方法代码:

public function set($env, $value = null): void
{
    if (is_array($env)) {
        //全部KEY转为大写字母
        $env = array_change_key_case($env, CASE_UPPER);

        foreach ($env as $key => $val) {
            //有二级配置的,转为KEY1_KEY2 => $v 的形式
            if (is_array($val)) {
                foreach ($val as $k => $v) {
                    $this->data[$key . '_' . strtoupper($k)] = $v;
                }
            } else {
                $this->data[$key] = $val;
            }
        }
        //ENV的值不是数组的情况
    } else {
        $name = strtoupper(str_replace('.', '_', $env));

        $this->data[$name] = $value;
    }
}

.env 读取到的值大概是这样的:  
Analysieren der ThinkPHP6-Anwendungsinitialisierung

$this->set($env) 之后得到的大概是这样的:  

Analysieren der ThinkPHP6-Anwendungsinitialisierung

(B) 调试模式设置

$this->debugModeInit() 运行原理详见注释。

protected function debugModeInit(): void
{
    // 应用调试模式
    if (!$this->appDebug) {
        $this->appDebug = $this->env->get('app_debug') ? true : false;
        // 关闭错误显示
        ini_set('display_errors', 'Off');
    }
    // 如果不是命令行模式
    if (!$this->runningInConsole()) {
        // 重新申请一块比较大的buffer
        // php缓冲控制
        // 参考:https://www.php.net/manual/en/ref.outcontrol.php
        // https://www.cnblogs.com/saw2012/archive/2013/01/30/2882451.html
        // 新版PHP默认开启缓冲区ob_start(),ob_get_level() == 1
        if (ob_get_level() > 0) {
            // 相当于ob_get_contents() 和 ob_clean()
            // 获取缓冲区内容并删除缓冲区内容
            $output = ob_get_clean();
        }
        // 开启新的缓冲区控制
        ob_start();
        if (!empty($output)) {
            // 由于开启了新的缓冲区控制,
            // 这里不会直接输出$output
            // 而是等到依次执行了ob_flush()和flash()之后才将内容输出到浏览器
            echo $output;
        }
    }
}

需要注意的是,这里貌似有个 Bug,应该先执行 $this->appDebug = $this->env->get('app_debug') ? true : false; 获取是否是调试模式的配置,然后再判断:if(!$this->appDebug)

(C)加载应用文件和配置等操作

接下来执行 $this->load();,「load」方法具体实现如下:

protected function load(): void
{
    $appPath = $this->getAppPath();

    // 加载「app」目录下的「common.php」文件
    if (is_file($appPath . 'common.php')) {
        include_once $appPath . 'common.php';
    }
    // 加载核心目录下的「helper.php」文件
    // 可以看到,这里的加载顺序:先「common.php」,后「helper.php」
    // 且「helper.php」中的函数包裹在「if (!function_exists('xxx'))」下
    // 所以可以在「common.php」文件中覆盖系统定义的助手函数
    include_once $this->thinkPath . 'helper.php';

    $configPath = $this->getConfigPath();

    $files = [];

    // glob的作用是扫描给定路径模式下的文件,非常好用
    // 这里扫描「config」目录下的所有「.php」文件
    if (is_dir($configPath)) {
        $files = glob($configPath . '*' . $this->configExt);
    }

    foreach ($files as $file) {
        // $this->config 还是同样的套路获得了「Config」类的实例
        // 「load」的第二个参数为一级配置名,这里传入一个文件名,所有文件名作为一级配置
        // 比如「app.php」配置文件,一级配置为「app」
        // 在 「Config」类作用域下的操作:
        // 「load」加载文件后,通过「parse」方法解析文件内容
        // 最后,通过「set」方法将所有配置合并了「config」成员变量
        $this->config->load($file, pathinfo($file, PATHINFO_FILENAME));
    }

    // 加载「app」目录下的「event.php」文件
    if (is_file($appPath . 'event.php')) {
        $this->loadEvent(include $appPath . 'event.php');
    }
    // 注册自定义的服务
    if (is_file($appPath . 'service.php')) {
        $services = include $appPath . 'service.php';
        foreach ($services as $service) {
            $this->register($service);
        }
    }
}

值得一提的是,程序先加载「common.php」,后加载「helper.php」,而「helper.php」中的函数包裹在「if (!function_exists (‘xxx’))」下,所以我们如果有需要,可以在「common.php」文件中覆盖系统定义的助手函数。

除了加载这两个文件,该方法还扫描了「config」目录下的所有配置文件,并将其加载到 Config 类的 $config 成员变量,加载了「app」目录下的「event.php」文件,以及加载并注册自定义的服务。

(D) 初始化错误和异常处理、注册系统服务和初始化系统服务

接着,看初始化函数的最后一段:

foreach ($this->initializers as $initializer) {
    $this->make($initializer)->init($this);
}

这几行代码做了比较多的操作:分别实例化包含在里面的类,然后调用其「init」方法。initializers 数组的值如下:

protected $initializers = [
    Error::class,  //错误处理类
    RegisterService::class, //注册系统服务类
    BootService::class,  //启动系统服务
];

略过系统错误处理类,先看注册系统服务类。值得注意的是,这个类有一个成员变量:

protected $services = [
    PaginatorService::class,
    ValidateService::class,
    ModelService::class,
];

包含了三个系统核心服务。在其 init 方法中,这些服务被注册到系统服务,与前面的自定义服务合并起来,其主要实现代码:

foreach ($services as $service) {
    if (class_exists($service)) {
        // 注册到系统服务
        $app->register($service);
    }
}

最后实例化的是启动系统服务类,该类的 init 方法仅调用了 App 类的 boot 方法,该方法的作用是初始化每个系统服务,也就是调用每个服务的 boot 方法。  
启动系统服务类实现如下:

class BootService
{
    public function init(App $app)
    {
        $app->boot();
    }
}

App 类的 boot 方法:

public function boot(): void
{
    array_walk($this->services, function ($service) {
        $this->bootService($service);
    });
}

其中关键是 bootService 方法:

public function bootService($service)
{
    if (method_exists($service, 'boot')) {
        return $this->invoke([$service, 'boot']);
    }
}

该方法分别调用了每个服务的 boot 方法,从而初始化已注册的服务。  
从以上代码可以看到,系统注册的服务的来源有三个地方:

  1. 系统自带的,如 PaginatorServiceValidateServiceModelServicerrreee
  2. Die erste Zeile dieser Methode führt $this->initialize(); aus Um die Anwendung zu initialisieren, analysieren wir diesen Initialisierungsvorgang im Detail.
    Die initialize()-Methode der Http-Klasse:
  3. rrreee
  4. ruft tatsächlich die initialize()Appauf > Klasse /Code> Methode. Code dieser Methode:
  5. rrreee
  6. Die Initialisierung der Anwendung führt viele Operationen aus. Ihre Hauptoperationen sind: Laden von Umgebungsvariablen, Laden von Konfigurationsdateien, Laden von Sprachpaketen, Überwachung von AppInit und Initialisierung von im Initialisierungsarray enthaltenen Klassen.

(A) Umgebungsvariablen laden

Entsprechende Anweisung: $this->env->load($this->rootPath . '.env');, Unter diesen hat $this->env das gleiche Prinzip wie das vorherige (new App())->http (siehe den ersten Artikel), und das ist auch möglich erhalten werden Eine Instanz der thinkEnv-Klasse. Nachdem Sie die Klasseninstanz Env erhalten haben, rufen Sie die Methode load() auf. Der übergebene Parameter ist die Adresse der Datei .env. Die load()-Methode wird wie folgt implementiert:

rrreee🎜Nach dem Lesen des Werts der .env-Datei ruft diese Methode den set()auf >-Methode zum Konfigurieren von Saved in der Mitgliedsvariablen $data der Klasse Env. set() Methodencode: 🎜rrreee🎜Der aus .env gelesene Wert ist wahrscheinlich so:
Analysieren der ThinkPHP6-Anwendungsinitialisierung🎜🎜$this-> set($env ) Code> Was Sie danach erhalten, ist wahrscheinlich so: 🎜🎜Analysieren der ThinkPHP6-Anwendungsinitialisierung🎜

(B) Debug-Modus-Einstellung

🎜$this->debugModeInit() Einzelheiten zum Funktionsprinzip finden Sie in den Kommentaren. 🎜rrreee🎜Es sollte beachtet werden, dass hier ein Fehler vorliegt. Sie sollten zuerst $this->appDebug = $this->env->get('app_debug') : false; Rufen Sie die Konfiguration des Debug-Modus ab und beurteilen Sie dann: if(!$this->appDebug). 🎜

(C) Anwendungsdateien und Konfigurationen laden

🎜Als nächstes $this->load(); ausführen, wird die „load“-Methode wie folgt implementiert: 🎜rrreee🎜 It Erwähnenswert ist, dass das Programm zuerst „common.php“ und dann „helper.php“ lädt und die Funktionen in „helper.php“ unter „if (!function_exists ('xxx'))“ verpackt sind, sodass wir bei Bedarf können Sie die systemdefinierte Hilfsfunktion in der Datei „common.php“ überschreiben. 🎜🎜Zusätzlich zum Laden dieser beiden Dateien scannt diese Methode auch alle Konfigurationsdateien im Verzeichnis „config“ und lädt sie in die Mitgliedsvariable $config der Klasse Config , die Datei „event.php“ in das Verzeichnis „app“ geladen und den benutzerdefinierten Dienst geladen und registriert. 🎜

(D) Initialisierungsfehler- und Ausnahmebehandlung, Registrierung von Systemdiensten und Initialisierung von Systemdiensten

🎜Dann schauen Sie sich den letzten Absatz der Initialisierungsfunktion an: 🎜rrreee🎜Diese Codezeilen bewirken eine Menge Operationen: separate Beispiele Transformieren Sie die darin enthaltene Klasse und rufen Sie dann ihre Methode „init“ auf. Der Wert des Arrays initializers lautet wie folgt: 🎜rrreee🎜Überspringen Sie die Systemfehlerbehandlungsklasse und sehen Sie sich zuerst die Registrierungssystemdienstklasse an. Es ist erwähnenswert, dass diese Klasse eine Mitgliedsvariable hat: 🎜rrreee🎜, die drei Kernsystemdienste enthält. In seiner init-Methode werden diese Dienste beim Systemdienst registriert und mit dem vorherigen benutzerdefinierten Dienst zusammengeführt. Sein Hauptimplementierungscode lautet: 🎜rrreee🎜Die letzte Instanziierung besteht darin, die Systemdienstklasse zu starten Die Methode init ruft nur die Methode boot der Klasse App auf. Die Funktion dieser Methode besteht darin, jeden Systemdienst zu initialisieren, also aufzurufen Die Methode boot.
Die Startup-Systemdienstklasse wird wie folgt implementiert: 🎜rrreee🎜boot-Methode der App-Klasse: 🎜rrreee🎜Der Schlüssel ist der bootService-Methode: 🎜rrreee🎜Diese Methode ruft die <code>boot-Methode jedes Dienstes separat auf, um den registrierten Dienst zu initialisieren.
Wie Sie dem obigen Code entnehmen können, gibt es drei vom System registrierte Dienstquellen: 🎜
    🎜Das System wird mitgeliefert, z. B. PaginatorService, ValidateService , ModelService; 🎜🎜app-Verzeichnis, angepasst in der Datei „service.php“ 🎜🎜vendor-Verzeichnis, definiert in der Datei „service.php“. 🎜🎜🎜Nach der Initialisierung sieht die Instanz der Klasse „App“ wahrscheinlich so aus: 🎜

    Analysieren der ThinkPHP6-Anwendungsinitialisierung

Das obige ist der detaillierte Inhalt vonAnalysieren der ThinkPHP6-Anwendungsinitialisierung. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Dieser Artikel ist reproduziert unter:segmentfault.com. Bei Verstößen wenden Sie sich bitte an admin@php.cn löschen