#### 核心運行類
現在我們已經有了配置類,至于配置文件中有什么樣的鍵值對,我們可以邊做,需要時再去定義,這相當于我們的準備工作,現在我們可以開始繼續順著我們的執行流程繼續做啦~
接下來我們要做的是核心運行類``S.php``,在前面我寫過的``入口文件及核心文件``的最后,是引入核心運行類,并執行這個類的run方法
~~~
4.引入加載函數
``include(CORE_PATH . 'S.php');``
5.執行加載函數的run方法
``S::run();``
~~~
下面開始寫這個核心運行類``S.php``:
(先貼出全部代碼,再進行詳細解釋)
~~~
<?php
namespace S;
class S{
private static $prefixes = [];
public static function run(){
//應該做的是:設置字符集,系統類映射,自動加載注冊方法,實例化路由
self::setHeader();
self::getMapList();
spl_autoload_register('self::s_autoload');
try{
new Route();
}catch (Exception $e){
$e->getDetail();
}
}
private static function setHeader(){
header("Content-type:text/html;Charset=".C('default_charset'));
date_default_timezone_set(C('default_timezone'));
}
public static function addNamespace($prefix, $base_dir, $prepend = false)
{
//格式化命名空間前綴,以反斜杠結束(去除兩側的反斜杠,只在最后加一個反斜杠)
$prefix = trim($prefix, '\\') . '\\';
//格式化基目錄以正斜杠結尾,DIRECTORY_SEPARATOR是系統常量,目錄分隔符,把基目錄右側斜杠去掉,換成系統支持的斜杠,然后最后統一為正斜杠
$base_dir = rtrim($base_dir, '/') . DIRECTORY_SEPARATOR;
$base_dir = rtrim($base_dir, DIRECTORY_SEPARATOR) . '/';
//初始化命名空間前綴數組
//如果前綴已存在數組中則跳過,否則存入數組
if (isset(self::$prefixes[$prefix]) === false) {
self::$prefixes[$prefix] = [];
}
if ($prepend) {
//命名空間前綴相同時,后增基目錄(array_unshift() 函數用于向數組插入新元素。新數組的值將被插入到數組的開頭。)
array_unshift(self::$prefixes[$prefix], $base_dir);
} else {
//前增,向數組尾部增加值
array_push(self::$prefixes[$prefix], $base_dir);
}
}
private static function getMapList()
{
//實例化Config類,執行get函數,獲取到namespace_map_list的值,循環更改$prefixes的值
foreach (Config::getInstance()->get('namespace_map_list') as $key => $value) {
self::addNamespace($key, $value);
}
}
private static function s_autoload($className){
// 當前命名空間前綴
$prefix = $className;
//從后面開始遍歷完全合格類名中的命名空間名稱,來查找映射的文件名
//strpos獲取參數2在參數1中最后出現的位置,substr截取字符串
while (false !== $pos = strrpos($prefix, '\\')) {
// 命名空間前綴
$prefix = substr($className, 0, $pos + 1);
// 相對的類名
$relative_class = substr($className, $pos + 1);
//嘗試加載與映射文件相對的類
$mapped_file = self::loadMappedFile($prefix, $relative_class);
// var_dump($mapped_file);
if ($mapped_file) {
return $mapped_file;
}
//去除前綴的反斜杠
$prefix = rtrim($prefix, '\\');
}
return false;
}
private static function loadMappedFile($prefix, $relative_class)
{
//這個命名空間前綴是否存在基本的目錄?
if (isset(self::$prefixes[$prefix]) === false) {
return false;
}
$relative_class = str_replace('\\', '/', $relative_class);
foreach (self::$prefixes[$prefix] as $base_dir) {
$file = $base_dir . $relative_class . '.php';
// 如果映射文件存在就加載它
if (self::requireFile($file)) {
return true;
}
}
return false;
}
private static function requireFile($file)
{
if (file_exists($file)) {
include $file;
return true;
}
return false;
}
}
~~~
好了,現在進行詳細解釋:
首先,我們執行的是這個類的run方法:
~~~
public static function run(){
self::setHeader(); //執行本類的setHeader方法,該方法用于設置字符集
self::getMapList(); //然后執行getMapList方法,用來把命名空間的路徑映射為真實目錄路徑
spl_autoload_register('self::s_autoload'); //自動加載函數,當需要實例化一個沒有找到的類時,就會調用本類的s_autoload方法
try{
new Route(); //實例化路由,這個類用于解析URL
}catch (Exception $e){
$e->getDetail();
}
}
~~~
下面開始詳細講解里面用到的方法
setHeader方法
~~~
private static function setHeader(){
//設置默認字符集,這里用到了上一章我們定義的C函數,獲取到了配置項‘default_charset’的值
header("Content-type:text/html;Charset=".C('default_charset'));
//設置默認時區,這個設置主要就是影響時間函數中取得的結果
date_default_timezone_set(C('default_timezone'));
}
~~~
header()函數是一個非常重要的函數,用于設置響應頭部,比如我們在配置文件中加入配置項``'default_charset'=>'UTF-8',``那么當我們通過網址訪問網站,所獲得的響應就會使用uft8進行編碼,如果網頁中的字符集設置的是GBK的話,那么就會出現亂碼,所以指定一個統一的字符集是非常必要的。
date_default_timezone_set()設置時區,這里在配置文件中添加配置項``'default_timezone'=>'PRC'``,表示默認時區是中國時區
* * * * *
接下來是``getMapList()``方法:
~~~
private static function getMapList()
{
//實例化Config類,執行get函數,獲取到namespace_map_list的值,循環更改$prefixes的值
foreach (Config::getInstance()->get('namespace_map_list') as $key => $value) {
self::addNamespace($key, $value);
}
}
public static function addNamespace($prefix, $base_dir, $prepend = false)
{
//格式化命名空間前綴,以反斜杠結束(去除兩側的反斜杠,只在最后加一個反斜杠)
$prefix = trim($prefix, '\\') . '\\';
//格式化基目錄以正斜杠結尾,DIRECTORY_SEPARATOR是系統常量,目錄分隔符,把基目錄右側斜杠去掉,換成系統支持的斜杠,然后最后統一為正斜杠
$base_dir = rtrim($base_dir, '/') . DIRECTORY_SEPARATOR;
$base_dir = rtrim($base_dir, DIRECTORY_SEPARATOR) . '/';
//初始化命名空間前綴數組
//如果前綴已存在數組中則跳過,否則存入數組
if (isset(self::$prefixes[$prefix]) === false) {
self::$prefixes[$prefix] = [];
}
if ($prepend) {
//命名空間前綴相同時,后增基目錄(array_unshift() 函數用于向數組插入新元素。新數組的值將被插入到數組的開頭。)
array_unshift(self::$prefixes[$prefix], $base_dir);
} else {
//前增,向數組尾部增加值
array_push(self::$prefixes[$prefix], $base_dir);
}
}
~~~
詳細解釋:這里的內容比較抽象,請不要著急,慢慢理解。
在前面我說過很多次的類映射,將命名空間的路徑映射為項目中控制器類的真實路徑。下面舉個栗子~,我在配置文件中添加一個配置項
`` 'namespace_map_list' => [
'S' => S_PATH . 'S/core',
'Home' => S_PATH . 'Application/Home/Controller',
],``
鍵名是``namespace_map_list``,而對應的值是一個數組,然后看這個數組,這個數組中的每一個鍵值對中的鍵,就是命名空間的路徑,而對應的值,就是真實的項目路徑,例如:項目中存在一個``Home``模塊,這個模塊下的所有控制器類的命名空間都是``Home``,所對應的真實路徑是``S_PATH . 'Application/Home/Controller'``,當路由解析URL得到的模塊名是Home,控制器名是IndexController時,就會查找映射配置中是否存在這個模塊,如果有的話,就去對應的項目路徑中去尋找真正的控制器類并動態加載進來。這就是類映射存在的意義。
所以,``getMapList()``方法先獲得了配置文件中的映射配置項,然后遍歷這個配置項(每個鍵值對都是一個映射),對每一個鍵值對都調用``addNamespace()``方法,把得到的名稱存到一個靜態數組中,這樣以后實例化控制器時,就直接在這個靜態數組中進行尋找真實路徑。
* * * * *
接下來是自動加載類:
~~~
private static function s_autoload($className){
// 當前命名空間前綴
$prefix = $className;
//從后面開始遍歷完全合格類名中的命名空間名稱,來查找映射的文件名
//strpos獲取參數2在參數1中最后出現的位置,substr截取字符串
while (false !== $pos = strrpos($prefix, '\\')) {
// 命名空間前綴
$prefix = substr($className, 0, $pos + 1);
// 相對的類名
$relative_class = substr($className, $pos + 1);
//嘗試加載與映射文件相對的類
$mapped_file = self::loadMappedFile($prefix, $relative_class);
if ($mapped_file) {
return $mapped_file;
}
//去除前綴的反斜杠
$prefix = rtrim($prefix, '\\');
}
return false;
}
private static function loadMappedFile($prefix, $relative_class)
{
//這個命名空間前綴是否存在基本的目錄?
if (isset(self::$prefixes[$prefix]) === false) {
return false;
}
$relative_class = str_replace('\\', '/', $relative_class);
foreach (self::$prefixes[$prefix] as $base_dir) {
$file = $base_dir . $relative_class . '.php';
// 如果映射文件存在就加載它
if (self::requireFile($file)) {
return true;
}
}
return false;
}
//文件加載
private static function requireFile($file)
{
if (file_exists($file)) {
include $file;
return true;
}
return false;
}
}
~~~
每一行的注釋我都寫的很清楚啦,慢慢理解,這里很抽象~~~