热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

【Laravel系列3.2】路由:指哪儿打哪儿

在没有接触到Laravel之前,早先的框架基本上都没有这个完整的路由的概念。像是TP3、Yii1版本的时候,都是通过入口文件对参数的解析来加载指定的控制器。关于这种形式的加载方式大

在没有接触到 Laravel 之前,早先的框架基本上都没有这个完整的路由的概念。像是 TP3 、Yii1 版本的时候,都是通过入口文件对参数的解析来加载指定的 控制器 。关于这种形式的加载方式大家可以在去看一下老的这些框架是如何实现的。而在 Laravel 中,从我开始接触的时候,就使用的是自定义路由的方式来指定请求的路径。其实,传统方式可以看做是一种隐式路由,而我们需要写的这种是一种显式的路由。Laravel 也是支持隐式路由的,只是它并不推荐这么做。那么,显式路由有什么好处呢?



  • Restful 风格的 URL



  • 多个 URI 指向同一个控制器(可以在路由中区分参数)



  • URI 和 控制器 解耦,自由定义名称,不受控制器里面方法名的限制



  • 很多场景都不需要控制器,比如一个静态页面,直接路由里就可以搞定了



  • 有规范、有约定,路由配置文件写上注释就是个文档



当然,也不能说使用这种显式路由全是好处,每个请求都得过来写一行路由就是它最大的麻烦,而且这玩意多了一层路由的控制,性能自然也没有老框架里面的直接路由来得好。而且团队开发的时候,如果没有拆分路由文件的话,很容易出现冲突。事物总是有两面性的,既然现在 TP5 、 Yii2 都已经在推荐这种显式路由的方式了,那么可以看出,大家在实际的工作中还是更接受显式路由的这些优点的。

另外,在更新的一些框架中,比如需要搭配 Swoole 的 Hyperf 框架中,已经支持 注解路由 这种形式了。大家有兴趣的可以去看一下,这个也是越来越靠近 Java 的一种写法。而且个人使用之后感觉这种方式也很爽。


基本路由配置

简单地介绍一下基本的路由配置,我们可以指定路由的请求,比如使用 GET 还是 POST 。

Route::get('/get/request', function(){
return 'get';
});
Route::post('/post/request', function(){
return 'post';
});

除了常用的这两个之外,完整的 RESTful 格式请求也是支持的,比如 PUT 、DELETE 之类的请求。

Route::put('/put/request', function(){
return 'put';
});

如果你在 POST 的路由上,使用 GET 的方式来进行访问的话,就会触发异常信息,就像下图这样。

从这里也能够看出,Laravel 的路由对于数据安全的好处。大部分情况下,我们使用的接口都会以 POST 为主,特别是数据提交的接口。如果是传统框架没有加任何判断的情况下,也是可以直接请求到控制器的,只是说我们从 $_POST 中无法获取数据而已。这种情况下,如果处理得当一般也不会有什么问题,但有可能也会造成意外的数据泄露,比如说万一我们使用 $_REQUEST 来接收数据。在 Laravel 中路由处理的时候,就会将这个问题给避免了。当然,如果你想让一个请求直接使用任何方式都可以请求,也可以直接在路由中配置。

Route::any('/any/request', function(){
return 'any';
});
Route::match(['get', 'post'], '/match/request', function(){
return 'match get or post';
});

any() 就是接收任意方式的请求,而 match() 则是接收指定数组内部的请求。


请求参数 RESTful 风格配置

对于请求的参数,我们可以使用依赖注入的 Request 来获取,不过还有另外一种方式,可以获取 RESTful 风格的参数,也就是 URL Path 风格的参数。

Route::get('/get/request/{id}/{name?}', function($id, $name=''){
return 'get:' . $id . ', ' . $name;
})->where(['id'=>'[0-9]+', 'name'=>'[a-z]+']);
// http://laravel8/get/request/1/a
// http://laravel8/get/request/1
// http://laravel8/get/request/1/1 404

在这个路由中,我们指定了两个参数,一个是 id 一个是 name ,当使用 /get/request/1/a 或者 /request/1 这两种形式的链接访问的时候,都是可以正常访问的,但使用 /get/request/1/1 则无法访问。name 参数后面的冒号表明这个参数是一个可选的参数,而 where 中则指定了参数的规则,比如 id 必须是数字,而 name 必须是小写的 a 到 z 。注意,这个 where 里面的是正则表达式,它还有一些其它的类似函数可以方便地指定规则而不需要我们手写正则,这个大家可以自行查阅相关的文档。


路由命名与跳转

在原始的 PHP 中,如果我们需要跳转链接,一般使用的是 header() 方法,并在参数里使用 Location:url 这种方式。在 Laravel 中,可以比较方便地在路由中实现跳转。

Route::get('/get/request/{id}/{name?}', function($id, $name=''){
return 'get:' . $id . ', ' . $name;
})->name('get/request/params')->where(['id'=>'[0-9]+', 'name'=>'[a-z]+']);
Route::get('/jump', function(){
$url = route('get/request/params', ['id'=>2, 'name'=>'b']);
echo $url; // http://laravel8/get/request/2/b
return redirect()->route('get/request/params', ['id'=>2, 'name'=>'b']);
});

在这段代码中,我们先给之前测试的那个带参数的路由命名,使用的是 name() 这个方法。然后使用 redirect()->route() 方法就可以实现路由的跳转重定向。其中直接使用 route() 方法可以生成一个指定路由的链接字符串。如果需要使用 header() 方法的话,直接使用这个生成的链接字符串就可以了。


路由组配置

什么是路由组呢?比如我们有一组链接,都归属于同一个资源下,比如下面这几个链接:

http://laravel8/temp/
http://laravel8/temp/{id}
http://laravel8/temp/edit
http://laravel8/temp/delete
http://laravel8/temp/insert

就可以把这五个链接都放到 temp 这个资源路径下,一般这种类似的资源也会只使用一个控制器。这种情况下,我们就可以使用路由组来规范管理这一组链接。

Route::group(['prefix'=>'temp'], function(){
Route::get('/', function(){
return '根列表';
});
Route::get('/{id}', function($id){
return '详情页,id:' . $id;
});
Route::post('/insert', function(){
return '添加';
});
Route::put('/edit', function(){
return '修改';
});
Route::delete('/delete', function(){
return '删除';
});
});

路由与控制器、模型

对于路由操作来说,我们在日常开发中其实很少会在路由文件中写代码,真实的情况其实是路由去指向控制器,实现控制器和路由的关联。这样实现的最大好处其实就是路由和控制器的解耦,也就是前面所说过的路由的好处之一。传统框架中的控制器就是路由,如果想要动态地改变这个路由链接,那么要么改控制器的名字,要么去 nginx 上配置转发重写,都远不如框架中自带路由来得方便快捷。

首先,我们使用在 Http/Controller 下面创建一个控制器 RouteController 。

namespace App\Http\Controllers;
class RouteController extends Controller
{
public function test($id){
return 'test ' . $id;
}
}

然后在路由文件中定义指向这个控制器的路由。

Route::get('route/test/{id}', 'App\Http\Controllers\RouteController@test');
// http://laravel8/route/test/1
Route::get('route/t/{id}', 'App\Http\Controllers\RouteController@test');
// http://laravel8/route/t/1

这样就实现了路由和控制器的关联。当然,这个链接名我们怎么写都可以了,而且都指向同一个控制器中的方法也没有什么问题。需要注意的这个控制器写的格式是完整的 命名空间 名,然后接一个 @ 符号后面是方法名。当然,如果一个控制器是一个 RESTful 资源文件的话,直接使用 Route::resource() 去指向一个控制器就可以了,可以省略掉方法名,这种方式在文档中叫做 资源型控制器 。这个功能大家可以自己参考文档实现,我们也将在下篇文章学习控制器的时候进行简单的演示。

除了字符串的写法外,还可以使用类名的方式。

Route::get('route/tt/{id}', [\App\Http\Controllers\RouteController::class, 'test']);
// http://laravel8/route/tt/1

路由与模型的绑定

对于模型来说,我们也可以直接进行路由的关联,不过这种操作也是比较少见的。

Route::get('route/user/{user}', function(\App\Models\User $user){
return $user->name;
});
// http://laravel8/route/user/1
// Erwin Ortiz

上述代码中,我们需要参数名称和模型对象的参数名称相同,然后在 URL 中传递对应数据的 ID ,这样就可以直接查询到模型对象对应的数据信息。对于这个功能,大家也是以了解为主,实际的业务开发中,除了真的只是要一些简单的数据,否则不会直接通过路由就去操作模型,毕竟少了控制器的一些参数整合过滤,还是略有一些风险的。


路由分析

在研究路由的源码路径之前,我们先看一个命令。

php artisan route:list

它的作用是使用呢?可以看到我们现在已经定义的路由都有哪些。

接下来,我们以 http://laravel8/temp/ 这个链接为例,使用调试工具看一下路由是如何调用分派的。

首先当然还是请求的封装,也就是我们上篇文章中的 Request 对象的生成。接下来,使用这个 Request 对象,并根据它的 pathInfo 属性来找到对应的路由信息。

通过

laravel/framework/src/Illuminate/Foundation/Http/Kernel.php 内核文件中 dispatchToRouter() 方法,开始进入路由分派的操作。

dispatchToRouter() 方法中的 $this->router->dispatch($request) 方法调用会一路进入到

laravel/framework/src/Illuminate/Routing/Router.php 的 findRoute() 方法中,接着我们一路进入到 $this->routes->match() 方法,它就是核心的路由分派函数,最主要的就是用于找到我们这条 Request 请求对应的路由是哪一个。

这个 \$this->routes->match() 的对象是

laravel/framework/src/Illuminate/Routing/RouteCollection.php

文件中的这个 RouteCollection 类,其实也就是调用的这个类的 match() 方法。在 match() 方法里面,$this->get($request->getMethod()) 会获取所有的路由信息,并保存在 routes 这个变量中(就像我们上面那个查看所有路由信息的命令一样),然后通过 matchAgainstRoutes() 方法去匹配相应的路由,最后的路由结果可以在 route 对象中的 uri 属性中看到。没错,最后这个 uri 中显示的正是 temp ,也就是我们访问的路径。而这个 route 对象,正是我们需要找到的那个路由。

// laravel/framework/src/Illuminate/Routing/RouteCollection.php
public function match(Request $request)
{
$routes = $this->get($request->getMethod());
// First, we will see if we can find a matching route for this current request
// method. If we can, great, we can just return it so that it can be called
// by the consumer. Otherwise we will check for routes with another verb.
$route = $this->matchAgainstRoutes($routes, $request);
return $this->handleMatchedRoute($request, $route);
}

接着,这个 route 对象继续向上并通过一系列的中间件管道来到 laravel/framework/src/Illuminate/Routing/Route.php 文件的 run() 方法中,在这个方法里面,框架会判断这个路由是走控制器还是走回调函数。在这里我们定义的路由走的是回调函数。

// laravel/framework/src/Illuminate/Routing/Route.php
public function run()
{
$this->cOntainer= $this->container ?: new Container;
try {
if ($this->isControllerAction()) {
return $this->runController();
}
return $this->runCallable();
} catch (HttpResponseException $e) {
return $e->getResponse();
}
}

也就是走的 return $this->runCallable(); 这一行。接下来,就回到路由文件中 temp 这个路由的回调函数中。之后就是响应的输出了。

整个路由功能的调用路径就是这样,其实相对来说没有请求响应的路径长,毕竟它只是请求响应路径中的一部分而已。


总结

关于路由还有很多可以配置的技巧与功能,在这里就不一一列出了,毕竟我们是以应用和源码分析为主,很多小技巧其实也并不是很常用。比如说路由绑定模型这个就从来没有用过,但是,存在即合理,总会有它的应用场景,否则框架的作者也不会保留着这个功能,您说是不是。

参考文档:

https://learnku.com/docs/laravel/8.x/routing/9365

https://learnku.com/laravel/t/3614/similar-to-the-laravel-framework-such-explicit-routing-is-really-good#reply1



推荐阅读
  • 本文作为探讨PHP依赖注入容器系列文章的开篇,将首先通过具体示例详细阐述依赖注入的基本概念及其重要性,为后续深入解析容器的实现奠定基础。 ... [详细]
  • 包含phppdoerrorcode的词条 ... [详细]
  • 本文详细介绍了如何在PHP中记录和管理行为日志,包括ThinkPHP框架中的日志记录方法、日志的用途、实现原理以及相关配置。 ... [详细]
  • Laravel 开发技巧:如何为集合中的每个元素添加递增编号
    本文将介绍如何在 Laravel 集合中为每个数组元素添加递增的编号,帮助开发者更好地管理和操作数据。 ... [详细]
  • 本文详细解析了一种实用的函数,用于从URL中提取查询参数。该函数通过处理URL中的搜索部分,能够高效地获取并解析出所需的参数值,适用于各种Web开发场景。 ... [详细]
  • 在对WordPress Duplicator插件0.4.4版本的安全评估中,发现其存在跨站脚本(XSS)攻击漏洞。此漏洞可能被利用进行恶意操作,建议用户及时更新至最新版本以确保系统安全。测试方法仅限于安全研究和教学目的,使用时需自行承担风险。漏洞编号:HTB23162。 ... [详细]
  • 如何撰写适应变化的高效代码:策略与实践
    编写高质量且适应变化的代码是每位程序员的追求。优质代码的关键在于其可维护性和可扩展性。本文将从面向对象编程的角度出发,探讨实现这一目标的具体策略与实践方法,帮助开发者提升代码效率和灵活性。 ... [详细]
  • 在PHP中使用`exec`函数执行Shell命令时,需要特别注意安全性问题。当使用来自远程的数据构建命令时,极易引发命令注入漏洞。本文详细探讨了如何避免使用污染数据,确保系统命令的安全执行,并提供了多种防护措施和最佳实践。 ... [详细]
  • 本文作为“实现简易版Spring系列”的第五篇,继前文深入探讨了Spring框架的核心技术之一——控制反转(IoC)之后,将重点转向另一个关键技术——面向切面编程(AOP)。对于使用Spring框架进行开发的开发者来说,AOP是一个不可或缺的概念。了解AOP的背景及其基本原理,对于掌握这一技术至关重要。本文将通过具体示例,详细解析AOP的实现机制,帮助读者更好地理解和应用这一技术。 ... [详细]
  • 本文探讨了在Lumen框架中实现自定义表单验证功能的方法与挑战。Lumen的表单验证机制默认返回无状态的JSON格式API响应,这给初学者带来了一定的难度。通过深入研究Validate类,作者分享了如何有效配置和使用自定义验证规则,以提升表单数据的准确性和安全性。 ... [详细]
  • 自然语言处理(NLP)——LDA模型:对电商购物评论进行情感分析
    目录一、2020数学建模美赛C题简介需求评价内容提供数据二、解题思路三、LDA简介四、代码实现1.数据预处理1.1剔除无用信息1.1.1剔除掉不需要的列1.1.2找出无效评论并剔除 ... [详细]
  • PHP 5.5.31 和 PHP 5.6.17 安全更新发布
    PHP 5.5.31 和 PHP 5.6.17 已正式发布,主要包含多个安全修复。强烈建议所有用户尽快升级至最新版本以确保系统安全。 ... [详细]
  • 本文介绍了如何在Python中使用插值方法将不同分辨率的数据统一到相同的分辨率。 ... [详细]
  • 本地存储组件实现对IE低版本浏览器的兼容性支持 ... [详细]
  • 该大学网站采用PHP和MySQL技术,在校内可免费访问某些外部收费资料数据库。为了方便学生校外访问,建议通过学校账号登录实现免费访问。具体方案可包括利用学校服务器作为代理,结合身份验证机制,确保合法用户在校外也能享受免费资源。 ... [详细]
author-avatar
书友47721235_104
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有