包含参数的URL路由

时间:2019-02-04 19:31:17

标签: php url-routing

我一直在尝试创建一个接受参数的路由来获取单个用户,但是我一直在努力了解自己做错了什么,我被困住了。

以下是路线: 第一个作品没有任何问题:

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

这是我的路由器类,我正在匹配网址并使用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'));
}

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

1 个答案:

答案 0 :(得分:0)

在本节中(方法direct

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

应该是

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

或者简单地(也是首选):

explode('@', $controller)

作为URI(对于第3个),是这样的:

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');

您可以将其删除。通常我在网址中看到过这种模式

   www.yoursite.com/controller/method/...args

这给了我们关于零件是什么的“保证”。但这是您的代码,无论什么情况,您都可以决定放弃第一个代码。

我应该提到我没有测试以上任何代码,但是根据我的经验,这些是您可能会需要的功能。

干杯!