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
另外我想第一个和第二个应该可以工作,只有以实际正则表达式作为键的那个会受到影响,因为它是“动态”的。
其他东西
您缺少的一件事是 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
这给了我们一种关于零件是什么的“受让人”。但这是您的代码,您可以决定无论如何都可以丢弃第一个...
我应该提一下,我没有测试上述任何代码,但根据我的经验,这些是您在某些时候可能需要的功能。
干杯!
我一直在尝试创建一个接受参数的路由来获取单个用户,但我很难理解我做错了什么,我被卡住了。
路线如下: 第一个没有任何问题:
<?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
另外我想第一个和第二个应该可以工作,只有以实际正则表达式作为键的那个会受到影响,因为它是“动态”的。
其他东西
您缺少的一件事是 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
这给了我们一种关于零件是什么的“受让人”。但这是您的代码,您可以决定无论如何都可以丢弃第一个...
我应该提一下,我没有测试上述任何代码,但根据我的经验,这些是您在某些时候可能需要的功能。
干杯!