基本控制器及控制器路由、控制器中间件都比较简单,这里不再赘述,相关文档参考HTTP 控制器文档一节。 1、创建RESTFul风格控制器 注:关于什么是RESTFul风格及其规范可参考这篇文章:理解RESTful架构。 本文我们主要讨论创建一个RESTFul风格的控制器用于对博客文章进行增删改查,创建这样的控制器很简单,在应用根目录运行如下Artisan命令即可:
1 |
php artisan make:controller PostController |
该命令会在app/Http/Controllers目录下生成一个PostController.php文件,该控制器内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 |
<?php namespace App\Http\Controllers; use Illuminate\Http\Request; use App\Http\Requests; use App\Http\Controllers\Controller; class PostController extends Controller { /** * 显示文章列表. * * @return Response */ public function index() { // } /** * 创建新文章表单页面 * * @return Response */ public function create() { // } /** * 将新创建的文章存储到存储器 * * @param Request $request * @return Response */ public function store(Request $request) { // } /** * 显示指定文章 * * @param int $id * @return Response */ public function show($id) { // } /** * 显示编辑指定文章的表单页面 * * @param int $id * @return Response */ public function edit($id) { // } /** * 在存储器中更新指定文章 * * @param Request $request * @param int $id * @return Response */ public function update(Request $request, $id) { // } /** * 从存储器中移除指定文章 * * @param int $id * @return Response */ public function destroy($id) { // } } |
2、为RESTFul风格控制器注册路由 接下来我们在routes.php文件中为该控制器注册路由:
1 |
Route::resource('post','PostController'); |
该路由包含了指向多个动作的子路由: 方法 路径 动作 路由名称 GET /post index post.index GET /post/create create post.create POST /post store post.store GET /post/{post} show post.show GET /post/{post}/edit edit post.edit PUT/PATCH /post/{post} update post.update DELETE /post/{post} destroy post.destroy 比如我们在浏览器中以GET方式访问http://laravel.app:8000/post,则访问的是PostController的index方法,我们可以通过route('post.index')生成对应路由URL。类似的,如果我们以POST方式访问http://laravel.app:8000/post,则访问的是PostController的store方法,对应的POST表单action属性值则可以通过route('post.store')来生成。 3、实例教程——文章增删改查 接下来我们演示基本的增删改查操作,关于数据库的操作我们后面再讲,这里我们使用缓存作为存储器(Laravel默认使用文件缓存)。 注意:我们这里用到了Cache门面,使用前不要忘了在PostController顶部使用use Cache;引入。关于Cache的用法,可参考缓存文档。 3.1 新增文章 首先我们新增一篇文章,定义PostController控制器的create方法和store方法如下(视图部门我们放到后面讲,这里就将HTML放到PHP变量里):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
/** * 创建新文章表单页面 * * @return Response */ public function create() { $postUrl = route('post.store'); $csrf_field = csrf_field(); $html = <<<CREATE <form action="$postUrl" method="POST"> $csrf_field <input type="text" name="title"><br/><br/> <textarea name="content" cols="50" rows="5"></textarea><br/><br/> <input type="submit" value="提交"/> </form> CREATE; return $html; } /** * 将新创建的文章存储到存储器 * * @param Request $request * @return Response */ public function store(Request $request) { $title = $request->input('title'); $content = $request->input('content'); $post = ['title'=>trim($title),'content'=>trim($content)]; $posts = Cache::get('posts',[]); if(!Cache::get('post_id')){ Cache::add('post_id',1,60); }else{ Cache::increment('post_id',1); } $posts[Cache::get('post_id')] = $post; Cache::put('posts',$posts,60); return redirect()->route('post.show',['post'=>Cache::get('post_id')]); } |
3.2 查看文章 访问http://laravel.app:8000/post/create页面,填写表单,点击“提交”,保存成功后,页面跳转到详情页:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
/** * 显示指定文章 * * @param int $id * @return Response */ public function show($id) { $posts = Cache::get('posts',[]); if(!$posts || !$posts[$id]) exit('Nothing Found!'); $post = $posts[$id]; $editUrl = route('post.edit',['post'=>$id]); $html = <<<DETAIL <h3>{$post['title']}</h3> <p>{$post['content']}</p> <p> <a href="{$editUrl}">编辑</a> </p> DETAIL; return $html; } |
3.3 编辑文章 同理我们定义编辑文章对应的edit方法和update方法如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
/** * 显示编辑指定文章的表单页面 * * @param int $id * @return Response */ public function edit($id) { $posts = Cache::get('posts',[]); if(!$posts || !$posts[$id]) exit('Nothing Found!'); $post = $posts[$id]; $postUrl = route('post.update',['post'=>$id]); $csrf_field = csrf_field(); $html = <<<UPDATE <form action="$postUrl" method="POST"> $csrf_field <input type="hidden" name="_method" value="PUT"/> <input type="text" name="title" value="{$post['title']}"><br/><br/> <textarea name="content" cols="50" rows="5">{$post['content']}</textarea><br/><br/> <input type="submit" value="提交"/> </form> UPDATE; return $html; } /** * 在存储器中更新指定文章 * * @param Request $request * @param int $id * @return Response */ public function update(Request $request, $id) { $posts = Cache::get('posts',[]); if(!$posts || !$posts[$id]) exit('Nothing Found!'); $title = $request->input('title'); $content = $request->input('content'); $posts[$id]['title'] = trim($title); $posts[$id]['content'] = trim($content); Cache::put('posts',$posts,60); return redirect()->route('post.show',['post'=>Cache::get('post_id')]); } |
3.4 删除文章 我们还可以使用destroy方法删除文章:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
/** * 从存储器中移除指定文章 * * @param int $id * @return Response */ public function destroy($id) { $posts = Cache::get('posts',[]); if(!$posts || !$posts[$id]) exit('Nothing Deleted!'); unset($posts[$id]); Cache::decrement('post_id',1); return redirect()->route('post.index'); } |
要删除文章,需要参考编辑表单伪造删除表单方法为DELETE(一般使用AJAX删除),这里不再演示。 3.5 文章列表 最后我们再来定义一个用于显示所有文章列表的index方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
/** * 显示文章列表. * * @return Response */ public function index() { $posts = Cache::get('posts',[]); if(!$posts) exit('Nothing'); $html = '<ul>'; foreach ($posts as $key=>$post) { $html .= '<li><a href='.route('post.show',['post'=>$key]).'>'.$post['title'].'</li>'; } $html .= '</ul>'; return $html; } from:<a href="http://laravelacademy.org/post/549.html">http://laravelacademy.org/post/549.html</a> |
一种软件架构风格,设计风格而不是标准,只是提供了一组设计原则和约束条件。它主要用于客户端和服务器交互类的软件。基于这个风格设计的软件可以更简洁,更有层次,更易于实现缓存等机制。 REST(英文:Representational State Transfer,简称REST)描述了一个架构样式的网络系统,比如 web 应用程序。它首次出现在 2000 年 Roy Fielding 的博士论文中,他是 HTTP 规范的主要编写者之一。在目前主流的三种Web服务交互方案中,REST相比于SOAP(Simple Object Access protocol,简单对象访问协议)以及XML-RPC更加简单明了,无论是对URL的处理还是对Payload的编码,REST都倾向于用更加简单轻量的方法设计和实现。值得注意的是REST并没有一个明确的标准,而更像是一种设计的风格。
View Details中间件是一种独立的系统软件或服务程序,分布式应用软件借助这种软件在不同的技术之间共享资源。中间件位于客户机/ 服务器的操作系统之上,管理计算机资源和网络通讯。是连接两个独立应用程序或独立系统的软件。相连接的系统,即使它们具有不同的接口,但通过中间件相互之间仍能交换信息。执行中间件的一个关键途径是信息传递。通过中间件,应用程序可以工作于多平台或OS环境。
View Details1、中间件简介 Laravel中可以把HTTP中间件看做“装饰器”,在请求到达最终动作之前对请求进行过滤和处理。 中间件在Laravel中有着广泛的应用,比如用户认证、日志、维护模式、开启Session、从Session中获取错误信息,以及上一篇教程中提到的CSRF验证,等等。 中间件类默认存放在app/Http/Middleware目录下。 2、中间件创建及其使用 我们在《HTTP路由实例教程(二)—— 路由命名和路由分组》一文已经演示了如何创建中间件以及中间件的基本使用。 自定义中间件类只需要定义一个handle方法即可,然后我们将主要业务逻辑定义在该方法中,如果我们想在请求处理前执行业务逻辑,则在$next闭包执行前执行业务逻辑操作:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
<?php namespace App\Http\Middleware; use Closure; class BeforeMiddleware { public function handle($request, Closure $next) { // 执行业务逻辑操作 return $next($request); } } |
如果想要在请求处理后执行中间件业务逻辑,则在$next闭包执行后执行操作:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
<?php namespace App\Http\Middleware; use Closure; class AfterMiddleware { public function handle($request, Closure $next) { $response = $next($request); // 执行动作 return $response; } } |
我们处理的大部分操作都是第一种场景,即在请求处理前执行操作,比如用户认证、CSRF验证、维护模式等都是这样,但也有用到第二种场景的时候,比如StartSession中间件,该中间件在请求处理前后都有操作,其handle方法如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
public function handle($request, Closure $next) { $this->sessionHandled = true; //如果session驱动已配置,那么我们需要开启session以便为应用准备好数据 //注意Laravel session并没有使用原生的PHP session相关方法,因为它们显得那样蹩脚 if ($this->sessionConfigured()) { $session = $this->startSession($request); $request->setSession($session); } $response = $next($request); // 同样,如果session经过配置那么我们需要关闭session以便将session数据持久化到某些存储介质中 // 我们还会添加session id到响应头cookie中 if ($this->sessionConfigured()) { $this->storeCurrentUrl($request, $session); $this->collectGarbage($session); $this->addCookieToResponse($response, $session); } return $response; } |
此外,定义好中间件后,需要在app/Http/Kernel.php文件中注册该中间件,如果我们定义的中间件想要在全局有效,即每次请求都会调用,则将该中间件追加到$middleware属性数组;否则如果中间件只是在某些特定的路由中使用,则将其追加到$routeMiddleware属性数组,并在路由定义时使用middleware选项指定。关于这一点我们已经在路由分组中有所陈述,这里不再赘述。 3、中间件参数 除了请求实例$request和闭包$next之外,中间件还可以接收额外参数,我们还是以TestMiddleware为例,现在要求年龄在18岁以上的男性才能访问指定页面,handle方法定义如下:
1 2 3 4 5 6 7 8 |
public function handle($request, Closure $next, $gender) { if($request->input('age')>=18 && $gender==$request->input('gender')){ return $next($request); }else{ return redirect()->route('refuse'); } } |
对应的路由配置如下:
1 2 3 4 5 6 7 8 9 10 11 12 |
Route::group(['middleware'=>'test:male'],function(){ Route::get('/write/laravelacademy',function(){ //使用Test中间件 }); Route::get('/update/laravelacademy',function(){ //使用Test中间件 }); }); Route::get('/age/refuse',['as'=>'refuse',function(){ return "18岁以上男子才能访问!"; }]); |
4、定义可终止的中间件 可终止的中间件是指定义了terminate方法的中间件,terminate方法会在一次请求生命周期的末尾执行一些操作。比如StartSession中间件定义了该方法,在响应数据发送到浏览器之后将session数据保存起来。 可终止的中间件需要追加到app/Http/Kernel.php类的全局中间件列表即$middleware属性数组中。 调用中间件的terminate方法时,Laravel会从服务容器中取出新的中间件实例,所以如果想要调用handle方法和terminate方法时使用的是同一个中间件实例,需要使用singleton方法将该中间件注册到服务容器。 from:http://laravelacademy.org/post/537.html
View Details一.CSRF是什么? CSRF(Cross-site request forgery),中文名称:跨站请求伪造,也被称为:one click attack/session riding,缩写为:CSRF/XSRF。 二.CSRF可以做什么? 你这可以这么理解CSRF攻击:攻击者盗用了你的身份,以你的名义发送恶意请求。CSRF能够做的事情包括:以你名义发送邮件,发消息,盗取你的账号,甚至于购买商品,虚拟货币转账……造成的问题包括:个人隐私泄露以及财产安全。 三.CSRF漏洞现状 CSRF这种攻击方式在2000年已经被国外的安全人员提出,但在国内,直到06年才开始被关注,08年,国内外的多个大型社区和交互网站分别爆出CSRF漏洞,如:NYTimes.com(纽约时报)、Metafilter(一个大型的BLOG网站),YouTube和百度HI……而现在,互联网上的许多站点仍对此毫无防备,以至于安全业界称CSRF为“沉睡的巨人”。 四.CSRF的原理 下图简单阐述了CSRF攻击的思想: 从上图可以看出,要完成一次CSRF攻击,受害者必须依次完成两个步骤: 1.登录受信任网站A,并在本地生成Cookie。 2.在不登出A的情况下,访问危险网站B。 看到这里,你也许会说:“如果我不满足以上两个条件中的一个,我就不会受到CSRF的攻击”。是的,确实如此,但你不能保证以下情况不会发生: 1.你不能保证你登录了一个网站后,不再打开一个tab页面并访问另外的网站。 2.你不能保证你关闭浏览器了后,你本地的Cookie立刻过期,你上次的会话已经结束。(事实上,关闭浏览器不能结束一个会话,但大多数人都会错误的认为关闭浏览器就等于退出登录/结束会话了……) 3.上图中所谓的攻击网站,可能是一个存在其他漏洞的可信任的经常被人访问的网站。 上面大概地讲了一下CSRF攻击的思想,下面我将用几个例子详细说说具体的CSRF攻击,这里我以一个银行转账的操作作为例子(仅仅是例子,真实的银行网站没这么傻:>) 示例1: 银行网站A,它以GET请求来完成银行转账的操作,如:http://www.mybank.com/Transfer.php?toBankId=11&money=1000 危险网站B,它里面有一段HTML的代码如下: <img src=http://www.mybank.com/Transfer.php?toBankId=11&money=1000> 首先,你登录了银行网站A,然后访问危险网站B,噢,这时你会发现你的银行账户少了1000块…… 为什么会这样呢?原因是银行网站A违反了HTTP规范,使用GET请求更新资源。在访问危险网站B的之前,你已经登录了银行网站A,而B中的<img>以GET的方式请求第三方资源(这里的第三方就是指银行网站了,原本这是一个合法的请求,但这里被不法分子利用了),所以你的浏览器会带上你的银行网站A的Cookie发出Get请求,去获取资源“http://www.mybank.com/Transfer.php?toBankId=11&money=1000”,结果银行网站服务器收到请求后,认为这是一个更新资源操作(转账操作),所以就立刻进行转账操作…… 示例2: 为了杜绝上面的问题,银行决定改用POST请求完成转账操作。 银行网站A的WEB表单如下: <form action="Transfer.php" method="POST"> <p>ToBankId: <input type="text" name="toBankId" /></p> <p>Money: <input type="text" name="money" /></p> <p><input type="submit" value="Transfer" /></p> </form> 后台处理页面Transfer.php如下: <?php session_start(); if (isset($_REQUEST['toBankId'] && isset($_REQUEST['money'])) { buy_stocks($_REQUEST['toBankId'], $_REQUEST['money']); } ?> 危险网站B,仍然只是包含那句HTML代码: <img src=http://www.mybank.com/Transfer.php?toBankId=11&money=1000> 和示例1中的操作一样,你首先登录了银行网站A,然后访问危险网站B,结果…..和示例1一样,你再次没了1000块~T_T,这次事故的原因是:银行后台使用了$_REQUEST去获取请求的数据,而$_REQUEST既可以获取GET请求的数据,也可以获取POST请求的数据,这就造成了在后台处理程序无法区分这到底是GET请求的数据还是POST请求的数据。在PHP中,可以使用$_GET和$_POST分别获取GET请求和POST请求的数据。在JAVA中,用于获取请求数据request一样存在不能区分GET请求数据和POST数据的问题。 示例3: 经过前面2个惨痛的教训,银行决定把获取请求数据的方法也改了,改用$_POST,只获取POST请求的数据,后台处理页面Transfer.php代码如下: <?php session_start(); if (isset($_POST['toBankId'] && isset($_POST['money'])) { buy_stocks($_POST['toBankId'], $_POST['money']); } ?> 然而,危险网站B与时俱进,它改了一下代码: <html> <head> <script type="text/javascript"> function steal() { iframe = document.frames["steal"]; iframe.document.Submit("transfer"); } </script> </head> <body onload="steal()"> <iframe name="steal" display="none"> <form method="POST" name="transfer" action="http://www.myBank.com/Transfer.php"> <input type="hidden" name="toBankId" value="11"> <input type="hidden" name="money" value="1000"> </form> </iframe> </body> </html> 如果用户仍是继续上面的操作,很不幸,结果将会是再次不见1000块……因为这里危险网站B暗地里发送了POST请求到银行! 总结一下上面3个例子,CSRF主要的攻击模式基本上是以上的3种,其中以第1,2种最为严重,因为触发条件很简单,一个<img>就可以了,而第3种比较麻烦,需要使用JavaScript,所以使用的机会会比前面的少很多,但无论是哪种情况,只要触发了CSRF攻击,后果都有可能很严重。 理解上面的3种攻击模式,其实可以看出,CSRF攻击是源于WEB的隐式身份验证机制!WEB的身份验证机制虽然可以保证一个请求是来自于某个用户的浏览器,但却无法保证该请求是用户批准发送的! 五.CSRF的防御 我总结了一下看到的资料,CSRF的防御可以从服务端和客户端两方面着手,防御效果是从服务端着手效果比较好,现在一般的CSRF防御也都在服务端进行。 1.服务端进行CSRF防御 服务端的CSRF方式方法很多样,但总的思想都是一致的,就是在客户端页面增加伪随机数。 (1).Cookie Hashing(所有表单都包含同一个伪随机值): […]
View Details1、什么是CSRF攻击 CSRF是跨站请求伪造(Cross-site request forgery)的英文缩写。关于CSRF攻击原理及其防护,可查看Github上的这个项目:理解CSRF,说得比较详细和透彻。 2、Laravel中如何避免CSRF攻击 Laravel框架中避免CSRF攻击很简单:Laravel自动为每个用户Session生成了一个CSRF Token,该Token可用于验证登录用户和发起请求者是否是同一人,如果不是则请求失败。 Laravel提供了一个全局帮助函数csrf_token来获取该Token值,因此只需在视提交图表单中添加如下HTML代码即可在请求中带上Token:
1 |
<input type="hidden" name="_token" value="<?php echo csrf_token(); ?>"> |
该段代码等同于全局帮助函数csrf_field的输出:
1 |
<?php echo csrf_field(); ?> |
在Blade模板引擎中还可以使用如下方式调用:
1 |
{!! csrf_field() !!} |
测试代码 我们在routes.php中定义如下代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
Route::get('testCsrf',function(){ $csrf_field = csrf_field(); $html = <<<GET <form method="<a title="View all posts in POST" href="http://laravelacademy.org/tags/post" target="_blank">POST</a>" action="/testCsrf"> {$csrf_field} <input type="submit" value="Test"/> </form> GET; return $html; }); Route::post('testCsrf',function(){ return 'Success!'; }); |
在浏览器中我们输入http://laravel.app:8000/testCsrf,点击“Test”按钮,浏览器输出:
1 |
Success! |
则表示请求成功,否则,如果我们定义GET路由如下:
1 2 3 4 5 6 7 8 |
Route::get('testCsrf',function(){ $html = <<<GET <form method="POST" action="/testCsrf"> <input type="submit" value="Test"/> </form> GET; return $html; }); |
则点击“Test”按钮,则抛出TokenMismatchException异常。 3、从CSRF验证中排除指定URL 并不是所有请求都需要避免CSRF攻击,比如去第三方API获取数据的请求。 可以通过在VerifyCsrfToken(app/Http/Middleware/VerifyCsrfToken.php)中间件中将要排除的请求URL添加到$except属性数组中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
<?php namespace App\Http\Middleware; use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as BaseVerifier; class VerifyCsrfToken extends BaseVerifier { /** * 指定从 CSRF 验证中排除的URL * * @var array */ protected $except = [ 'testCsrf' ]; } |
这样我们刷新页面,再次在http://laravel.app:8000/testCsrf页面中点击“Test”按钮,则页面不会报错,正常输出如下内容:
1 |
Success! |
4、X-CSRF-Token及其使用 如果使用Ajax提交POST表单,又该如何处理呢?我们可以将Token设置在meta中:
1 |
<meta name="csrf-token" content="{{ csrf_token() }}"> |
然后在全局Ajax中使用这种方式设置X-CSRF-Token请求头并提交:
1 2 3 4 5 |
$.ajaxSetup({ headers: { 'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content') } }); |
Laravel的VerifyCsrfToken中间件会检查X-CSRF-TOKEN请求头,如果该值和Session中CSRF值相等则验证通过,否则不通过。 5、X-XSRF-Token及其使用 除此之外,Laravel还会将CSRF的值保存到名为XSRF-TOKEN的Cookie中,然后在VerifyCsrfToken中间件验证该值,当然,我们不需要手动做任何操作,一些JavaScript框架如Angular会自动帮我们实现。 6、Laravel中CSRF验证原理分析 说了这么多使用方式,接下来我们来分析下源码,看看Laravel底层到底是如何避免CSRF攻击的: 1)首先Laravel开启Session时会生成一个token值并存放在Session中(Illuminate\Session\Store.php第90行start方法),对应源码如下:
1 2 3 4 5 6 7 8 9 10 |
public function start() { $this->loadSession(); if (! $this->has('_token')) { $this->regenerateToken(); } return $this->started = true; } |
2)然后重点分析VerifyToken中间件的handle方法,该方法中先通过isReading方法判断请求方式,如果请求方法是HEAD、GET、OPTIONS其中一种,则不做CSRF验证; 3)再通过shouldPassThrough方法判断请求路由是否在$excpet属性数组中进行了排除,如果做了排除也不做验证; 4)最后通过tokensMatch方法判断请求参数中的CSRF TOKEN值和Session中的Token值是否相等,如果相等则通过验证,否则抛出TokenMismatchException异常。 对应源码如下:
1 2 3 4 5 6 7 8 |
public function handle($request, Closure $next) { if ($this->isReading($request) || $this->shouldPassThrough($request) || $this->tokensMatch($request)) { return $this->addCookieToResponse($request, $next($request)); } throw new TokenMismatchException; } |
注:tokensMatch方法首先从Request中获取_token参数值,如果请求中不包含该参数则获取X-CSRF-TOKEN请求头的值,如果该请求头也不存在则获取X-XSRF-TOKEN请求头的值,需要注意的是X-XSRF-TOKEN请求头的值需要调用Encrypter的decrypt方法进行解密。 from:http://laravelacademy.org/post/525.html
View Details1、路由命名——给路由起个名字 1.1 基本使用 我们使用as关键字来为路由命名:
1 2 3 |
<a title="View all posts in Route" href="http://laravelacademy.org/tags/route" target="_blank">Route</a>::get('/hello/laravelacademy',['as'=>'academy',function(){ return 'Hello <a title="View all posts in Laravel" href="http://laravelacademy.org/tags/laravel" target="_blank">Laravel</a>Academy!'; }]); |
路由命名可以让我们在使用route函数生成指向该路由的URL或者生成跳转到该路由的重定向链接时更加方便:
1 2 3 |
Route::get('/testNamedRoute',function(){ return route('academy'); }); |
我们在浏览器中访问http://laravel.app:8000/testNamedRoute时输出http://laravel.app:8000/hello/laravelacademy,然后我们修改上述闭包内代码:
1 2 3 |
Route::get('/testNamedRoute',function(){ return redirect()->route('academy'); }); |
再次在浏览器中访问http://laravel.app:8000/testNamedRoute时会跳转到http://laravel.app:8000/hello/laravelacademy。 我们甚至还可以在使用带参数的路由命名:
1 2 3 |
Route::get('/hello/laravelacademy/{id}',['as'=>'academy',function($id){ return 'Hello LaravelAcademy '.$id.'!'; }]); |
对应的测试路由定义如下:
1 2 3 |
Route::get('/testNamedRoute',function(){ return redirect()->route('academy',['id'=>1]); }); |
这样,当我们在浏览器中访问http://laravel.app:8000/testNamedRoute时会跳转到http://laravel.app:8000/hello/laravelacademy/1 1.2 路由分组时路由命名方式 再来看一个更复杂的例子,使用路由分组时如何定义路由命名?官网文档提供的例子如下:
1 2 3 4 5 |
Route::group(['as' => 'admin::'], function () { Route::get('dashboard', ['as' => 'dashboard', function () { // }]); }); |
在Route门面的group方法中使用一个as关键字来指定该路由群组中所有路由的公共前缀,然后再在里面每个路由中使用as关键字为该路由命名。 这样我们可以通过如下方式来生成该路由URL:
1 2 3 |
Route::get('/testNamedRoute',function(){ return route('admin::dashboard'); }); |
2、路由分组 路由分组就是将一组拥有相同属性(中间件、命名空间、子域名、路由前缀等)的路由使用Route门面的group方法聚合起来。 2.1 中间件 首先我们在应用根目录下运行如下Artisan命令生成一个测试用的中间件TestMiddleware:
1 |
php artisan make:middleware TestMiddleware |
这样会在/app/Http/Middleware目录下生成一个TestMiddleware.php文件,打开该文件编辑TestMiddleware类的handle方法如下:
1 2 3 4 5 6 |
public function handle($request, Closure $next) { if($request->input('age')<18) return redirect()->route('refuse'); return $next($request); } |
我们在中间件中定义这段业务逻辑的目的是年龄18岁以下的未成年人不能访问。 然后我们打开/app/Http/Kernal.php文件,新增TestMiddleware到Kernel的$routeMiddleware属性:
1 2 3 4 5 6 |
protected $routeMiddleware = [ 'auth' => \App\Http\Middleware\Authenticate::class, 'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class, 'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class, 'test' => \App\Http\Middleware\TestMiddleware::class, ]; |
接下来我们在routes.php中定义路由如下:
1 2 3 4 5 6 7 8 9 10 11 12 |
Route::group(['middleware'=>'test'],function(){ Route::get('/write/laravelacademy',function(){ //使用Test中间件 }); Route::get('/update/laravelacademy',function(){ //使用Test中间件 }); }); Route::get('/age/refuse',['as'=>'refuse',function(){ return "未成年人禁止入内!"; }]); |
这样当我们在浏览器中访问http://laravel.app:8000/write/laravelacademy?age=15或者http://laravel.app:8000/update/laravelacademy?age=15时就会跳转到http://laravel.app:8000/age/refuse,并显示:
1 |
未成年人禁止入内! |
2.2 命名空间 默认情况下,routes.php中的定义的控制器位于App\Http\Controllers命名空间下,所以如果要指定命名空间,只需指定App\Http\Controllers之后的部分即可:
1 2 3 4 5 6 7 8 |
Route::group(['namespace' => 'LaravelAcademy'], function(){ // 控制器在 "App\Http\Controllers\LaravelAcademy" 命名空间下 Route::group(['namespace' => 'DOCS'], function() { // 控制器在 "App\Http\Controllers\LaravelAcademy\DOCS" 命名空间下 }); }); |
2.3 子域名 子域名可以通过domain关键字来设置:
1 2 3 4 5 6 7 8 |
Route::group(['domain'=>'{service}.laravel.app'],function(){ Route::get('/write/laravelacademy',function($service){ return "Write FROM {$service}.laravel.app"; }); Route::get('/update/laravelacademy',function($service){ return "Update FROM {$service}.laravel.app"; }); }); |
这样我们在浏览器中访问http://write.laravel.app:8000/write/laravelacademy,则输出
1 |
Write FROM write.laravel.app |
访问http://update.laravel.app:8000/write/laravelacademy时,则输出:
1 |
Write FROM update.laravel.app |
注意:要想让子域名解析生效,需要在hosts中绑定IP地址 2.4 路由前缀 如果路由群组中的所有路由包含统一前缀,则我们可以通过在group方法中设置prefix属性来指定该前缀:
1 2 3 4 5 6 7 8 |
Route::group(['prefix'=>'laravelacademy'],function(){ Route::get('write',function(){ return "Write LaravelAcademy"; }); Route::get('update',function(){ return "Update LaravelAcademy"; }); }); |
这样我们就可以通过http://laravel.app:8000/laravelacademy/write或者http://laravel.app:8000/laravelacademy/update来访问对应的操作。 我们甚至还可以在路由前缀中指定参数:
1 2 3 4 5 6 7 8 |
Route::group(['prefix'=>'laravelacademy/{version}'],function(){ Route::get('write',function($version){ return "Write LaravelAcademy {$version}"; }); Route::get('update',function($version){ return "Update LaravelAcademy {$version}"; }); }); |
这样我们在浏览器中访问http://laravel.app:8000/laravelacademy/5.1/write,则对应会输出:
1 2 3 4 |
Write LaravelAcademy 5.1 from:<a href="http://laravelacademy.org/post/417.html">http://laravelacademy.org/post/417.html</a> |
1、路由基本使用示例 1.1 默认示例 Laravel中所有路由定义在/app/Http/routes.php文件中,该文件默认定义了应用的首页路由:
1 2 3 |
<a title="View all posts in Route" href="http://laravelacademy.org/tags/route" target="_blank">Route</a>::get('/', function () { return view('welcome'); }); |
这段代码的意思是:当访问应用首页http://laravel.app:8000(使用Homestead虚拟机作为开发环境)的时候,返回/resources/views/welcome.blade.php视图中的内容并渲染到浏览器页面中: 以上是应用自带的路由示例,下面我们来自定义一些示例来演示路由的基本使用。 1.2 GET请求路由定义 对页面常见的请求方式有GET和POST,上面这个例子就是使用GET路由的例子,接下里来我们自定义一个/hello请求:
1 2 3 |
Route::get('/hello',function(){ return "Hello Laravel[GET]!"; }); |
我们在浏览器中输入http://laravel.app:8000/hello,以上代码在浏览器中输出:
1 |
Hello Laravel[GET]! |
1.3 POST请求路由示例 然后我们来演示一个POST请求的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
Route::get('/testPost',function(){ $csrf_token = csrf_token(); $form = <<<FORM <form action="/hello" method="POST"> <input type="hidden" name="_token" value="{$csrf_token}"> <input type="submit" value="Test"/> </form> FORM; return $form; }); Route::post('/hello',function(){ return "Hello Laravel[POST]!"; }); |
首先我们定义一个/testPost页面用于提交POST请求表单,在http://laravel.app:8000/testPost页面点击“Test”按钮,页面跳转到http://laravel.app:8000/hello并显示:
1 |
Hello Laravel[POST]! |
表明这是通过POST请求访问而非GET请求。 1.4 其它便捷路由定义 还可以使用Route门面上的match方法匹配多种请求方式:
1 2 3 |
Route::match(['get','post'],'/hello',function(){ return "Hello Laravel!"; }); |
当然还使用更方便的any方法匹配所有请求方式:
1 2 3 |
Route::any('/hello',function(){ return "Hello Laravel!"; }); |
效果都一样。 2、路由参数使用示例 2.1 必选参数
1 2 3 |
Route::get('/hello/{name}',function($name){ return "Hello {$name}!"; }); |
在浏览器中访问http://laravel.app:8000/hello/Laravel输出:
1 |
Hello Laravel! |
当然还可以指定多个参数:
1 2 3 |
Route::get('/hello/{name}/by/{user}',function($name,$user){ return "Hello {$name} by {$user}!"; }); |
这样在浏览器中访问http://laravel.app:8000/hello/Laravel/by/Laravel学院则会输出:
1 |
Hello Laravel by Laravel学院! |
注意以上参数是必选的,如果没有输入参数会抛出MethodNotAllowedHttpException或NotFoundHttpException异常。 此外闭包函数中的参数与路由参数一一对应。 2.2 可选参数 有时候我们并不总是想要输入对应参数,也就是说,我们期望参数是可有可无的,我们通过这种方式来定义:
1 2 3 |
Route::get('/hello/{name?}',function($name="Laravel"){ return "Hello {$name}!"; }); |
我们同时为可选参数指定了默认值,这样当我们访问http://laravel.app:8000/hello时输出:
1 |
Hello Laravel! |
当我们访问http://laravel.app:8000/hello/Laravel学院的时候输出:
1 |
Hello Laravel学院! |
2.3 正则约束 有时候我们希望对路由有更加灵活的条件约束,可以通过正则表达式来实现:
1 2 3 |
Route::get('/hello/{name?}',function($name="Laravel"){ return "Hello {$name}!"; })->where('name','[A-Za-z]+'); |
该条件约束意味着$name参数只能包含大小写字母,如果包含数字或中文就会抛出NotFoundHttpException异常。 如果我们想要在全局范围内对参数进行条件约束,可以在RouteServiceProvider的boot方法中做如下定义:
1 2 3 4 5 |
public function boot(Router $router) { $router->pattern('name','[A-Za-z]+'); parent::boot($router); } |
我们访问http://laravel.app:8000/hello/Laravel123/by/Laravel学院时一样会抛出NotFoundHttpException异常。这意味着boot方法定义的参数条件约束将会应用到所有包含该参数的路由中。 此外,服务提供者的boot方法在所有服务提供者的register方法执行完毕后开始执行,也就是说,我们可以在boot方法对任意服务容器中的对象进行依赖注入。 from:http://laravelacademy.org/post/398.html
View Details