据说 TP5 已经 RC4 了,曾经在 RC3 的时候用它写过一个小东西。官方说从 RC4 之后改动不是太大。索性读一下它的源码。而后顺便记录一下,若有错漏,请路过大神多多指正!php
做为单入口框架,就从入口文件看起,按照tp5文档所示的规范,入口文件应该是放在public/ 下。html
那么为何大多数要把入口放到子文件夹下呢?这是一个小技巧。java
第一为了动静分离,由于如今的php框架通常都是单入口,既然是单入口,那么必然要作rewrite,若是把静态文件和程序文件放到一块儿。框架路由势必要对每个请求进行筛选。因此这些框架不约而同的把资源文件和程序文件区分开来,放在了不一样的文件夹下。从总体来看,也就是为何入口会在子目录下了。node
第二是为了安全,linux下的权限划分很是严格,分别分为读、写、执行,在这个基础上又分为文件全部组、所在组、其余组。这样划分能够更好的对文件权限进行梳理,避免上传漏洞(用户上传php文件被执行)等等。linux
打开public/index.phpgit
define('APP_PATH', __DIR__ . '/../application/'); // 加载框架引导文件 require __DIR__ . '/../thinkphp/start.php';
只有两行代码,定义 APP_PATH
,加载 '/../thinkphp/start.php'
。APP_PATH 能够本身修改。thinkphp
而后打开 /../thinkphp/start.php
数据库
namespace think; // ThinkPHP 引导文件 // 加载基础文件 require __DIR__ . '/base.php'; // 执行应用 App::run()->send();
也只有三行代码,定义命名空间,加载基础文件,启动应用。这里注意一下命名空间,全部thinkphp类都在think及其子命名空间下。程序中用到框架类的时候要先use 该类的命名空间;npm
而后咱们打开base.php
12-31行定义了一坨常量。注意里面 defined('THINK_PATH') or define('THINK_PATH', __DIR__ . DS);
这种定义方式,先判断时候存在,若是不存在则定义。也就是说咱们能够在这行代码以前(通常在index.php中)执行定义这个常量,而不会被覆盖。php框架
36-37行
// 载入Loader类 require CORE_PATH . 'Loader.php';
载入Loader类,这个类比较重要,实现了自动加载。
39-51行
// 加载环境变量配置文件 if (is_file(ROOT_PATH . 'env' . EXT)) { $env = include ROOT_PATH . 'env' . EXT; foreach ($env as $key => $val) { $name = ENV_PREFIX . strtoupper($key); if (is_bool($val)) { $val = $val ? 1 : 0; } elseif (!is_scalar($val)) { continue; } putenv("$name=$val"); } }
加载环境变量配置文件,可能不少同窗不理解是干什么用的。
咱们假设一个场景,你在公司和家里开发程序,在内网服务器上进行测试,在外网服务器上部署,全部的配置不能可能所有相同(好比数据库账号密码、文件路径等等)。总不能每次都改配置吧?若是作负载、有几十个服务器怎么部署?总不能都用ftp上传,而后改配置吧?
因此如今主流的作法就是区分环境(开发环境、测试环境、生产环境),而后程序自动加载不一样的配置。可是经过什么区分呢?方法有不少,可是大多数都是选择经过环境变量来区分,而后加载对应的配置文件。而后使用 git 作版本控制,而后在服务器部署同步脚本,经过 git push钩子进行代码同步,以达到自动化部署的模式。固然也还有其余方式,可是大多都相似。
为何要使用自动加载呢?由于像java、C等编译型语言在编译过程当中会把程序中引用的库、包等等自动引入进来。可是php是脚本行语言啊,没有编译过程,怎么办呢?最先期的程序都是手动引入,好比早期的xxshop、xxcms,都是写一坨require、include。又搓又不方便,对于世界上最好的语言来讲这样多丢面啊,因此咱们须要用自动加载让咱们最好的语言看起来更有B格(至于某些性能论的同窗会说自动加载影响性能啊之类的,请用汇编!)。
咱们继续看base.php 的 54行\think\Loader::register();
注册自动加载,从这一行以后就可使用符合自动加载规范的任何类了。
好比56-60行,虽然没有加载对应的文件,可是经过自动加载就能够直接使用。
// 注册错误和异常处理机制 \think\Error::register(); // 加载惯例配置文件 \think\Config::set(include THINK_PATH . 'convention' . EXT);
接下来咱们看一下自动加载的实现方法。打开Loader.php
,按照上面的执行顺序,先看Loader类的register方法
核心是
spl_autoload_register($autoload ?: 'think\\Loader::autoload', true, true); // 注册命名空间定义 self::addNamespace([ 'think' => LIB_PATH . 'think' . DS, 'behavior' => LIB_PATH . 'behavior' . DS, 'traits' => LIB_PATH . 'traits' . DS, ]); // 加载类库映射文件 if (is_file(RUNTIME_PATH . 'classmap' . EXT)) { self::addClassMap(__include_file(RUNTIME_PATH . 'classmap' . EXT)); }
spl_autoload_register方法可能不少同窗都有了解,在咱们实例化一个当前已加载文件中不存在的类后(好比在a.php中new一个类,会先在a.php和已加载的文件中找),会执行此方法指定的函数,并把类名传递进去。在这个函数中若是能正确加载到该文件,那么也能够实例化成功,并不会报错。因此借助此函数能够达到自动加载。
首先咱们知道当 new 一个不存在的类时,若是使用spl_autoload_register
定义了一个处理函数,那么这个函数能够得到一个参数,参数名是new 的类名。好比从前面base.php中咱们看到 \think\Error::register();
使用think命名空间下的Error类的register静态方法,可是咱们并无引入这个文件。因而咱们能够在spl_autoload_register
注册的函数中获得一个参数thinkError,若是咱们的命名空间按照文件夹格式的方法命名(这也是推荐的、经常使用的命名方式),那么就能够经过该参数来加载对应的文件,可是若是特殊状况下没有按照文件夹的格式来进行命名空间的命名,那么就须要手动指定映射关系。self::addClassMap(__include_file(RUNTIME_PATH . 'classmap' . EXT));
加载了映射文件。而后咱们看spl_autoload_register
中指定的函数:autoload。
这个不用详细解释了,先处理由 addNamespace 设定的命名空间别名,而后经过 findFile 来处理映射关系,获得真实的路径,并加载文件。
而__autoload()
函数具备相似的功能。可是为何用的不多呢?由于 __autoload()
只能指定一个函数,而spl_autoload_register
能够注册多个函数来处理这个逻辑。一旦业务复杂 __autoload()
就彻底不能胜任。好比咱们继续看Loader类的register方法下面的内容
// Composer自动加载支持 if (is_dir(VENDOR_PATH . 'composer')) { self::registerComposerLoader(); } // 自动加载extend目录 self::$fallbackDirsPsr4[] = rtrim(EXTEND_PATH, DS);
一个框架是否好用,很大程度取决于它的扩展能力。因此自动加载除了要处理自身类库的加载、还要处理扩展类库的自动加载。tp5支持使用两种方式来扩展类库一种是Composer,一种是手动放入 extend 目录。
Composer不用多说,就像npm之于nodejs、yum之于Centos、apt-get之于、Ubuntu。一个php的包管理工具。Composer有一套本身的自动加载机制,tp5这里只不过是调用了Composer本身的注册自动加载函数的方法。有兴趣的同窗能够看一下registerComposerLoader
方法,以及vendor/composer
下几个autoload开头的文件。原理基本上和上面的一致。
Loader.php中核心方法已经介绍完毕,其余的几个方法有处理规范的方法(PSR-0,PSR-2,PSR-4,有兴趣的同窗能够谷歌、百度了解一下)、和根据类名处理模型和控制器的方法,这点须要看完路由才能详解。