最初,我的Slim Framework应用具有经典结构
(index.php)
<?php
$app = new \Slim\Slim();
$app->get('/hello/:name', function ($name) {
echo "Hello, $name";
});
$app->run();
但是随着我添加更多路线和路线组,我转向了基于控制器的方法:
index.php
<?php
$app = new \Slim\Slim();
$app->get('/hello/:name', 'HelloController::hello');
$app->run();
HelloController.php
<?php
class HelloController {
public static function hello($name) {
echo "Hello, $name";
}
}
这有效,对组织应用程序结构很有帮助,同时让我为每种控制方法建立单元测试。
但是,我不确定这是正确的方法。我觉得我是在特殊基础上嘲笑Silex的mount
方法,那不是很好。在每个Controller方法中使用$ app上下文要求我使用\ Slim \ Slim :: getInstance(),这似乎比仅使用$ app像闭包一样效率低。
所以...有没有兼顾效率和订单的解决方案,或者效率是以路线/封锁的噩梦为代价的?
我想我可以和你们分享我的所作所为。我注意到Slim \ Slim中的每个route方法在某个时候都称为方法mapRoute
((为清楚起见,我更改了官方源代码的缩进)
Slim.php
protected function mapRoute($args)
{
$pattern = array_shift($args);
$callable = array_pop($args);
$route = new \Slim\Route(
$pattern,
$callable,
$this->settings['routes.case_sensitive']
);
$this->router->map($route);
if (count($args) > 0) {
$route->setMiddleware($args);
}
return $route;
}
反过来,称为setCallable的Slim \ Route构造函数>
Route.php
public function setCallable($callable) { $matches = []; $app = $this->app; if ( is_string($callable) && preg_match( '!^([^\:]+)\:([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)$!', $callable, $matches ) ) { $class = $matches[1]; $method = $matches[2]; $callable = function () use ($class, $method) { static $obj = null; if ($obj === null) { $obj = new $class; } return call_user_func_array([$obj, $method], func_get_args()); }; } if (!is_callable($callable)) { throw new \InvalidArgumentException('Route callable must be callable'); } $this->callable = $callable; }
基本上是哪个
$callable
是字符串,并且(注意单个冒号)的格式为ClassName
: method
,则它是非静态的,因此Slim将实例化该类,然后调用该方法在它上面。[ClassName
应该是FQCN,所以更像\MyProject\Controllers\ClassName
。
实例化控制器(或其他对象)的点是注入App实例的好机会。因此,对于初学者来说,我overrode mapRoute
将应用实例注入到其中:\Util\MySlim
protected function mapRoute($args) { $pattern = array_shift($args); $callable = array_pop($args); $route = new \Util\MyRoute( $this, // <-- now my routes have a reference to the App $pattern, $callable, $this->settings['routes.case_sensitive'] ); $this->router->map($route); if (count($args) > 0) { $route->setMiddleware($args); } return $route; }
因此,基本上
\Util\MyRoute
是\Slim\Route
,在其构造函数中带有一个额外的参数,我将其存储为$this->app
此时,getCallable
可以将应用程序注入到每个需要实例化的控制器中\Util\MyRoute.php
将把该方法称为静态方法,因此,如果我尝试访问其中的public function setCallable($callable) { $matches = []; $app = $this->app; if ( is_string($callable) && preg_match( '!^([^\:]+)\:([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)$!', $callable, $matches ) ) { $class = $matches[1]; $method = $matches[2]; $callable = function () use ($app, $class, $method) { static $obj = null; if ($obj === null) { $obj = new $class($app); // <--- now they have the App too!! } return call_user_func_array([$obj, $method], func_get_args()); }; } if (!is_callable($callable)) { throw new \InvalidArgumentException('Route callable must be callable'); } $this->callable = $callable; }
就这样。使用这两个类,只要我使用单个冒号将控制器与方法分开,就可以将
$app
注入到在路由中声明的任何控制器中。使用paamayim nekudotayim
$this->app
,将引发错误。我使用blackfire.io进行了测试,...性能提升可忽略不计。
Pros
:缺点:
结束语 :( 4年后)
在Slim v3中,他们删除了静态访问器。反过来,如果您使用相同的约定FQCN\ClassName:method
,则将使用应用程序的容器实例化控制器。而且,该方法从路由接收请求,响应和$args
。这样的DI,很大的IoC。我非常喜欢。
回顾我对Slim 2的使用方法,它打破了替换掉的最基本原理(Liskov替代)。
class Route extends \Slim\Route { protected $app; public function __construct($app, $pattern, $callable, $caseSensitive = true) { ... } }
应该是
class Route extends \Slim\Route { protected $app; public function __construct($pattern, $callable, $caseSensitive = true, $app = null) { ... } }
因此它不会违反合同,可以透明地使用。