【问题标题】:Slim Framework: routes and controllersSlim 框架:路由和控制器
【发布时间】:2015-04-30 20:26:54
【问题描述】:

最初,我的 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 效率低。

那么...有没有一种解决方案可以兼顾效率和秩序,还是效率是以路线/关闭噩梦为代价的?

【问题讨论】:

  • 似乎要走的路是使用 DI 和/或中间件来添加额外的路由层。您将为每个路由分配一些通用闭包/中间件,然后让它处理要实例化的控制器和调用的方法。好像我以前做过一次,但我找不到这个项目。如果我能让 POC 相对快速地工作,我会将其发布为答案。
  • 嗯,你知道这听起来很像我正在装配的东西。你传递那个包裹有什么原因吗? (还没看代码)
  • 是的,这正是我的想法,但由于我只是尝试 POC,所以功能更多......
  • 我带你指出,但我不认为单冒号是 Symfony2 和 Silex 使用的问题,为了做到这一点,你必须有某种语法或约定(语法更好IMO) ;-) 事实上,使用 paamayim nekudotayim 可能会更令人困惑,因为人们可能会认为它意味着方法的静态调用。至于缺乏对组和验证的支持,我可以看到这是有问题的,但我一般不认为数组是我使用 Slim(和 Silex)的时间,我做的第一件事是使用 YAML 使我的路由配置文件基于或 JSON。
  • 我个人会 fork 那个包,然后尝试添加对缺失功能的支持。

标签: php slim


【解决方案1】:

我想我可以与你们分享我所做的事情。我注意到 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 将实例化类,然后调用它的方法。
  • 如果不可调用,则抛出异常(足够合理)
  • 否则,无论它是什么(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-&gt;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-&gt;app 会引发错误。

我使用blackfire.io 进行了测试,并且...性能提升可以忽略不计。

优点

  • 这让我免去了在每个静态方法调用上调用 $app = \Slim\Slim::getInstance() 的痛苦,总共大约 100 行文本。
  • 它为进一步优化开辟了道路,让每个控制器都继承自一个抽象控制器类,而抽象控制器类又将应用程序方法包装成便利方法。
  • 它让我更好地理解了 Slim 的请求和响应生命周期。

缺点:

  • 性能提升可以忽略不计
  • 您必须将所有路由转换为使用单个冒号而不是 paamayin,并将所有控制器方法从静态转换为动态。
  • 在推出 v 3.0.0 时,从 Slim 基类的继承可能会中断

结语:(4 年后)

在 Slim v3 中,他们删除了静态访问器。反过来,如果您使用相同的约定FQCN\ClassName:method,控制器将使用应用程序的容器进行实例化。此外,该方法从路由接收请求、响应和$args。这样的DI,很多IoC。我很喜欢。

回顾我对 Slim 2 的做法,它打破了替换掉线的最基本原则(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) {
   ...
   }
}

所以它不会违反合同并且可以透明地使用。

【讨论】:

    猜你喜欢
    • 2014-01-17
    • 1970-01-01
    • 1970-01-01
    • 2017-10-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-03-08
    • 2016-06-28
    相关资源
    最近更新 更多