使用注册表模式获取路由器参数

Getting router parameters using the Registry pattern

我的应用程序的核心是 Router.php。

Router.php

<?php

final class Router
{
    protected $routes = [];
    protected $params = [];

    public function add($route, $params = [])
    {
        $route = preg_replace('/\//', '\/', $route);
        $route = preg_replace('/\{([a-z]+)\}/', '(?P<>[a-z-]+)', $route);
        $route = preg_replace('/\{([a-z]+):([^\}]+)\}/', '(?P<>)', $route);
        $route = '/^' . $route . '$/i';

        $this->routes[$route] = $params;
    }

    public function getRoutes()
    {
        return $this->routes;
    }

    public function match($url)
    {
        foreach ($this->routes as $route => $params) {
            if (preg_match($route, $url, $matches)) {
                foreach ($matches as $key => $match) {
                    if (is_string($key)) {
                        $params[$key] = $match;
                    }
                }

                $this->params = $params;
                return true;
            }
        }

        return false;
    }

    public function getParams()
    {
        return $this->params;
    }

    public function dispatch($url)
    {
        $url = $this->removeQueryStringVariables($url);

        if ($this->match($url)) {
            $controller = $this->params['controller'];
            $controller = $this->convertToStudlyCaps($controller);
            $controller = $this->getNamespace() . $controller;

            if (class_exists($controller)) {
                $controller_object = new $controller($this->params);
                $action = $this->params['action'];
                $action = $this->convertToCamelCase($action);

                if (is_callable([$controller_object, $action])) {
                    $controller_object->$action();

                } else {
                    echo "Method $action (in controller $controller) not found";
                }
            } else {
                echo "Controller class $controller not found";
            }
        } else {
            echo 'No route matched.';
        }
    }

    protected function convertToStudlyCaps($string)
    {
        return str_replace(' ', '', ucwords(str_replace('-', ' ', $string)));
    }

    protected function convertToCamelCase($string)
    {
        return lcfirst($this->convertToStudlyCaps($string));
    }

    protected function removeQueryStringVariables($url)
    {
        if ($url != '') {
            $parts = explode('&', $url, 2);

            if (strpos($parts[0], '=') === false) {
                $url = $parts[0];
            } else {
                $url = '';
            }
        }

        return $url;
    }

    protected function getNamespace()
    {
        $namespace = 'catalog\controller\';

        if (array_key_exists('namespace', $this->params)) {
            $namespace .= $this->params['namespace'] . '\';
        }

        return $namespace;
    }
}

为了实现对象的中央存储,我实现了这个注册表模式,它是结构的核心。

Registry.php

<?php
final class Registry
{
    private $data = array();

    public function get($key)
    {
        return (isset($this->data[$key]) ? $this->data[$key] : null);
    }

    public function set($key, $value)
    {
        $this->data[$key] = $value;
    }

    public function has($key)
    {
        return isset($this->data[$key]);
    }
}

base/core 控制器在其构造函数中还有 $registry。

CoreController.php

<?php
abstract class CoreController
{
    protected $registry;

    public function __construct($registry)
    {
        $this->registry = $registry;
    }

    public function __get($key)
    {
        return $this->registry->get($key);
    }

    public function __set($key, $value)
    {
        $this->registry->set($key, $value);
    }
}

CoreController被所有app controller扩展继承属性

Posts.php

<?php
class Posts extends CoreController
{
    public function index() {
        echo 'Hello from the index action in the posts controller';
    }

    public function addNew() {
        echo 'Hello from the addNew action in the posts controller';
    }

    public function edit() {
        echo '<p>Route parameters: <pre>'.var_dump($this->registry).'</pre></p>';
    }
}

要实例化注册表和路由器,这就是

中的内容

index.php

<?php
// Instantiate registry
$registry = new \system\core\Registry();

// Database
$db = new DB(DB_HOSTNAME, DB_USERNAME, DB_PASSWORD, DB_DATABASE);
$registry->set('db', $db);


$router = new \system\core\Router();
$registry->set('router', $router);


// Add the routes
$router->add('', ['controller'=>'HomeController', 'action'=>'index']);
$router->add('posts', ['controller'=>'posts', 'action'=>'index']);
//$router->add('posts/new', ['controller'=>'posts', 'action'=>'new']);
$router->add('{controller}/{action}');
$router->add('{controller}/{id:\d+}/{action}');
$router->add('admin/{controller}/{action}');

$router->dispatch($_SERVER['QUERY_STRING']);

urlhttp://localhost/mvcsix/posts/1235/edit之后显示的是这个

所有这些看起来都不错,而且工作正常。

不知何故,这感觉不对。我 var_dumped $this->registry 并且显示了路由参数,但我觉得要从路由获取参数我应该 var_dumped $this->router->getParams()。当我 var_dump $this->router->getParams() 时,我得到一个错误

Fatal error: Call to a member function get() on array in

我这样说是因为我在注册表中也有数据库对象,为了显示查询,我这样做 $result = $this->db->query("SELECT * FROM members");

为什么我的参数显示在 $this->registry 而不是 $this->router->getParams(); ?

P.S。上面的代码是原始代码的剥离。有名称空间和其他一些不需要的东西post。

无法测试您在此处发布的代码,因为它缺少 HomeController class 定义,而且也不太清楚 var_dump(...) 在何处以及在何时被调用。但是我试图根据你提到的致命错误来猜测你的问题,并在你的 Posts class 中调用 edit() 函数中的 var_dump() 。看起来您试图从该函数中转储 $this->router->getParams()

"Fatal error: Call to a member function get() on array in" 表示您试图在 $arr 上调用 $arr->get(),那是一个数组(不是对象)。您在 CoreController 的 class getter 中调用了这样的 get() 函数。该调用是从 $registry 属性 的范围进行的,因此应该具有对象类型。

因此在这种情况下,您应该在尝试转储 $this->router->getParams() 之前检查 protected $registry 的类型。它可能不是你所期望的那样。

我没有找到您在代码中实例化 Posts class 的对象的位置以及您在 __constructor() 中作为 $registry 放置的内容,所以我可以不检查我的猜测。如果你澄清这一点,就会更容易找到问题。

正如 alex_edev 注意到的那样,您正试图在数组上调用 get 方法。但它从何而来?

怎么了

Posts控制器在路由器的方法dispatch中初始化。 url /posts/1235/edit 确实匹配第二条路由规则,所以执行下面几行

$controller_object = new $controller($this->params);
$action = $this->params['action'];
$action = $this->convertToCamelCase($action);

注意传递给控制器​​构造函数的内容。您通过路线 params 属性!查看 Posts.phpPosts controller extends CoreController,因此它期望 Registry 作为构造函数的参数,但您传递了一个数组 - Route::params 属性.所以是错误的对象构造阻碍了聚会。

为什么可以正常使用

没有 var_dump 一切正常,因为您不调用 Posts::__get 方法。当您在 Posts 控制器中调用 $this->router->getParams() 时,它会尝试使用 getter 获取未定义的 router 属性 并且由于注册表错误而失败 - 请记住,您注入了一个数组到控制器。

应该做什么

你应该这样启动控制器

$controller_object = new $controller($this->registry);

其中 registry 被注入 __construct:

final class Router
{
    // add definition
    private $registry;

    // pass it to the router
    public function __construct($registry) {
         $this->registry = $registry;
    }
    ....
 }

路由器初始化如下

$registry->set('db', $db);


$router = new \system\core\Router($registry);

所以,你只需要编辑 6 行代码。

P.S。使用 Type declarations 来避免这种错误。如果你写 public function __construct(Registry $registry) php 在传递数组时抛出 TypeError 异常。