一、基础组件 —— 路由

---恢复内容开始---


路由实现逻辑在:
app/Providers/RouteServiceProvider

路由入门
最基本的 Laravel 路由只接收一个 URI 和一个闭包,并以此为基础提供一个非常简单优雅的路由定义方法:

Web 界面的路由 routes/web.php
定义了 Web 界面的路由,这些路由被分配到了 web 中间件组,从而可以使用 Session 和 CSRF 保护等功能。
例:
URL:/hello

Route::get('hello', function () {
    return 'Hello, World';
});

API的路由 routes/api.php
API路由是无状态的,这是因为被分配到了 api 中间件组。
API路由嵌套在一个路由群组中,所有路由会被自动添加 /api 前缀,所以你不需要再到路由文件中为每个路由手动添加,你可以通过编辑 RouteServiceProvider 类来修改路由前缀以及其他的路由群组选项:

   

有效的路由方法:

Route::get($uri, $callback);
Route::post($uri, $callback);
Route::put($uri, $callback);
Route::patch($uri, $callback);
Route::delete($uri, $callback);
Route::options($uri, $callback);

   注册一个路由响应多种 HTTP 请求动作 —— 可以通过 match 方法来实现。或者使用 any 方法注册一个路由来响应所有 HTTP 请求动作:

Route::match(['get', 'post'], 'foo', function () {
    return 'This is a request from get or post';
});

Route::any('bar', function () {
    return 'This is a request from any HTTP verb';
});

   如果上面的路由是定义在 routes/web.php 的话,在测试 POST 请求之前,需要将对应路由取消 CSRF 保护检查,否则会返回 419 状态码导致无法请求成功,取消的方法是在 app/Http/Middleware/VerifyCsrfToken 中设置排除检查路由:

   

 

路由重定向

   如果你需要定义一个重定向到其他 URI 的路由,可以使用 Route::redirect 方法,该方法非常方便,以至于你不需要再定义额外的路由或控制器来执行简单的重定向逻辑:

Route::redirect('/here', '/there');           #here 表示原路由,there 表示重定向之后的路由。
Route::redirect('/here', '/there',301);       #默认 Route::redirect 返回 302 状态码,使用可选的第三个参数定义状态码
Route::permanentRedirect('/here', '/there');  #或者使用 Route::permanentRedirect 方法来返回 301 状态码

 

路由视图

   如果你的路由需要返回一个视图,可以使用 Route::view 方法,和 Route::redirect 方法类似,这个方法很方便,以至于你不需要在额外定义一个路由或控制器。

  view 方法接收一个 URI 作为第一个参数,以及一个视图名称作为第二个参数,此外,你还可以提供一个数组数据传递到该视图方法作为可选的第三个参数,该数组数据可用于视图中的数据渲染:

Route::view('/welcome', 'welcome');
Route::view('/welcome', 'welcome', ['name' => '博客园']);

   

 

路由参数

   必选参数

     有时需要在路由中获取 URI 请求参数。

     例如,如果要从 URL 中获取用户ID,需要通过如下方式定义路由参数:

URL : user/5 
Route::get('user/{id}', function ($id) { return 'User ' . $id; });

     可以根据需要在路由中定义多个路由参数:

URL: /user/5/group/3
Route::get('user/{userid}/group/{groupid}', function ($userId, $groupId) { return 'userid:'.$userId.';groupid:'.$groupId.PHP_EOL; });

     路由参数名称不能包含 - 字符,如果需要的话可以使用 _ 替代,比如如果某个路由参数定义成 {user-id}则访问路由会报错,应该修改成 {user_id} 才行。

     路由参数被注入到路由回调/控制器取决于它们的顺序,与回调/控制器名称无关。

   可选参数

     可选参数可以通过在参数名后加一个 ? 标记来实现,这种情况下需要给相应的变量指定默认值,当对应的路由参数为空时,使用默认值:

Route::get('user/{name?}', function ($name = 'mrzhao') {
    return $name;
});
URL : /user/wang #wang
/user #mrzhao

   正则约束

     可以通过路由实例上的 where 方法来约束路由参数的格式。where 方法接收参数名和一个正则表达式来定义该参数如何被约束:

Route::get('user/{name}', function ($name) {
    // $name 必须是字母且不能为空
    return $name;
})->where('name', '[A-Za-z]+');
Route::get('user/{id}', function ($id) {
// $id 必须是数字
return $id;
})->where('id', '[0-9]+');

#多个参数
URL : user/123/wan
Route::get('user/{id}/{name}', function ($id, $name) {
// 同时指定 id 和 name 的数据格式
return 'id:'.$id.';name:'.$name.PHP_EOL;
})->where(['id' => '[0-9]+', 'name' => '[a-z]+']);

     使用正则约束还有一个好处就是避免了 user/{id} 和 user/{name} 的混淆。

   全局约束

   如果想要路由参数在全局范围内被给定正则表达式约束,可以使用 pattern 方法。需要在 RouteServiceProvider类的 boot 方法中定义这种约束模式:

/**
 * 定义路由模型绑定,模式过滤器等
 *
 * @param  \Illuminate\Routing\Router  $router
 * @return void
 * @translator  http://laravelacademy.org
 */
public function boot()
{
    Route::pattern('id', '[0-9]+');
    parent::boot();
}

     一旦模式被定义,将会自动应用到所有包含该参数名的路由中:

Route::get('user/{id}/{name}', function ($id, $name) {
   // 一旦模式被定义,将会自动应用到所有包含该参数名的路由中: 
// 如果还有其他参数,再继续指定 name 的数据格式 return 'id:'.$id.';name:'.$name.PHP_EOL; })->where('name' , '[a-z]+');

//除此之外,该模式还会被应用到诸如下面这些路由参数上:
Route::get('post/{id}', function ($id) {
// 只有当 {id} 是数字时才会被调用
return $id;
});

Route::get('product/{id}', function ($id) {
// 只有当 {id} 是数字时才会被调用
return $id;
});

     很显然这种方式让代码更简洁,也为我们实现同一参数统一约束带来了方便。

   编码/

     路由组件支持除 / 之外的所有字符,如果要在占位符中使用 / 需要通过 where 条件正则表达式显式允许:

Route::get('search/{search}', function ($search) {
    return $search;
})->where('search', '.*');

     注:只有最后一个路由参数片段中才支持编码正斜杠/

   命名路由

     命名路由为生成 URL 或重定向提供了方便,实现起来也很简单,在路由定义之后使用 name 方法链的方式来定义该路由的名称:

Route::get('user/profile', function () {
    // 通过路由名称生成 URL
    return 'my url: ' . route('profile');
})->name('profile');

     还可以为控制器动作指定路由名称:

Route::get('user/profile', 'UserController@showProfile')->name('profile');

     通过以下方式定义重定向:

Route::get('redirect', function() {
    // 通过路由名称进行重定向
    return redirect()->route('profile');
});

   为命名路由生成 URL

     如上面代码所展示的,为给定路由分配名称之后,就可以通过辅助函数 route 为该命名路由生成 URL 或者通过 redirect 函数进行重定向:

// 生成URL
$url = route('profile');

// 生成重定向
return redirect()->route('profile');

     如果命名路由定义了参数,可以将该参数作为第二个参数传递给 route 函数。给定的路由参数将会自动插入到 URL 中:

Route::get('user/{id}/profile', function ($id) {
    $url = route('profile', ['id' => $id]);
    return $url;
})->name('profile');

   检查当前路由

     想要判断当前请求是否被路由到给定命名路由,可以使用 Route 实例上的 named 方法,例如,你可以从路由中间件中检查当前路由名称:

/**
 * 处理输入请求
 *
 * @param  \Illuminate\Http\Request  $request
 * @param  \Closure  $next
 * @return mixed
 */
public function handle($request, Closure $next)
{
    if ($request->route()->named('profile')) {
        //
    }

    return $next($request);
}

路由分组

   路由分组的目的是让我们在多个路由中共享相同的路由属性,比如中间件和命名空间等,这样的话我们定义了大量的路由时就不必为每一个路由单独定义属性。共享属性以数组的形式作为第一个参数被传递给 Route::group 方法。

   嵌套的分组会尝试智能地将属性合并到父分组中,中间件和 where 条件会直接被合并,而路由命名、命名空间、以及路由前缀会被附加到父组件对应属性之后。命名空间分隔符和 URI 中的斜杠会被自动添加到合适的位置。

   中间件

     要给某个路由分组中定义的所有路由分配中间件,可以在定义分组之前使用 middleware 方法。中间件将会按照数组中定义的顺序依次执行:

Route::middleware(['first', 'second'])->group(function () {
    Route::get('/', function () {
        // Uses first & second Middleware
    });

    Route::get('user/profile', function () {
        // Uses first & second Middleware
    });
});

     关于中间件的使用在后面单独讲中间件时再进行示例演示,先了解这样使用就行。

   命名空间

     路由分组另一个通用的例子是使用 namespace 方法分配同一个 PHP 命名空间给该分组下的多个控制器:

Route::namespace('Admin')->group(function () {
    // Controllers Within The "App\Http\Controllers\Admin" Namespace
});

     默认情况下,RouteServiceProvider 在一个命名空间分组下引入所有路由文件,并指定所有控制器类所在的默认命名空间是 App\Http\Controllers,因此,我们在定义控制器的时候只需要指定命名空间 App\Http\Controllers之后的部分即可。

   子域名路由

     路由分组还可以被用于处理子域名路由,子域名可以像 URI 一样被分配给路由参数,从而允许捕获子域名的部分用于路由或者控制器,子域名可以在定义分组之前调用 domain 方法来指定:

#比如设置会员子域名为 account.blog.test,那么就可以通过 http://account.blog.test/user/1 访问用户ID为 1 的会员信息了
#This is account page of User 1
Route::domain('{account}.blog.dev')->group(function () {
    Route::get('user/{id}', function ($account, $id) {
        return 'This is ' . $account . ' page of User ' . $id;
    });
});

   路由前缀

     prefix 方法可以用来为分组中每个路由添加一个给定 URI 前缀,例如,你可以为分组中所有路由 URI 添加 admin前缀 :

URL : /admin/users
Route::prefix('admin')->group(function () { Route::get('users', function () { // Matches The "/admin/users" URL }); });

   路由名称前缀

     name 方法可通过传入字符串为分组中的每个路由名称设置前缀,例如,你可能想要在所有分组路由的名称前添加 admin 前缀,由于给定字符串和指定路由名称前缀字符串完全一样,所以需要在前缀字符串末尾后加上 . 字符:

Route::name('admin.')->group(function () {
    Route::get('users', function () {
        // 新的路由名称为 "admin.users"...
    })->name('users');
});

   路由模型绑定

     注入模型 ID 到路由或控制器动作时,通常需要查询数据库才能获取相应的模型数据。Laravel 路由模型绑定让注入模型实例到路由变得简单。

     例如,你可以将匹配给定 ID 的整个 User 类实例注入到路由中,而不只是注入用户 ID。

     隐式绑定

        Laravel 会自动解析定义在路由或控制器动作(变量名匹配路由片段)中的 Eloquent 模型类型声明,例如(我们将这个路由定义在 routes/api.php 文件中):

    Route::get('users/{user}', function (App\User $user) {
        dd($user);
//
return $user->email; });

        在这个例子中,由于类型声明了 Eloquent 模型 App\User,对应的变量名 $user 会匹配路由片段中的 {user},这样,Laravel 会自动注入与请求 URI 中传入的 ID 对应的用户模型实例。如果匹配模型实例在数据库中不存在,会自动生成 404 响应。

        在演示本功能之前,我们需要先创建数据表,由于我是在 Valet 开发环境中开发,需要自己创建数据库,我们将数据库命名为 testlv,本地的数据库用户名为 root,密码为123456,对应地,修改 .env 文件配置如下:

     DB_CONNECTION=mysql
     DB_HOST=127.0.0.1
     DB_PORT=3306
     DB_DATABASE=valet
     DB_USERNAME=root
     DB_PASSWORD=123456

         基于 Laravel 强大的数据库迁移功能创建 users 表,关于数据库迁移后面在数据库部分会详细讨论,这里我们通过以下命令来生成 users 表即可:

     php artisan migrate

         这时,users 数据表还没有任何记录,如果数据库中找不到对应的模型实例,会自动生成 HTTP 404 响应,提示页面不存在,所以我们需要在这张表中插入一条记录,这里我们基于 Laravel 强大的数据库填充器来快速完成数据填充功能,首先通过如下命令生成 users 对应的数据表填充器:

     php artisan migrate

         进入数据库可以看到该表已经生成:

          

      users 数据表还没有任何记录,如果数据库中找不到对应的模型实例,会自动生成 HTTP 404 响应,提示页面不存在,所以需要在这张表中插入一条记录,这里我们基于 Laravel 强大的数据库填充器来快速完成数据填充功能,首先通过如下命令生成 users 对应的数据表填充器:

     php artisan make:seeder UsersTableSeeder

           该命令会在 database/seeds 目录下生成一个 UsersTableSeeder 文件,编辑该文件内容如下:

           

          然后编辑同目录下的 DatabaseSeeder.php 文件如下(取消调用数据表填充器前的注释):

           

     php artisan db:seed
     php artisan db:seed  --class=UsersTableSeeder  #上面命令不管用 再试这条

            执行上面的命令即可插入对应数据到 users 表了,这样在浏览器中再次访问  api/users/1 的时候就会显示模型数据了:

            

           接下来,你就可以在应用代码中直接拿 $user 模型去做你想做的事情了,而不需要自己去数据库查询,从而提高了开发的效率。

    自定义键名

           如果你想要在隐式模型绑定中使用数据表的其它字段而不是 id 字段,可以重写 Eloquent 模型类的 getRouteKeyName 方法,以 User 模型为例,可以在该模型类中添加这个方法 :

      //如果是name 
//首先修改api.php
      $router->get('users/{name}', function(App\User $name) {
dd($name);
}); //其次修改app\User.php 模型文件
     /*
      * Get the route key for the model.
      *
      * @return string
*/ public function getRouteKeyName() { return 'name'; }

              这样我们就可以通过 users/lisi 访问同一个模型实例了。这里需要注意的点是如果该字段不是唯一键,则会返回结果集的第一条记录,对应的底层实现在这里:

              

     显式绑定

         有隐式绑定,就有显式绑定。要注册显式绑定,可以使用路由器的 model 方法来为给定参数指定绑定类。你需要在 RouteServiceProvider 类的 boot 方法中定义显式模型绑定:

    public function boot()
   {
       parent::boot();
       Route::model('user_model', \App\User::class);
   }

        接下来,在 routes/api.php 中定义一个包含 {user} 参数的路由:

    $router->get('profile/{user_model}', function(App\User $user) {
       dd($user);
    });

        由于已经绑定 {user_model} 参数到 App\User 模型,User 实例会被注入到该路由。因此,请求 URL 是 /api/profile/1,就会注入一个用户 ID 为 1 的 User 实例。

        如果匹配的模型实例在数据库不存在,会自动生成并返回 HTTP 404 响应。

     自定义解析逻辑

        如果你想要使用自定义的解析逻辑,可以在 RouteServiceProvider 类的 boot 方法中使用 Route::bind 方法,传递到 bind 方法的闭包会获取到 URI 请求参数中的值,并且返回你想要在该路由中注入的类实例:

    public function boot()
   {
      parent::boot();

      Route::bind('user', function($value) {
          return App\User::where('name', $value)->first() ?? abort(404);
      });
   }

        此外,你还可以在 Eloquent 模型中覆盖 resolveRouteBinding 方法,这个方法会获取 URI 片段中的值并返回应该被注入的路由模型类实例:

   /**
     * Retrieve the model for a bound value.
     *
     * @param  mixed  $value
     * @return \Illuminate\Database\Eloquent\Model|null
     */
    public function resolveRouteBinding($value)
   {
      return $this->where('name', $value)->first() ?? abort(404);
   }

     兜底路由

       使用 Route::fallback 方法可以定义一个当所有其他路由都未能匹配请求 URL 时所执行的路由。通常,未处理请求会通过 Laravel 的异常处理器自动渲染一个「404」页面,不过,如果你在 routes/web.php 文件中定义了 fallback 路由的话,所有 web 中间件组中的路由都会应用此路由作为兜底,当然,如果需要的话,你还可以添加额外的中间件到此路由:

   Route::fallback(function () {
       return '您所请求的页面不存在,请检查';
   });

      注:兜底路由应该总是放到应用注册的所有路由的最后。

   频率限制

     Laravel 自带了一个中间件用于限制对应用路由的访问频率。开始使用该功能之前,分配 throttle 中间件到某个路由或路由分组,throttle 中间件接收两个参数用于判断给定时间内(单位:分钟)的最大请求次数。例如,我们指定登录用户每分钟只能访问下面的分组路由 60 次:

 Route::middleware('auth:api', 'throttle:60,1')->group(function () {
    Route::get('/user', function () {
        //
    });
 });

     超出访问次数后,会返回 429 状态码并提示「Too many requests」。

     动态频率限制

       此外,还可以基于 User 模型的属性来动态设置最大请求次数。

       例如,如果 User 模型包含 rate_limit 属性,就可以将其这个属性名传递到 throttle 中间件,这样就可以将属性值作为计算最大请求次数的数据来源:

   Route::middleware('auth:api', 'throttle:rate_limit,1')->group(function () {
      Route::get('/user', function () {
         //
      });
   });

   表单方法伪造

     HTML 表单不支持 PUTPATCH 或者 DELETE 请求方法,因此,在 HTML 表单中调用 PUTPATCH 或 DELETE 路由时,需要添加一个隐藏的 _method 字段,其值被用作该表单的 HTTP 请求方法:

<form action="/foo/bar" method="POST">
    <input type="hidden" name="_method" value="PUT">
    <input type="hidden" name="_token" value="{{ csrf_token() }}">
</form>
#还可以直接使用 Blade 指令 @method 来生成 _method 字段:
<form action="/foo/bar" method="POST">
    @method('PUT')
    @csrf
</form>

   访问当前路由

     你可以使用 Route 门面上的 currentcurrentRouteName 和 currentRouteAction 方法来访问处理当前输入请求的路由信息:

 // 获取当前路由实例
 $route = Route::current(); 
 // 获取当前路由名称
 $name = Route::currentRouteName();
 // 获取当前路由action属性
 $action = Route::currentRouteAction();

 

 

 

 

 

 

 

 



















---恢复内容结束---

posted @ 2019-07-28 09:21  龙福  阅读(810)  评论(0编辑  收藏  举报