不是鸡蛋里挑骨头,webman本身就是出于对性能的追求,所以应要该精于求精,改进任何不太合理的地方。
修改后的代码
protected static function parseControllerAction(string $path)
{
// $path = str_replace('-', '', $path); //这个方法里注释掉这一行,为了路径更规范,比如some-other,如果不注释掉这行,就变成someother了
//....
}
protected static function guessControllerAction($pathExplode, $action, $suffix, $classPrefix)
{
array_unshift($pathExplode, 'app');
$map = [];
$map2 = [];
foreach ($pathExplode as $index => $section) {
$tmp = $pathExplode;
array_splice($tmp, $index + 1, 0, 'controller');
$map2[] = trim("$classPrefix\\" . implode('\\', $tmp), '\\') . '\\Index';
$controller = array_pop($tmp);
if ($controller === 'controller') {
continue;
}
$controller = static::kebab2camel($controller);
$map[] = trim("$classPrefix\\" . implode('\\', $tmp), '\\') . '\\' . $controller;
}
$map = array_merge($map, $map2);
foreach ($map as $controllerClass) {
$controllerClass .= $suffix;
if ($controllerAction = static::getControllerAction($controllerClass, $action)) {
return $controllerAction;
}
}
return false;
}
public static function kebab2camel($name)
{
$name = preg_replace_callback('/-([a-z]+)/', function($m) {
return ucfirst($m[1]);
}, $name);
return ucfirst($name);
}
修改后的getController(...):
protected static function getController(string $controllerClass)
{
if (class_exists($controllerClass)) {
return (new ReflectionClass($controllerClass))->name;
}
$basePath = '';
$file = '';
if (0 === strpos($controllerClass, 'plugin\\')) {
$basePath = BASE_PATH . '/plugin';
$file = $basePath . str_replace('\\', '/', substr($controllerClass, 6)) . '.php';
} else {
$basePath = static::$appPath;
$file = $basePath . str_replace('\\', '/', substr($controllerClass, 3)) . '.php';
}
if (is_file($file)) {
require_once $file;
if (class_exists($controllerClass, false)) {
return (new ReflectionClass($controllerClass))->name;
}
}
return false;
}
感谢你的建议,不过这里压测发现官方代码还是比你优化后的快1.3倍。
以下是测试程序 (测试时需要将guessControllerAction改成public)
官方代码每秒可运行169000左右。
你的代码每秒可运行76000左右。
以上是本地macbook跑的结果,压测可能不严谨,不过webman是常驻内存的框架,第一次加载控制器的时候速度慢0.00000x秒不是很重要,后面请求就直接走内存了,不会再去磁盘找文件了,就没有这层消耗了。
不好意思,没测,仅凭代码判断,回头我测一下。不过我还是认为我这段更合理、更简洁一些,对目录文件命名规范应该有所限定。
目录规范
假设有个/app/api/controller/UserController.php里面有个
getSomeData()
方法目前支持通过这样的url访问
1、/api/user/getSomeData
2、/api/user/getsomedata
3、/api/user/get-some-data
看起来你的代码只支持1这种方式访问,url里大小写混用,这样很多开发者就会觉得很别扭
我的代码支持这样/api/some-other/get-some-data,some-other严格与目录对应,官方的貌似只能用someother目录,我一向遵循的规范是url一定小写,目录名一定小写,特别讨厌url用驼峰命名方法,也不喜欢url用下划线。
@walkor,我跟你的测试差距很大
用我的方式将近快一倍。
我试了下,还是官方的快。
官方使用scandir(),php内部有缓存机制,不会每次去读磁盘。
你的慢getController里使用了在
is_file()
,因为文件不存在每次都要查磁盘。如果把is_file()
注释掉,就和官方的一样快。还是那个结论,第一次加载控制器这里没必要优化,快了0.00000x秒没有意义,后续这个控制器的请求不会有这个查找文件的消耗。
我以为是时间,弄错了,哈哈。关键问题是没有判断static::$appPath,官方代码实际没往下执行了,我的还在判断is_file,加上static::$appPath判断就快了,如果scandir有缓存机制,我的可以改成scandir,但我看了官方代码,scandir使用了两次,实际没有必要,出于严谨,我希望some-other这样的目录名不会被替换成someother,这样会导致我的控制器找不到,更重要的是不便于查找和排错。
@walkor,如果定义了static::$appPath,官方的慢不是一星半点,比我的慢30多倍
定义static::$appPath后,
我的3次测试时间分别是:3.3秒,3.0秒,3.3秒
官方的3次测试时间分别是:96.9秒,100.8秒,100.3秒(我的本本风扇都开始响起了)
官方的慢就在于代码有两处sandir,实际执行的还不止两次,因为第一次scandir是在循环中,如果explodes在3个以上,那实际执行scandir的次数就是4次以上,而且,如果目录文件很多的话,官方的会更慢,因为我本地测试还没那么多目录文件,如果正式项目,可能会有几百以上的目录文件。
确实,定义static::$appPath后官方的会慢非常多。
发个pr吧,我合并下,如果哪里有兼容问题我后面再修。
linux下有个问题
app\controller\UserLogin::testUser
通过url /userlogin/testuser 访问404,应该是大小写问题
url地址有规范限定,app\controller\UserLogin::testUser,应该对应/user-login/test-user
对应/user-login/testuser也可以,因为php方法名不区分大小写,但为了规范,最好是对应/user-login/test-user
url与controller对应就是烤串命名对驼峰命名
这样规定死不行。/userlogin/testuser能访问 app\controller\UserLogin::testUser 是很普遍的需求。
我大概知道为什么官方要scandir目录了,因为需要解决小写url能匹配到正确的目录和文件问题。
你看下能不能解决,不能的话可能要回滚到之前官方版本
应该可以解决,我改下
已经修改并发了pr,在原来基础上增加了scandir,但性能跟之前相比没太明显差别,完全可以接受。
官方文档最好告诉用户,url最好使用烤串命名法,对应目录名也用烤串命名,文件名用大驼峰命名,action用小驼峰命名
等等,发现问题
重新pr了,在原来基础上增加了scandir,不过对性能无明显影响。