Slim 框架:路由和控制器
Slim Framework: routes and controllers
最初,我的 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
方法,这不太好。在每个控制器方法中使用 $app 上下文需要我使用 \Slim\Slim::getInstance(),这似乎比像闭包那样使用 $app 效率更低。
那么...是否有兼顾效率和秩序的解决方案,或者效率是以 route/closure 噩梦为代价的?
我想我可以和你们分享我所做的事情。我注意到 Slim\Slim 中的每个路由方法在某个时候都调用了方法 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;
}
反过来,Slim\Route 构造函数调用了 setCallable
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 将实例化 class 然后调用它的方法。
- 如果不可调用,则抛出异常(足够合理)
- 否则,无论它是什么(ClassName::staticMethod、闭包、函数名)都将按原样使用。
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可以将app注入到每一个需要实例化的controller中
\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;
}
就是这样。使用这两个 classes,我可以将 $app
注入到我在路由上声明的任何控制器中,只要我使用单个冒号将控制器与方法分开即可。使用 paamayim nekudotayim 会将方法作为静态方法调用,因此如果我尝试访问其中的 $this->app
将抛出错误。
我 运行 使用 blackfire.io 进行测试并且...性能提升可以忽略不计。
优点:
- 这让我免去了在每个静态方法调用中调用 $app = \Slim\Slim::getInstance() 的痛苦,总共占了大约 100 行文本。
- 它通过使每个控制器都继承自抽象控制器class,从而为进一步优化开辟了道路,后者又将应用程序方法包装为便捷方法。
- 它让我更好地理解了 Slim 的请求和响应生命周期。
缺点:
- 性能提升可以忽略不计
- 您必须将所有路由转换为使用单个冒号而不是 paamayin,并将所有控制器方法从静态转换为动态。
- 来自 Slim 基础的继承 classes 在推出 v 3.0.0 时可能会中断
结语:(4 年后)
在 Slim v3 中,他们删除了静态访问器。反过来,如果您使用相同的约定 FQCN\ClassName:method
,控制器将使用应用程序的容器实例化。此外,该方法从路由接收请求、响应和 $args
。这样的DI,很多IoC。很喜欢
回顾我对 Slim 2 的做法,它打破了最基本的 drop in replacement (Liskov Substitution) 原则。
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) {
...
}
}
所以它不会破坏合同并且可以运行顺利地使用。
最初,我的 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
方法,这不太好。在每个控制器方法中使用 $app 上下文需要我使用 \Slim\Slim::getInstance(),这似乎比像闭包那样使用 $app 效率更低。
那么...是否有兼顾效率和秩序的解决方案,或者效率是以 route/closure 噩梦为代价的?
我想我可以和你们分享我所做的事情。我注意到 Slim\Slim 中的每个路由方法在某个时候都调用了方法 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;
}
反过来,Slim\Route 构造函数调用了 setCallable
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 将实例化 class 然后调用它的方法。 - 如果不可调用,则抛出异常(足够合理)
- 否则,无论它是什么(ClassName::staticMethod、闭包、函数名)都将按原样使用。
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可以将app注入到每一个需要实例化的controller中
\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;
}
就是这样。使用这两个 classes,我可以将 $app
注入到我在路由上声明的任何控制器中,只要我使用单个冒号将控制器与方法分开即可。使用 paamayim nekudotayim 会将方法作为静态方法调用,因此如果我尝试访问其中的 $this->app
将抛出错误。
我 运行 使用 blackfire.io 进行测试并且...性能提升可以忽略不计。
优点:
- 这让我免去了在每个静态方法调用中调用 $app = \Slim\Slim::getInstance() 的痛苦,总共占了大约 100 行文本。
- 它通过使每个控制器都继承自抽象控制器class,从而为进一步优化开辟了道路,后者又将应用程序方法包装为便捷方法。
- 它让我更好地理解了 Slim 的请求和响应生命周期。
缺点:
- 性能提升可以忽略不计
- 您必须将所有路由转换为使用单个冒号而不是 paamayin,并将所有控制器方法从静态转换为动态。
- 来自 Slim 基础的继承 classes 在推出 v 3.0.0 时可能会中断
结语:(4 年后)
在 Slim v3 中,他们删除了静态访问器。反过来,如果您使用相同的约定 FQCN\ClassName:method
,控制器将使用应用程序的容器实例化。此外,该方法从路由接收请求、响应和 $args
。这样的DI,很多IoC。很喜欢
回顾我对 Slim 2 的做法,它打破了最基本的 drop in replacement (Liskov Substitution) 原则。
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) {
...
}
}
所以它不会破坏合同并且可以运行顺利地使用。