🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
[TOC] * * * * * ## 1 文件加载的意义 大型项目工程中,通常将项目的功能组织**以文件进行组织**,并使用目录进行划分。 为了**使用不同文件中定义的函数,类等**,需要加载目标文件。 因此文件加载是**项目工程分文件组织**后,**加载功能目标文件**的方法。 ## 2 文件的手动加载 >[info] 1 四个手动加载语言结构 * * * * * **inculde**与**require**是php用来手动加载文件的函数, **include_once**与**require_once**是限定同一个文件只被加载一次 1 首先按照参数给出的路径查找 2 没有给出目录,从include_path指定目录查找 3 include_path指定目录查找失败,在调用脚本文件所在目录和当前工作目录下查找 4 最后加载失败时,include发出一条警告,而require会发出致命错误 这四个是**语言构造器或者说是语言结构**,通常后面**接一个字符串**。 与普通函数不同,普通函数后面接带参数的大括号。 >[info] 2 示例 * * * * * exampe1:基本的include例子。 ~~~ vars.php <?php $color = 'green' ; $fruit = 'apple' ; ?> test.php <?php echo "A $color $fruit " ; // A include 'vars.php' ; echo "A $color $fruit " ; // A green apple ?> ~~~ >[danger] include等加载调用出现在一个函数里,则加载文件包含的代码中的普通变量的作用域范围就是该函数,其中的魔术变量是全局范围。 example2:函数中include文件 ~~~ <?php function foo () { global $color ; include 'vars.php' ; echo "A $color $fruit " ; } /*vars.php中的$furit的作用域在foo()函数中,因此 因此foo()函数正常输出$fruit echo语句中的$fruit为空 $color 声明为全局变量 ,因此$color的作用域在全局。 foo() echo都输出$color。 */ foo (); // A green apple echo "A $color $fruit " ; // A green ?> ~~~ >[info] 3 加载文件模式切换 加载文件时,php语法解析器会**从php模式切换到html模式**,**加载完文件后恢复到php模式**。因此也可以用来加载html文件,**其中的php代码必须包含在php的起始与结束标记中** >[info] 4 url加载远程文件 php.ini的配置中URL fopen_wrappers激活时,可以使用url加载网络服务器文件进行解释。**与加载本地文件不同的是模板文件如果作为php代码解释,已经在服务器运行了,本地脚本只是包含其输出结果**。通常使用其他的文件打开函数如readfile()等更好 example #3 通过 HTTP 进行的 include ~~~ <?php include 'http://www.example.com/file.txt?foo=1&bar=2' ; //txt文件不会被运行 include 'file.php?foo=1&bar=2' ; //查找本地file.php?foo=1&bar=2文件,而不是运行file.php include 'http://www.example.com/file.php?foo=1&bar=2' ; // 查找远程服务器的file.php,并以foo=1&bar=2参数运行。 $foo = 1 ; $bar = 2 ; include 'file.txt' ; //加载本地file.txt文件 include 'file.php' ; //加载本地file.php文件 ?> ~~~ >[info] 5 加载返回值检测 include等加载失败时返回false,并发出警告,成功则返回1,除非包含文件使用return等给出了返回值。 需要注意的是include 不是函数,其参数不需要括号 example #4 比较 include 的返回值 ~~~ <?php //错误的检查返回值格式, if (include( 'vars.php' ) == 'OK' ) { echo 'OK' ; } //正确的检查其返回值格式 if ((include 'vars.php' ) == 'OK' ) { echo 'OK' ; } ?> ~~~ 可以在目标文件中**使用return语句可以终止文件的加载,并返回调用的脚本**。thinkphp的默认**数组配置就是使用这个功能返回一个数组**。 example #5 include 和 return 语句 ~~~ return.php <?php $var = 'PHP' ; return $var ; ?> noreturn.php <?php $var = 'PHP' ; ?> testreturns.php <?php $foo = include 'return.php' ; echo $foo ; // prints 'PHP' $bar = include 'noreturn.php' ; echo $bar ; // prints 1 ?> ~~~ >[info] 6 加载包含函数的文件 包含文件中定义了函数,不管是return之前还是之后定义的,都可以独立在主文件中使用。 **php5会对重复加载文件时出现的函数重复定义发出致命错误报告**。php4不会对return之后定义的函数报错。**推荐使用include_once加载此类文件** >[info] 7 tp5.0中手动加载的使用 ~~~ index.php: require __DIR__ . '/../thinkphp/start.php'; ~~~ ~~~ start.php: require __DIR__ . '/base.php'; require CORE_PATH . 'Loader.php'; ~~~ ~~~ App.php public static function run(){ ... if (!empty($config['extra_file_list'])) { foreach ($config['extra_file_list'] as $file) { $file = strpos($file, '.') ? $file : APP_PATH . $file . EXT; if (is_file($file)) { include_once $file; } } } ... } private static function initModule($module, $config){ $module = (COMMON_MODULE == $module || !APP_MULTI_MODULE) ? '' : $module . DS; if (is_file(APP_PATH . $module . 'init' . EXT)) { include APP_PATH . $module . 'init' . EXT; } else { $path = APP_PATH . $module; $config = Config::load(APP_PATH . $module . 'config' . EXT); if ($config['app_status']) { $config = Config::load(APP_PATH . $module . $config['app_status'] . EXT); } if ($config['extra_config_list']) { foreach ($config['extra_config_list'] as $name => $file) { $file = strpos($file, '.') ? $file : $path . $file . EXT; Config::load($file, is_string($name) ? $name : pathinfo($file, PATHINFO_FILENAME)); } } if (is_file($path . 'alias' . EXT)) { Loader::addMap(include $path . 'alias' . EXT); } if (APP_HOOK && is_file($path . 'tags' . EXT)) { Hook::import(include $path . 'tags' . EXT); } if (is_file($path . 'common' . EXT)) { include $path . 'common' . EXT; } if ($config['lang_switch_on'] && $module) { Lang::load($path . 'lang' . DS . LANG_SET . EXT); } } } ~~~ ## 3 文件的自动加载 在php引入面向对象编程思想后, 开发者习惯将每个类的定义建立一个php文件。 使用这些类需要使用require加载需要的类文件。 为了简化类的加载,php中引入了类的自动加载 >[info] 1 __autoload 在php5中可以通过定义一个__auload()函数, 使用未被定义的类时自动调用。 通过调用此函数,脚本引擎可以加载目标类文件 ~~~ <?php function __autoload ( $class_name ) { require_once $class_name . '.php' ; } $obj = new MyClass1 (); $obj2 = new MyClass2 (); //在上面创建对象时,会调用__autoload,使用require加载对应的Myclass1.php类文件和MyClass2.php类文件。 ?> ~~~ 加载的异常处理 ~~~ <?php function __autoload ( $name ) { echo "Want to load $name .\n" ; throw new Exception ( "Unable to load $name ." ); } try { $obj = new NonLoadableClass (); } catch ( Exception $e ) { echo $e -> getMessage (), "\n" ; } ?> ~~~ 输出: Want to load NonLoadableClass. Unable to load NonLoadableClass. 提示类加载失败 5.3+版本后,__autoload 函数抛出的异常,可以被 catch 语句块捕获。 抛出的是一个自定义异常,那么必须存在相应的自定义异常类 __autoload 函数可以递归的自动加载自定义异常类。 ~~~ <?php function __autoload ( $name ) { echo "Want to load $name .\n" ; throw new MissingException ( "Unable to load $name ." ); } try { $obj = new NonLoadableClass (); } catch ( Exception $e ) { echo $e -> getMessage (), "\n" ; } ?> ~~~ 输出: Want to load NonLoadableClass. Want to load MissingException.Fatal error: Class 'MissingException' not found in testMissingException.php on line 4 异常类查找失败 >[info] 2 spl_autoload SPL提供了一个__autoload()函数默认实现。 如果不使用任何参数调用 autoload_register() 函数,则以后在进行 __autoload() 调用时会自动使用此函数 `void spl_autoload ( string $class_name [, string $file_extensions ] )` > $class_name 加载的类名 > $file_extensions 文件后缀,默认为.inc或.php * * * * * >[info] 3 spl_autoload_register 注册给定的函数作为 __autoload 的实现,并将函数注册到SPL __autoload函数队列中 该函数会将__autoload()函数取代为lspl_autoload()或者spl_autoload_call() 因此程序中实现的__autoload()函数,需要使用该函数注册到SPL __autoload队列中 需要多条autoload函数,使用该函数。 实际上创建了autoload函数队列,按照定义逐个执行 而__autoload()只可定义一个 `bool spl_autoload_register ([ callable $autoload_function [, bool $throw = true [, bool $prepend = false ]]] )` > $autoload_function 自动加载函数,省略则为spl_autoload > $throw 注册失败时,是否抛出异常 > $prepend 添加到加载函数队列队首,还是加载函数队尾 spl_autoload_register()示例 ~~~ <?php // function __autoload($class) { // include 'classes/' . $class . '.class.php'; // } function my_autoloader ( $class ) { include 'classes/' . $class . '.class.php' ; } spl_autoload_register ( 'my_autoloader' ); // 自 PHP 5.3.0 起可以使用一个匿名函数 spl_autoload_register (function ( $class ) { include 'classes/' . $class . '.class.php' ; }); ?> ~~~ spl_autoload_register()加载失败示例 ~~~ <?php namespace Foobar ; class Foo { static public function test ( $name ) { print '[[' . $name . ']]' ; } } spl_autoload_register ( __NAMESPACE__ . '\Foo::test' ); new InexistentClass ; ?> ~~~ 输出: [[Foobar\InexistentClass]] Fatal error: Class 'Foobar\InexistentClass' not found in ... >[info] 4 其他相关函数 spl_autoload_unregister() 注销已注册的__autoload()函数 spl_autoload_extensions() 修改spl_autoload()函数默认文件扩展名 spl_autoload_functions() 返回注册的__autoload()函数 spl_autoload_call() 手动调用已注册的__autoload()函数 * * * * * **在php的面向对象编程中,通常使用spl_autoload_register()来进行类的自动加载** * * * * * ## 4 composer的自动加载实现 使用composer安装tp5后 thinkphp\vendor\composer\目录中包含了composer的自动加载实现, 其目录结构如下: ![](https://box.kancloud.cn/2016-03-11_56e272c8d3d04.jpg) autoload_classmap.php ;类名映射文件 autoload_files.php ;文件名映射文件 autoload_namespace.php ;命名空间映射文件 autoload_psr4.php ;psr4规范修正文件 autoload_real.php ;composer加载器入口 ClassLoader.php ;composer加载器核心 >[info] 1 autoload_real.php ~~~ class ComposerAutoloaderInit900f8cefe6f24fb76e69c5c37058d680 { // 全局唯一加载器实例, private static $loader; // 加载ClassLoader.php文件中的Composer\Autoload'ClassLoader类 public static function loadClassLoader($class) { if ('Composer\Autoload\ClassLoader' === $class) { require __DIR__ . '/ClassLoader.php'; } } // 生成加载器 public static function getLoader() { // 单例模式生成全局唯一加载器 if (null !== self::$loader) { return self::$loader; } // 注册加载器 检测自动加载器的存在 spl_autoload_register(array('ComposerAutoloaderInit900f8cefe6f24fb76e69c5c37058d680', 'loadClassLoader'), true, true); // 生成加载器实例 self::$loader = $loader = new \Composer\Autoload\ClassLoader(); // 注销自动加载器 对应上面的检查过程,下面会再次注册 spl_autoload_unregister(array('ComposerAutoloaderInit900f8cefe6f24fb76e69c5c37058d680', 'loadClassLoader')); // 加载 命名空间映射文件。分析见下 $map = require __DIR__ . '/autoload_namespaces.php'; // 调用$loader->set() 设置命名空间别名路径 foreach ($map as $namespace => $path) { $loader->set($namespace, $path); } // 加载 psr4规范修正文件 $map = require __DIR__ . '/autoload_psr4.php'; // 调用$laoder->setPsr4()设置命名空间别名路径 foreach ($map as $namespace => $path) { $loader->setPsr4($namespace, $path); } // 加载 类映射文件 $classMap = require __DIR__ . '/autoload_classmap.php'; // 调用$loader->addClassMap() 设置类的别名 if ($classMap) { $loader->addClassMap($classMap); } // 注册加载器。 $loader->register(true); // 加载 需要包含的文件 $includeFiles = require __DIR__ . '/autoload_files.php'; // 调用下面的函数依次加载需要包含的文件 foreach ($includeFiles as $fileIdentifier => $file) { composerRequire900f8cefe6f24fb76e69c5c37058d680($fileIdentifier, $file); } return $loader; } } // 加载目标文件,并保存加载信息到全局变量 $GLOBALS['_composer_autolod_files'],防止重复加载 function composerRequire900f8cefe6f24fb76e69c5c37058d680($fileIdentifier, $file) { if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) { require $file; $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true; } } ~~~ >[info] 2 ClassLoader.php composer自动加载器核心 * * * * * >[info] 成员变量 ~~~ PSR-4规范 private $prefixLengthsPsr4 = array(); private $prefixDirsPsr4 = array(); private $fallbackDirsPsr4 = array(); PSR-0规范 private $prefixesPsr0 = array(); private $fallbackDirsPsr0 = array(); private $useIncludePath = false; private $classMap = array(); private $classMapAuthoritative = false; ~~~ * * * * * >[info] 成员方法 PSR-0 > getPrefixes() 获取前缀设置 `public function getPrefixes()` > getFallbackDirs() 获取搜索目录 `public function getFallbackDirs()` > add() 添加搜索目录 `public function add($prefix, $paths, $prepend = false)` > setPsr4() 修改搜索目录前缀索引 `public function set($prefix, $paths)` PSR-4 > getPrefixesPsr4() 获取前缀设置 ` public function getPrefixesPsr4()` > getFallbackDirsPsr4() 获取搜索目录 `public function getFallbackDirsPsr4()` > addPsr4() 添加搜索目录 `public function addPsr4($prefix, $paths, $prepend = false)` > setPsr4() 修改搜索目录前缀索引 `public function setPsr4($prefix, $paths)` 其他 > getClassMap() 获取类名映射信息 `public function getClassMap()` > addClassMap() 添加类名映射信息 `public function addClassMap(array $classMap)` > setUseIncludePath() 设置是否搜索include_path `public function setUseIncludePath($useIncludePath)` > getUseInlcudePath() 获取是否搜索include_path public function getUseIncludePath() > register() 注册加载函数 `public function register($prepend = false)` > unregister() 注销加载函数 public function unregister() > loadClsss() 加载目标类 `public function loadClass($class)` > findFile() 搜索类目标文件 `public function findFile($class)` > findFileWithExtension() 指定扩展名搜索文件 `private function findFileWithExtension($class, $ext)` >[info] 3 其他文件 autoload_classmap.php autoload_files.php autoload_namespaces.php autolaod_psr4.php 以**数组形式**返回需要操作的**映射信息**。 ## 5 tp5的自动加载实现 thinkphp5的自动加载实现在Loader.php文件 分析见 [附:(Loader.php)自动加载](http://www.kancloud.cn/zmwtp/tp5/119431)