最近在重构一个老项目,用ThinkPHP5.1写的。这东西就像我大学室友的袜子,看着还行,一翻开全是惊喜。今天就来聊聊在TP里引用PHP文件的各种姿势和坑。
先说说最基础的include和require。这俩货在TP里能用吗?当然能,但就像用筷子吃牛排,不是不行,就是有点别扭。比如你在控制器里直接写include './somefile.php',TP会一脸懵逼地看着你:"大哥,咱这不是纯PHP项目啊"。
更优雅的方式是用TP的Loader类。比如要引入一个不在命名空间下的工具类:
$loader = \think\Loader::getInstance();
$loader->addClassMap(['SomeTool' => '../extend/SomeTool.php']);
但是!这里有个巨坑:路径问题。TP的加载机制是基于应用目录的,你写相对路径时要注意当前工作目录。我上次就踩了这个坑,debug了俩小时才发现是因为在命令行模式下路径基准变了。
说到命令行,在TP的命令里引用文件更刺激。比如写个自定义命令要引入外部配置:
$config = include realpath(__DIR__ . '/../../config/special.php');
看到没?必须用realpath,不然可能在crontab里运行时炸得你怀疑人生。__DIR__是个好东西,比相对路径靠谱多了。
再来看看自动加载的骚操作。TP的自动加载是基于命名空间的,但有时候我们就是要引入那些老掉牙的无命名空间文件。这时候可以这么玩:
spl_autoload_register(function($class){
if(strpos($class, 'Legacy_') === 0){
require_once './legacy/'.substr($class, 7).'.php';
}
});
这个注册在公共入口文件里,以后用Legacy_开头的类就会自动加载legacy目录下的对应文件。注意那个===0,写成==就等着被注入。

说到安全,不得不提include的另一个大坑。有次我见到这样的代码:
$page = $_GET['page'];
include '/templates/'.$page.'.php';
这简直是开着敞篷车过地雷阵。正确的姿势应该是:
$allow = ['index','about','contact'];
$page = in_array($_GET['page'], $allow) ? $_GET['page'] : 'index';
再说说require_once的性能问题。你以为它只加载一次很高效?Too young!TP框架自己已经用了大量require_once,你再加就会导致opcache不能充分优化。建议用require,然后自己控制加载逻辑。
在TP里跨模块引用文件也是个技术活。比如要在admin模块里用common模块的一个类:
$commonModel = \think\facade\App::getNamespacePath('common').'model/User.php';
注意这个路径不是直接能用的,还得配合类名和命名空间。更推荐的做法是用依赖注入,但那是另一个话题了。
有时候我们需要动态加载文件,比如插件系统。这时候可以用这个骚操作:
$plugin = 'demo';
$file = \think\facade\Env::get('app_path').'plugin/'.$plugin.'/bootstrap.php';
if(file_exists($file)){
$closure = function() use ($file){
return require $file;
};
$closure();
}

为什么要包个闭包?因为这样可以控制作用域,避免变量污染。你看这个use,它就像是给文件加载加了层安全套。
说到变量污染,再分享个血泪教训。有次我在引入的文件里直接用了全局变量:
// config.php
$db_config = [...];
然后在控制器里:
include '../config.php';
结果$db_config变成了超全局变量,在模板里都能访问到。正确的做法是:
return [...];
// 控制器
$config = include '../config.php';
这样变量就不会泄漏了。
最后说个终极骚操作——用stream wrapper实现伪协议加载:
stream_wrapper_register('myphp', 'MyPHPStream');
$code = file_get_contents('myphp://somecode');
class MyPHPStream {

public function stream_open($path, $mode, $options, &$opened_path){
// 自定义加载逻辑
}
}
这个适合做代码加密或者远程加载,但别问我怎么解密,问就是base64(手动狗头)。
总结一下在TP里引用PHP文件的几个要点:
1. 尽量用TP自带的加载机制
2. 路径问题要特别注意工作目录
3. 动态加载要注意作用域隔离
4. 用户输入的包含路径一定要过滤
5. 考虑性能影响,避免过度使用require_once
写完这些,我突然想起上次在引入文件时遇到的编码问题。文件是UTF-8带BOM的,导致json输出报错。所以建议所有PHP文件都保存为UTF-8无BOM格式,这是血的教训。
对了,如果你看到这样的错误:
Warning: include(): Failed opening '...' for inclusion
别急着检查路径,先看看文件权限。我就曾经对着404的报错debug了半天,最后发现是nginx没权限读文件。现在的我,已经是个看到报错先ls -l的成熟程序员了(泪目)。
796

被折叠的 条评论
为什么被折叠?



