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
读取到的值大概是这样的:
$this->set($env)
之后得到的大概是这样的:
(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
方法,从而初始化已注册的服务。
从以上代码可以看到,系统注册的服务的来源有三个地方:
- 系统自带的,如
PaginatorService
,ValidateService
,ModelService
rrreee Die erste Zeile dieser Methode führt - rrreee ruft tatsächlich die
- rrreee 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.
$this->initialize();
aus Um die Anwendung zu initialisieren, analysieren wir diesen Initialisierungsvorgang im Detail. Die
initialize()
-Methode der Http
-Klasse: initialize()App
auf > Klasse /Code> Methode. Code dieser Methode: (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:
.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: 🎜🎜
$this-> set($env ) Code> Was Sie danach erhalten, ist wahrscheinlich so: 🎜🎜🎜(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: 🎜