#### 核心运行类
现在我们已经有了配置类,至于配置文件中有什么样的键值对,我们可以边做,需要时再去定义,这相当于我们的准备工作,现在我们可以开始继续顺着我们的执行流程继续做啦~
接下来我们要做的是核心运行类``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;
}
}
~~~
每一行的注释我都写的很清楚啦,慢慢理解,这里很抽象~~~