URL 包含参数的路由

URL routing containing parameters

我一直在尝试创建一个接受参数的路由来获取单个用户,但我很难理解我做错了什么,我被卡住了。

路线如下: 第一个没有任何问题:

<?php
$router->get('users', 'UsersController@index');
$router->get('users/about', 'UsersController@test');
$router->get('users/:id', 'UsersController@show');

这是我的路由器 Class,我正在匹配 url 并使用 preg_replace 因此我可以动态获取 ID

<?php

namespace App\Core;

class Router
{
    /**
     * All registered routes.
     *
     * @var array
     */
    public $routes = [
        'GET' => [],
        'POST' => []
    ];

    /**
     * Load a user's routes file.
     *
     * @param string $file
     */
    public static function load($file)
    {
        $router = new static;

        require $file;

        return $router;
    }

    /**
     * Register a GET route.
     *
     * @param string $uri
     * @param string $controller
     */
    public function get($uri, $controller)
    {
        $this->routes['GET'][$uri] = $controller;
    }

    /**
     * Register a POST route.
     *
     * @param string $uri
     * @param string $controller
     */
    public function post($uri, $controller)
    {
        $this->routes['POST'][$uri] = $controller;
    }


    /**
     * Load the requested URI's associated controller method.
     *
     * @param string $uri
     * @param string $requestType
     */
    public function direct($uri, $requestType)
    {
        $matches = [];

        foreach ($this->routes[$requestType] as $regex => $controller) {

            $pattern = "@^" . preg_replace('/\\:[a-zA-Z0-9\_\-]+/', '([a-zA-Z0-9\-\_]+)', preg_quote($regex)) . "$@D";

            if ( preg_match($pattern, $uri, $matches ) ) {

                print_r($matches[0]);


                return $this->callAction(
                    ...explode('@', $this->routes[$requestType][$uri])
                );
            }
        }

        throw new Exception('No route defined for this URI.');
    }

    /**
     * Load and call the relevant controller action.
     *
     * @param string $controller
     * @param string $action
     */
    protected function callAction($controller, $action)
    {

        $controller = "App\Controllers\{$controller}";
        $controller = new $controller;

        if (! method_exists($controller, $action)) {
            throw new Exception(
                "{$controller} does not respond to the {$action} action."
            );
        }

        return $controller->$action();
    }
}

在我的用户控制器中,我只有一个函数可以获取 id 并根据 $id

向我显示用户
/**
 * Show selected user.
 */

public function show($id)

{
$id = array_slice(explode('/', rtrim($_SERVER['REQUEST_URI'], '/')), -1)[0];

$user = App::get('database')->get('users', [
    'id' => $id
]);

return view('user', compact('user'));
}

如果你们需要更多信息,我可以将整个代码添加到代码笔中。谢谢

本节(方法direct

explode('@', $this->routes[$requestType][$uri])

这应该是

explode('@', $this->routes[$requestType][$regex])

或简单地(首选):

explode('@', $controller)

因为 URI(第三个)是这样的:

users/10
users/20

而实际的键是:users/:id 这也是 $regex 值(显然)

代码(仅供测试):

$routes = [
    'GET' => [
        'users'=>'UsersController@index',
        'users/about'=>'UsersController@test',
        'users/:id'=>'UsersController@show'
    ],
    'POST' => []
];

$requestType = 'GET';

$uri = 'users/10';

foreach ($routes[$requestType] as $regex => $controller) {
    $pattern = "@^" . preg_replace('/\\:[a-zA-Z0-9\_\-]+/', '([a-zA-Z0-9\-\_]+)', preg_quote($regex)) . "$@D";
    
    if ( preg_match($pattern, $uri, $matches ) ) {
    
        print_r($matches[0]);
        echo "\n";
        print_r($routes[$requestType][$uri]);
        echo "\n";
        print_r($routes[$requestType][$regex]);
    }
}

输出:

  #$matches[0]
  users/10
  #with $uri as the key - $routes[$requestType][$uri]
  <b>Notice</b>:  Undefined index: users/10 in <b>[...][...]</b> on line <b>27</b><br />
  #with $regex as the key - $routes[$requestType][$regex]
  UsersController@show

Sandbox

另外我想第一个和第二个应该可以工作,只有以实际正则表达式作为键的那个会受到影响,因为它是“动态”的。

其他东西

您缺少的一件事是 url 中的参数,以第三个示例 (users/10) 为例,您如何将 ID (10) 传递到您的控制器中?另外,如果是我,我会打破你对这一行的依赖 $controller = "App\Controllers\{$controller}"; 因为它限制你只能使用 App\Controllers\... 命名空间的 类。

因此,要解决此问题,请更改您的数据结构以删除 @ 符号。所以不是这个:

 $router->get('users', 'UsersController@index');

这样做:

 #Obj::class returns the fully qualified class name (includes namespace)
 # PHP 5.6+ I think?
 $router->get('users', [UsersController::class,'index']);

这实际上会简化您的代码并让您有可能做这样的事情(更简单和更灵活):

   $router->get('users', function(){
        //do something simple
   });
   #or
   $router->get('users', 'somefunction');
   #or (drop in plugins outside of your normal controller folder)
   $router->get('users', 'Plugins/Users/Controllers/User);

所以我们必须稍微修改一下:

public function direct($uri, $requestType)
{
    $matches = [];

    foreach ($this->routes[$requestType] as $regex => $controller) {

        $pattern = "@^" . preg_replace('/\\:[a-zA-Z0-9\_\-]+/', '([a-zA-Z0-9\-\_]+)', preg_quote($regex)) . "$@D";

        if ( preg_match($pattern, $uri, $matches ) ) {
            //Simplify the code here and also pass the uri as an array
            return $this->callAction($controller, explode('/', $uri));
        }
    }

    throw new Exception('No route defined for this URI.');
}

protected function callAction($controller, array $args=[])
{
    //you can check types here but all callables work with call_user_func & call_user_func_array

    //you may be able to just check !is_callable($controller) for them all if you don't need the granularity

    if(is_array($controller)){
        //[object, method]
        //[class name, method]
         if(!class_exists($controller[0]) || !method_exists($controller[0], $controller[1])){ 
            //the router has a direct interface to the end user
            //because of this it must handle requests to bad URLs and such
            //direct to 404 page, for example something like this
            //you can and should "log" the errors, but don't show them
            // ---- return $this->error404();
         }  
    }else if(is_object($controller) && !is_callable($controller)){
         //closure or magic method __invoke
          // ---- return $this->error404();
    }else if( !function_exists($controller) ){
         //standard functions
          // ---- return $this->error404();
    }

    return call_user_func_array($action, $args);
}

通过这个简单的设置,所有参数都会被传递,包括控制器的名称(如果它是 url 的一部分)。例如,使用值为 users/10 的第三条路由将调用

  $UsersController->show('users', '10');

在不将“方法”烘焙到路由路径的情况下删除它可能具有挑战性:例如

  $router->get('users/about', 'UsersController@test');

没有办法“知道”“用户”对“测试”方法是否重要。现在如果他们匹配:

  $router->get('test/about', 'UsersController@test');

您可以删除它。通常我在 urls

中看到过这种模式
   www.yoursite.com/controller/method/...args

这给了我们一种关于零件是什么的“受让人”。但这是您的代码,您可以决定无论如何都可以丢弃第一个...

我应该提一下,我没有测试上述任何代码,但根据我的经验,这些是您在某些时候可能需要的功能。

干杯!