路由和控制器

路由和控制器

简介

任何Web应用框架必要的功能就是获取用户的请求并返回一个响应, 通常是经过HTTP(S)。也就意味着当学习一个Web框架的时候,定义一个应用的路由是第一位的,同时也是要去解决的最重要的部分,没有路由,你是无法和终端用户交互的。javascript

在本文,咱们会学习Laravel的路由,看看如何定义它们,如何把它们指到要执行代码的地方,怎么样使用Laravel的路由工具来处理不一样的路由。php

路由定义

在Laravel应用里,Web 相关的路由定义在 routes/web.php 里,API 相关的路由定义在 routes/api.php 里。Web 路由会被终端用户访问到,而 API 路由主要是应用与API,好比你有一个移动客户端。接下来的内容,咱们主要关注在web路由 routes/web.phphtml

若是Laravel版本早于5.3, 只有一个路由文件 app/Http/routes.php前端

定义一个路由最简单的方式就是匹配一个带有闭包的根路径(好比: /),好比java

  • 基本路由定义 *
// routes/web.php
Route::get('/', function() {
    return 'Hello, World!';
})

什么是闭包? 闭包是PHP的匿名函数版本。更详细的说闭包是一个能够做为对象传递,分配给变量,做为参数传递给其余函数和方法,甚至能够序列化。web

如今你已经定义了一个路由,若是有人访问 /, Laravel 路由会执行定义好的闭包并返回结果。 注意这里是 return 内容而不是 echoprintajax

快速了解中间件 你可能想要知道,为何是 return Hello World! 代替 echo。 这里也有很多答案,可是最简单的是 Laravel 的请求和响应周期有不少包装,包括中间件。当路由闭包或者控制器方法执行完的时候,尚未时间将输出发送到浏览器;返回内容容许它在返回给用户以前继续流过响应堆栈和中间件。正则表达式

许多简单的网站能够彻底定义在web路由文件里。一些简单的 GET 路由也能够和模板组合着用,你能够很容易的启动一个网站,好比shell

  • 简单网站 *
Route::get('/', function () {
    return view('welcome');
});

Route::get('about', function () {
    return view('about');
});

Route::get('products', function () {
    return view('products');
});

Route::get('services', function () {
    return view('services');
});

关于静态调用 若是你对PHP开发有一些经验,你看到对于路由类的静态调用可能有一些惊讶。实际上这不是一个静态方法,而是使用了Laravel的 facade, 后面会有文章专门来介绍。 若是你不喜欢facade, 你也能够完成相同的定义,如数据库

$router->get('/', function () {
   return 'Hello, World!';
});

路由动做

你可能注意到咱们已经在路由定义里使用过 Route::get。这意思就是咱们告诉 Laravel 当有一个HTTP请求使用 GET 时只能匹配到这条路由。那若是是form的 POST, 或者是JavaScript发送了 PUT 或者 DELETE 请求呢? 下面有一些在路由定义时能够用的其余选项。

  • 路由动做 *
Route::get('/', function(){
    return 'Hello, World!';
});

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

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

Route::delete('/', function () {});

Route::any('/', function () {});

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

路由处理

正如你可能已经猜到的那样,将闭包传递给路由定义并非教导它如何解决路由的惟一方法。闭包是快速且简单,可是应用程序越大,将全部路由逻辑放在一个文件中变得越笨拙。另外,使用路由闭包的应用也不能使用 Laravel 路由缓存(使用路由缓存能够为每一个请求减小几百毫秒)。

另外一个经常使用的选项是传递一个控制器和方法名做为一个字符串代替闭包,例如

Route::get('/', 'WelcomeController@index');

该命令是告诉 Laravel 把请求传给 App\Http\Controllers\WelcomeController 控制器的 index() 方法。并且也能够传递与闭包相同的参数。

路由参数

若是你定义的路由有参数,在路由中定义它们并把它们传递给闭包也是很是简单的。

  • 路由参数 *
Route::get('users/{id}/friends', function($id) {
    // your code
});

关于 路由参数和闭包/控制器里方法参数之间的命名关系 这里要多说一下,可能不少人对这里都会有些疑问。 如你看到上面这个路由参数的例子,它是很是常见的,用于路由参数({id})和注入到路由定义里(function($id))的方法参数使用相同的参数名。可是这有必要这么作么? 除非你使用路由/模型绑定, 不然是没有必要的。定义的路由参数匹配哪个方法参数惟一要注意的是他们的顺序(从左到右),看下面的例子

Route::get('users/{userId}/comments/{commentId}', function (
    $thisIsActuallyTheUserId,
    $thisisReallyTheCommentId
){
    //
});

我推荐它们保持一致,经过命名相同能够减小错误。

经过在参数名后面使用?也可让路由参数变为可选。看下面的代码,这个case中,给参数提供了一个默认值。

  • 可选的路由参数 *
Route::get('users/{id?}', function ($id = 'fallbackId') {
    //
});

你也可使用正则表达式来定义一个路由,该路由只有参数知足实际的要求才会匹配,如

  • 正则表达式路由 *
Route::get('users/{id}', function ($id) {
    //
})->where('id', '[0-9]+');

Route::get('users/{username}', function ($username) {
    //
})->where('username', '[A-Za-z]+');

Route::get('posts/{id}/{slug}', function ($id, $slug) {
    //
})->where(['id' => '[0-9]+', 'slug' => '[A-Za-z]+']);

若是你访问的路径匹配到了一个路由,可是正则表达式不匹配参数,那最终也不会匹配到。因为路由匹配是从上到下的,因此 /users/abc 会跳过第一个路由,可是会匹配到第二条闭包路由。另外一方面 posts/abc/123 不会匹配到任何一条路由,因此会返回 404 Not Found的错误。

路由名称

在应用中使用这些路由最简单的方式就是只使用他们的路径(path)。url() helper 能够简化视图里的连接。看下面的例子,helper 会给路由前面加上网站的完整域名。

  • URL helper *
<a href="<?php echo url('/'); ?>">
// outputs <a href="http://myapp.com/">

并且Laravel还容许你给每一个路由起一个名字,使你能够在不明确引用URL的状况下使用它。这颇有帮助,由于这意味着你能够给复杂的路由提供简单的昵称,而且由于按名称连接它们意味着若是路径更改,则没必要重写前端连接。

  • 定义路由名称 *
// Defining a route with name in routes/web.php:
Route::get('members/{id}', 'MembersController@show')->name('members.show');

// Link the route in a view using the route() helper
<a href="<?php echo route('members.show', ['id' => 14]); ?>">

这个例子也提到了几个新的概念。首先是能够经过在 get() 方法以后经过链式方法 name() 使用熟练的路由定义来添加名称。而后这个方法容许咱们指定一个简短的别名,来在其余地方更容易使用。

咱们命名路由为 members.show, 资源复数 在Laravel的路由和视图名也是经常使用的规范。

  • 关于路由命名规范 * 你能够命名路由按照你喜欢的方式,可是经常使用到的规范是使用资源名称复数,而后使用一个点 ., 而后是具体的动做。这里有一组给图片资源命名的最经常使用到的路由名称:
photos.index
    photos.create
    photos.store
    photos.show
    photos.edit
    photos.update
    photos.destroy

咱们也介绍过 route() helper, 就像 url() 同样,它的目的是在视图中使用,以简化与命名路由的连接。若是路由里没有参数,你能够简单的传一个路由名 route('members.index') ,转化后的结果为: http://myapp.com/members/index。若是有参数,以数组的形式传入第二个参数里。

通常状况下,我推荐使用路由昵称来代替使用路由路径,所以使用 route() 代替 url()

  • 关于传递路由参数到route() *

当路由有参数的时候(好比: users/{id}), 在使用 route() 生成连接给路由的时须要定义这些参数。

传递参数有几种不一样的方式,设想一个路由是 users/{userId}/comments/{commentId}。 若是用户ID是1,评论ID是2,来看下咱们可使用的几种方式:

方式1:

route('users.comments.show', [1, 2])
// http://myapp.com/users/1/comments/2

方式2:

route('users.comments.show', ['userId' => 1, 'commentId' => 2])
// http://myapp.com/users/1/comments/2

方式3:

route('users.comments.show', ['commentId' => 2, 'userId' => 1])
// http://myapp.com/users/1/comments/2

方式4:

route('users.comments.show', ['userId' => 1, 'commentId' => 2, 'opt' => 'a'])
// http://myapp.com/users/1/comments/2?opt=a

如你所见,没有key的数组是须要按顺序指定的,带有key的数组经过和路由里的key进行匹配,剩下的会被做为查询参数。

路由组

一般,一组路由共享一个特定的特征 - 特定的身份验证要求,路径前缀或控制器命名空间。在每一条路由里一遍又一遍的定义这些共享的特征不只看起来乏味,并且可能会玷污你的路由文件,而且掩盖应用程序的某些结构。

路由组容许将几个路由组合到一块儿,应用共享的配置到整个路由组一次,减小重复。另外,路由组也是给将来开发者的一个能够看的见的线索。

要讲两个或更多个路由组合到一块儿,你要围绕一组路由。以下

定义一组路由

Route::group([], function(){
    Route::get('hello', function() {
        return 'Hello';
    });

    Route::get('world', function() {
        return 'world';
    })
});

默认状况下,路由组实际上不会作任何事情。上面的代码和以前的路由基本没啥区别。其中第一个参数是一个空数组,容许传入多重配置参数去应用到整个路由组。

中间件

可能对路由组最经常使用的就是应用一个中间件到一组路由组。后面会专门写一片关于中间件的文章。除此以外,它们是Laravel用于验证用户身份并限制访客使用网站的某些部分的内容。

下面的例子,是咱们建立了一个路由组,包含 dashboardaccount 视图,应用 auth 中间件到这两个路由。在这个例子中也就意味着用户必须登陆到应用里来看 dashboardaccount 页。

只有登陆用户能够访问的路由组

Route::group(['middleware' => 'auth'], function(){
    Route::get('dashboard', function(){
        return view('dashboard');
    });

    Route::get('account', function(){
        return view('account');
    })
});

关于在控制器中使用中间件

常常在控制器中使用路由中间件比在路由定义里可能更清晰,更直接。你能够在控制器的构造方法里使用 middleware() 方法来作到。给 middleware() 传递的参数就是中间件的名称。你也能够链式调用一些方法(好比: only(), except()) 来定义哪些方法将接受该中间件。

class DashboardController extends Controller {
    public function __construct()
    {
       $this->middleware('auth');

       $this->middleware('admin-auth')
            ->only('admin');

        $this->middleware('team-member')
           ->except('admin');
} }

请注意,若是你大量的使用 onlyexcept, 之后会很难维护,因此不建议大面积这么使用。

路径前缀

若是你有一组路由要共享一个path, 好比你网站的API是以 /api 开头的,那就可使用路由组来指定这个结构,如

前缀路由组

Route::group(['prefix' =>'api'], function(){
    Route::get('/', function(){
        // handle the path /api
    });

    Route::get('users', function(){
        // handle the path /api/users
    });
});

注意每个前缀路由组都会有一个 / 路由来表示根前缀,在这里就是 /api

子域名路由

子域名路由和路由前缀同样,可是它的做用域是子域名而不是路由前缀。有两种主要的使用方法。

第一种,你可让整个应用分为不一样的几个部分(或不一样的应用)到不一样的子域名,来看下怎么实现

子域名路由

Route::group(['domain' => 'api.myapp.com'], function(){
    Route::get('/', function(){
        //
    });
});

第二种,你也能够将子域名的一部分做为参数,最多见的例子就是slack,好比每个公司都有本身的子域名 phpcasts.slack.co

参数化的子域名

Route::get(['domain' => '{account}.myapp.com'], function(){
    Route::get('/', function($account) {
        //
    });

    Route::get('users/{id}', function($account, $Id){
        //
    });
});

注意,路由组里的任何一个参数均可以传入到组里的路由方法里做为第一个参数,若是是多个就依次写。

命名空间前缀

当你按子域名或路由前缀对路由进行分组时,极可能他们的控制器有一个相似PHP命名空间。在API的例子里,全部的API路由控制器可能都在 API 命名空间。 经过使用路由组命名空间前缀,能够避免在诸如 API/ControllerA@indexAPI/ControllerB@index 之类的组中使用较长的控制器引用。

路由组命名空间前缀

// App\Http\Controllers\ControllerA
Route::get('/', 'ControllerA@index');

Route::group(['namespace' => 'API'], function () {
    // App\Http\Controllers\API\ControllerB
    Route::get('api/', 'ControllerB@index');
});

名称前缀

前缀并不止于此。一般路由名称会反映路径元素的继承链,所以 users/comments/5 将由名为 users.comments.show 的路由提供服务。 在这种状况下, 一般在 users.comments 资源下的全部路由上层使用路由组。

路由名称前缀

Route::group(['as' => 'users.', 'prefix' => 'users'], function () { 
    Route::group(['as' => 'comments.', 'prefix' => 'comments'], function () {
        // Route name will be users.comments.show
        Route::get('{id}', function () {
            //
        })->name('show');
    });
});

视图

这部分先简单了解下就能够了,后面会有专门一文来讲模板视图。

目前为止咱们看到的路由闭包里,使用过一行 view('account')。这里是发生了什么呢?

若是你不熟悉 Model-View-Controlelr(MVC)模式, views (或者是模板) 是描述一些实际输出的文件。你也可能用于 JSON 或者 XML 或者 邮件 的视图,可是在Web框架里最经常使用的视图是输出 HTML。

在Laravel, 有两种能够当即使用的视图格式:plain phpBlade 模板。不一样的是在于文件名:about.php 会被PHP引擎来渲染, about.blade.php 则会被 Blade 引擎渲染。

关于加载模板视图的三种方式

有三种不一样的方式来返回视图。到目前,只是控制器本身使用过 view(), 但你曾可能看到过 View::make() 的话,它和 view() 作的事是同样的,还可使用注入的方式 Illuminate\View\ViewFactory

view()的简单使用

Route::get('/', function () {
    return view('home');
});

这段代码将会寻找 resources/views/home.blade.phpresources/views/home.php 视图文件,并加载里面的内容,解析PHP代码 或 控制结构语句知道视图输出。一旦返回以后,被专递到响应栈的剩余部分,最终返回给用户。

可是若是你须要传递变量呢? 来看下面的例子

传递变量到模板

Route::get('tasks', function(){
    return view('tasks.index')
        ->with('tasks', Task::all());
});

使用视图组合 与每个视图共享变量

有时候一遍又一遍的传递一样的变量到模板会感受很麻烦。这个变量多是网站的每一个视图都要访问的,或者是某一类视图,再或者是一些子视图,好比 全部的视图都会用到的 header 。

视图共享变量

view()->share('variableName', 'variableValue');

控制器

我已经好几回提到过控制器,但直到如今大多数的例子中只展现了路由闭包。若是不熟悉 MVC 模式(看下图),控制器本质上是一个类,它将一条或多条路由的逻辑组织在一块儿。控制器倾向与将相似的路由组合在一块儿,特别是若是你的应用是按照传统的相似CRUD的格式构建的;在这种状况下,控制器可能会处理能够在特定资源上执行的全部操做。

basic-of-mvc

关于什么是CRUD

CRUD 表明建立、读取、更新、删除,这是Web应用程序最常提供的四项主要操做。例如,你能够建立新的博客文章,能够阅读该文章,能够更新它,也能够将其删除。

将全部应用程序的逻辑嵌入到控制其中可能很诱人,但最好将控制器看作是将HTTP请求路由到应用程序的交通警察。因为还有其余的方式能够将请求加入到你的应用程序中,例如 cron 做业,Artisan 命令行调用,队列做业等等,因此不要依赖控制器来处理太多行为。这意味着控制器的主要工做是捕获HTTP请求的意图并将其传递给应用程序的其他部分。

来建立一个控制器,最简单的方式就是经过 Artisan 命令,在命令里运行以下命令

php srtisan make:controller TasksController

关于Artisan和Artisan生成器 Laravel 自带了一个名为Artisan的命令行工具。Artisan 可用于运行迁移,手动建立用户和其余数据库记录,一次性任务等等。 在 make 命令下,Artisan提供了用于为各类系统文件生成骨架文件的工具。 后面会专门文章来介绍 Artisan。

这条命令会在 app/Http/Controllers 目录下建立一个名为 TasksController.php 的新文件,同时包含一下内容

默认生成的控制器

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Http\Requests;

class TasksController extends Controller
{
}

修改这个文件而后增长一个新的方法 home(), 返回一些简单的文本

简单的控制器

<?php

use App\Http\Controllers\Controller;

class TasksController extends Controller
{
    public function home()
    {
        return 'Hello, World!';
    }
}

这个以前有学过,来看下如何把一个路由绑定到这个控制器上。

路由和控制器绑定

<?php

Route::get('/', 'TasksController@home');

这样就能够经过访问 / 路由看到返回的内容了 Hello, World!

关于控制器的命名空间 在上面的例子中看到,咱们引用了一个彻底限定类名的控制器 App\Http\Controllers\TasksController, 但只使用了类名。 这不是由于咱们简单地经过它们的类名来引用控制器,相反,当咱们引用控制器时,咱们忽略了 App\Http\Controllers\; 默认状况 下,Laravel 配置为在该命名空间下查找控制器。

控制器方法最经常使用的用法以下

经常使用的控制器方法

// TasksController.php
...
public function index()
{
    return view('tasks.index')
        ->with('tasks', Task::all());
}

这个控制器方法加载了 resources/views/tasks/index.blade.php or resources/views/tasks/index.php 视图,并传递了一个名为 tasks 的变量,其值是一个 Task::all() 的查询结果。

生成资源控制器 若是你曾在Laravel 5.3以前使用过 php artisan make:controller,你可能指望它来自动生成全部基本的资源路由如 create()update()。在5.3以后须要使用 --resource 参数来生成,如 php artisan make:controller TasksController --resource

获取用户输入

在控制器方法里第二个最经常使用的动做就是获取用户输入的参数并使用它。这里引入了一些新的概念,来看下代码

基本的动做绑定

// routes/web.php
Route::get('tasks/create', 'TasksController@create');
Route::post('tasks', 'TasksController@store');

注意这里咱们绑定了 GET 动做到 tasks/create (显示表单), POST 动做绑定到了 tasks/ (当建立任务的时候POST数据)。咱们假设 create() 方法只显示表一个form表单,因此来看下 store() 方法。

通用表单输入控制器的方法

// TasksController.php
...
public function store()
{
    $task = new Task;
    $task->title = Input::get('title'); 
    $task->description = Input::get('description');
    $task->save();

    return redirect('tasks'); }

这个例子中使用了 Eloquent model 和 redirect() 函数,后面会详细介绍,并且你也能够看到它实际上作了什么。咱们建立了一个新 Task, 获取用户输入的数据,设置 task model, 保存,最后跳转会任务列表页。

有两种方式能够获取用户输入的数据: facade 方式和 Request 对象(这个后面新开文章来写)。

关于导入facade 运行上面的代码你可能发现会报 Input facade找不到,这是由于它们(facade)不会出如今每个 namespace 下。但在根命名空间下是有效的。因此咱们须要在文件顶部导入 Input facade。有两种方式导入: \InputIlluminate\Support\Facades\Input。如

<?php
namespace App\Http\Controllers;

use Illuminate\Support\facades\Input;
// or \Input

class TasksController
{
    public function store()
    {
        $task = new Task;
        $task->title = Input::get('title');
        $task->description = Input::get('description');
        $task->save();

        return redirect('tasks');
    }
}

依赖注入到控制器

Laravel的 facade 为Laravel的代码库中最有用的类提供了一个简单的接口。经过使用简单的 facade 能够获取关于当前请求,用户输入,session, cache 的信息等。

但若是你更喜欢注入依赖或者使用一个service, 你须要找到一些传入这些类实例到控制的方法。

这也是咱们要将的,关于Laravel的服务容器(service container)。到目前,若是不熟悉这点,你能够认为这是Laravel的一点小魔法,关于容器更多的内容后面章节会有讲解。

全部控制器方法(包括构造行数)都从 Laravel 的容器中解析出来, 这意味着你键入的任何内容容器都知道如何解决都会被自动注入。 下面有一个很好的例子,若是你但愿有一个 Request 对象的实例, 而不是使用 facade, 那该怎么办? 能够仅仅键入 Illuminate\Http\Request 在方法参数里,如

控制器方法注入

// TasksController.php
...
public function store(\Illuminate\Http\Request $request)
{
    $task = new Task;
    $task->title = $request->input('title');
    $task->description = $request->input('description');
    $task->save();

    return redirect('tasks');
}

顺便说一句,这其实是我和其余许多Laravel开发人员更喜欢获取用户输入的方式:注入 Request 的实例并从那里读取用户输入,而不是依赖 Input facade。

资源控制器

有时在控制器里命名一个方法名多是写一个控制器很是艰难的一部分。 值得庆幸的是,Laravel 对传统 REST/CRUD 控制器(Laravel中称为资源控制器)的全部路由都有一些约定;此外,它附带了一个开箱即用的生成器和一个便捷的路由定义,可以让你一次绑定整个资源控制器。

想要看Laravle资源控制器提供的方法,咱们能够经过命令行来生成一个

php artisan make:controller MySampleResourseController --resource

如今打开 app\Http\Controllers\MySampleResourceController.php 。你就能够看到增长一些方法。让咱们来看下每个表明什么,经过 Task 例子来理解。

Laravel资源控制器的方法

经过这个表格来看下

|动做|URL|控制器方法名|路由名|描述| |:--|:--|:--|:--|:--| |GET|tasks|index()|tasks.index| 展现全部的task| |GET|tasks/create|create()|tasks.create|展现建立task的表单form| |POST|tasks|store()|tasks.store|接收来自建立task表单的提交| |GET|tasks{/task}|show()|tasks.show|展现一条task| |GET|tasks/{task}/edit|edit()|tasks.edit|编辑一条task| |PUT/PATCH|tasks/{task}|update()|tasks.update|接受来自编辑表单的提交| |DELETE|tasks/{task}|destory()|tasks.destory|删除一条task|

能够看到每个HTTP动做都有一个 URL, 控制器方法名,路由名称和描述。

绑定一个资源控制器

如咱们看到的,这些是在 Laravel中使用的 传统路由名称,而且很容易为每一个这些默认路由生成一个资源控制器和方法。幸运的是,你没必要手动为每个控制器方法生成路由。代替的是,这里有一个叫作资源绑定的动动。

资源控制器绑定

// routes/web.php
Route::resource('tasks', 'TasksController');

该命令会自动绑定全部路由到指定控制器上适合的方法名。它也会适当的命名这些路由;例如 tasks 资源控制器里的 index() 方法将会被命名为 tasks.index

路由模型绑定

最经常使用的路由模式之一就是在任何控制器方法的第一行就去经过给定的ID查找资源,好比

获取资源

Route::get('conferences/{id}', function($id) {
    $conference = Conference::findOrFail($id);
});

Laravel提供了一种简化称为“路由模型绑定”的模式的功能。

有两种路由模型绑定:隐式和自定义(或显式)

隐式的路由模型绑定

使用路由模型绑定最简单的方式是为该模型指定惟一的路由参数(例如:将其命名为 $conference 而不是 $id), 而后在闭包/控制器的方法里键入这个参数,并使用同名的变量就能够了。如

使用隐式的路由模型绑定

Route::get('conferences/{conference}', function (Conference $conference) { 
    return view('conferences.show')->with('conference', $conference);
});

因为路由参数({conference})和方法参数名($conference)同样, 方法参数键入时带有 Conference model(Conference $conference), Laravel看到这个会做为一个路由模型绑定。每次这个路由被访问到的时候,应用都会假设不管传入什么到URL,都会用 ID来替代 {conference},而后经过ID去到对应的 Conference model里查找。Conference实例都会被转入闭包或者控制器方法里。

隐式路由模型绑定是在 Laravel 5.2版本中加入的,因此在 5.1 版本里是无法是无法使用的。

自定义路由模型绑定

要手动的配置路由模型绑定,能够添加 像下面的一行到文件 App\Providers\RouteServiceProviderboot() 方法里。

添加路由模型绑定

public function boot(Router $router)
{
    parent::boot($router);

    // Perform the binding
    $router->model('event', Conference::class);
}

如今你已经定义了每当路由的定义中有一个名为 {event} 的参数时,路由解析器将返回具备该URL参数的ID的Conference类实例。

显式路由模型绑定

Route::get('events/{event}', function (Conference $event) { 
    return view('events.show')->with('event', $event);
});

路由缓存

若是你但愿缩短加载时间,那可能须要看下这个路由缓存。Laravel 的启动过程之一是要解析 routes/*, 可能会花费几十毫秒到几百毫秒,路由缓存能够明显加速这一过程。

要想缓存路由文件,你须要使用所有控制器或者资源控制器(而不是路由闭包)。若是你的应用没有使用任何的路由闭包,能够运行 php artisan route:cache,Laravel会序列化路由文件 routes/* 结果。若是想要删除缓存,能够运行 php artisan route:clear

固然路由缓存也有缺点。当有缓存文件存在的时候,Laravel 只会匹配缓存的路由文件而不是实际的 routes/* 文件。你不管怎么修改路由文件,都不会生效,直到你再次运行 php artisan route:cache。这意味着每次进行更改时都必须从新缓存,这引发了很大的混淆。

这里我会推荐这么作:由于不管如何Git默认忽略路由缓存文件,因此只考虑在在生成环境服务器上使用路由缓存,而且每次部署新代码时运行 php artisan route:cache 命令(不管是使用 Git post-hook, 仍是Forge部署),或做为你使用的任何其余部署系统的一部分。这样你就不会混淆本地的开发问题,可是你的远程环境任然受益于路由缓存。

Form方法欺骗

有时,你须要手动定义一个form应该哪一个HTTP动词,HTML表单仅支持 GETPOST,因此若是想要其余的动词类型,须要本身来指定。

HTTP 动词介绍

咱们已经讨论了GET和POST HTTP动词。若是对HTTP不熟悉,这里简单提一下。另外两个最多见的是 PUTDELETE,但还有 HEAD,OPTIONS,PATCH 以及其余两种在正常Web开发中不多使用到的 TRACECONNECT

快速了解下这几个动词: GET 请求一个资源,HEAD 只请求 GET 中携带的header部分, POST 建立一个资源,PUT 重写一个资源,PATCH 修改一个资源, DELETE 删除一个资源,OPTIONS 是获取这个URL哪些动词是被容许使用的。

Laravel 中的 HTTP 动词

如咱们看到的,你能够定义一个路由使用哪一个动词来匹配路由定义中的 Route::get(),Route::post(),Route::any(), 或 Route::match()。也能够匹配 Route::patch(), Route::put()Route::delete()

但如何用浏览器发送一个除 GET 以外的请求呢? 首先,HTML表单中的 method 属性决定了用哪一个HTTP动词。若是表单有 GET method,则经过查询参数和 GET method 来提交,若是表单是 POST 则会经过 POST method 和 post body 来提交。

JavaScript 框架发送其余的请求很容易,像 DELETE PATCH。但你会发如今 Laravel 中须要提交一个HTML表单,除了 GET,POST, 须要使用表单方法欺骗,也就是在HTML表单中欺骗HTTP method 属性。

HTML表单中的 HTTP 方法欺骗

要告诉Laravel你当前提交的表单应该被视为POST以外的其余内容,添加一个隐藏的变量名 _method,其值能够是 PUT,PATCH,DELETE,Laravel 将匹配和路由该表单提交,就好像它实际上对该动词的请求同样。

看下面这个例子,由于它经过Laravel传递 DELETE 方法,将匹配 Route::delete() 定义的路由,但不会匹配 Route::post() 路由。

表单方法欺骗

<form action="/tasks/1" method="POST">
    <input type="hidden" name="_method" value="DELETE">
</form>

CSRF 保护

若是你在Laravel应用中已经试着建立和提交一个表单,你可能会碰到这个异常 TokenMismatchException

默认状况下,除了只读路由(使用GETHEADOPTIONS 的路由)以外,Laravel中的全部路由都受到跨域请求伪造(CSRF)攻击的保护,它须要一个以名为 _token 的输入形式的令牌, 与每一个请求一块儿传递。该 token 会在每一个会话(session)开始时生成,而且每一个非只读路由都会将提交的 _token 与会话令牌进行比较。

什么是CSRF 跨网站请求伪造是指一个网站假装成另外一个网站。 目标是经过登陆用户的浏览器从他们的网站提交表单到您的网站,以便有人劫持您的用户访问您的网站。 CSRF攻击的最佳方式是使用 Laravel 开箱即用的令牌来保护全部入站路由(POST,DELETE等)。

你有两个选择来解决这个问题。首先,首选的方法是将_token输入添加到每一个提交中。 在HTML表单中,这很简单,如

CSRF tokens

<form action="/tasks/5" method="POST">
    <?php echo csrf_field(); ?>
    <!-- or: -->
    <input type="hidden" name="_token" value="<?php echo csrf_token(); ?>">
</form>

在JavaScript应用程序中,可能有点多,但还好。 使用JavaScript框架的网站最多见的解决方案是将标记存储在每一个页面的 <meta> 标记中,以下所示:

<meta name="csrf-token" content="<?php echo csrf_token(); ?>" id="token">

将标记存储在 <meta> 标记中能够很容易地将其绑定到正确的HTTP header,对于来自JavaScript框架的全部请求,你能够全局执行一次,以下所示。

把CSRF全局绑定到header

// in jQuery:
$.ajaxSetup({
    headers: {
        'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
    }
});

// in Vue:
Vue.http.interceptors.push((request, next) => {
    request.headers['X-CSRF-TOKEN'] =
        document.querySelector('#token').getAttribute('content');

    next();
});

Laravel将根据每一个请求检查 X-CSRF-TOKEN,而且经过的有效令牌将标记CSRF保护为满意。 请注意,若是你使用5.3 Vue引导程序,则此示例中的CSRF的Vue语法不是必需的; 它已经为你作了这项工做。

跳转

到目前为止,咱们从控制器方法或路由定义返回的惟一东西是视图。 可是咱们能够返回一些其余结构,以便浏览器指导如何操做。 首先,咱们介绍重定向。 有两种常见的方法来生成重定向; 咱们将在这里使用重定向全局helper,但你可能更喜欢facade。 二者都建立一个Illuminate\Http\RedirectResponse 的实例,对其执行一些便利方法,而后返回它。 你也能够手动作到这一点,但你必须本身作更多的工做。 看下面的例子,看看几种能够返回重定向的方法。

返回重定向的几种方式

// 使用全局helper生成 redirect response
Route::get('redirect-with-helper', function () { 
    return redirect()->to('login');
});

// 使用更简短的全局helper
Route::get('redirect-with-helper-shortcut', function () { 
    return redirect('login');
});

// 使用facade 生成 redirect response
Route::get('redirect-with-facade', function () { 
    return Redirect::to('login');
});

请注意,redirect() helper 与 Redirect facade 是同样的,但它也有一个快捷方式; 若是你将参数直接传递给 helper,而不是在它后面链式方法,则它是 to() 重定向方法的快捷方式。

redirect()->to()

先来看下参数说明

function to($to = null, $status = 302, $headers = [], $secure = null)
  • $to 有效的内部路径
  • $status HTTP状czzZ态码(默认是 302 FOUND)
  • $headers 跳转时携带的HTTP header
  • $secure 是 http仍是https

redirect()->to()

Route::get('redirect', function () {
     return redirect()->to('home');

    // 简短用法
    return redirect('home');
});

redirect()->route()

route() 方法和 to() 方法相同,但不指向特定路径,而是指向特定的路由名称。

redirect()->route()

Route::get('redirect', function () {
    return redirect()->route('conferences.index');
});

请注意,因为某些路由名称须要参数,所以其参数顺序稍有不一样。route() 有第二个可选的参数做为路由参数。

function route($to = null, $parameters = [], $status = 302, $headers = [])

带参数的redirect()->route()

Route::get('redirect', function () {
    return redirect()->route('conferences.show', ['conference' => 99]);
});

redirect()->back()

因为Laravel会话实现的一些内置便利性,你的应用程序将始终知道用户之前访问的页面是什么。 这为redirect()->back() 重定向提供了机会,它将用户简单地重定向到它来自的任何页面。 还有一个全局可访问的函数:back()

其余重定向的一些方法

重定向服务提供了其余不太经常使用的方法,这里也说明下做用:

  • home() 重定向到一个名为 home 的路由。
  • refresh() 重定向到用户当前所在的同一页面。
  • away() 容许在没有默认网址验证的状况下重定向到外部网址。
  • secure() 相似于 to(),只是将安全参数设置为 true
  • action() 容许连接到控制器和方法,如 redirect()->action('MyController@myMethod')
  • guest() 验证用户是否登陆

redirect()->with()

当你将用户重定向到不一样的页面时,你常常但愿将某些数据与他们一块儿传递。 能够手动将数据闪存到会话中,但 Laravel 有一些便利方法能够帮助你解决这个问题。

大多数状况下,你能够传递键值对的数组,或者使用 with() 来传递一个键和值,如

带有数据重定向

// 单个的键和值
Route::get('redirect-with-key-value', function () { 
    return redirect('dashboard')
        ->with('error', true);
});

// 数组
Route::get('redirect-with-array', function () { 
    return redirect('dashboard')
        ->with(['error' => true, 'message' => 'Whoops!']);
});

你还可使用 withInput() 来重定向用户的表单输入; 在验证错误的状况下,这是最多见的状况,你但愿将用户发回到刚刚来自的表单。

重定向与表单输入

Route::get('form', function () { 
    return view('form');
});

Route::post('form', function () { 
    return redirect('form')
        ->withInput()
        ->with(['error' => true, 'message' => 'Whoops!']);
});

获取使用 withInput() 传递的闪存输入的最简单方法是使用 old() 助手,该助手可用于获取全部旧输入(old())或仅用于特定键的值( old('username'),第二个参数做为默认值,若是没有旧值)。 你一般会在视图中看到这个,它容许在这个表单的 create 和 edit 视图中使用这个HTML:

<input name="username" value="<?=old('username', 'Default username instructions here');?>">

说到验证,还有一个有用的方法来将错误与重定向响应一块儿传递:withErrors()。 你能够传递任何错误的 "提供者",这多是一个错误字符串,一个错误数组,或者最多见的是一个 Illuminate Validator的实例,来看例子。

Route::post('form', function () {
    $validator = Validator::make($request->all()), $this->validationRules);

    if ($validator->fails()) { 
        return redirect('form')
            ->withErrors($validator)
            ->withInput();
    } 
});

withErrors() 会自动共享一个 $errors 变量,并将其重定向到的页面的视图分享,让你根据本身的喜爱进行处理。

终止请求

除了返回视图和重定向以外,退出路由的最多见方式是停止。 有几个全局可用的方法 (abort()abort_if()abort_unless()),它们将HTTP状态代码,消息和头数组做为可选参数。 下面的例子中,abort_if()abort_unless() 取第一个参数,它的真实性获得评估,并根据结果执行取消操做。

Route::post('something-you-cant-do', function (Illuminate\Http\Request) { 
    abort(403, 'You cannot do that!'); 
    abort_unless($request->has('magicToken'), 403); 
    abort_if($request->user()->isBanned, 403);
});

自定义响应

还有其余一些选项可供咱们返回,所以让咱们在查看,重定向和停止以后回顾最多见的响应。 就像重定向同样,你可使用 response() helper 或 Response facade 来运行这些方法。

response()->make()

若是你想手动建立HTTP响应,只需将你的数据传递给 response()->make() 的第一个参数:例如,return response()->make('Hello,World!')。 第二个参数是HTTP状态码,第三个参数是header。

response()->json() 和 ->jsonp()

要手动建立JSON编码的HTTP响应,能够将你的支持JSON的内容(数组,集合或任何其余)传递给 json() 方法:例如,return response()->json(User::all());。 它就像 make(),除了 json_encode 你的内容并设置适当的header。

response()->download() 和 ->file()

要发送文件供最终用户下载,请将 SplFileInfo 实例或字符串文件名传递给 download(),并使用文件名的可选第二个参数:例如,return response()->download('file20180405.pdf','myFile.pdf')。 要在浏览器中显示相同的文件(若是它是PDF或图像或浏览器能够处理的其余内容),请使用 response()->file(),它使用与 download() 相同的参数。

总结

Laravel的路由在 routes/web.phproutes/api.php 中定义,你能够在其中定义每一个路由的预期路径,哪些段是静态的,哪些是参数,HTTP动词能够访问路由,以及如何解决。 你还能够将中间件附加到路由,对它们进行分组,并给它们命名。 从路由闭包或控制器方法返回的内容指示Laravel如何响应用户。 若是它是一个字符串或视图,它会呈现给用户; 若是是其余类型的数据,则将其转换为JSON并呈现给用户; 若是它是重定向,它会强制重定向。 Laravel提供了一系列工具和便利来简化常见的路由相关任务和结构。 这些包括资源控制器,路由模型绑定和表单方法欺骗。

相关文章
相关标签/搜索