laravel进阶预备
Route的正则约束
可以通过路由实例上的 where 方法来约束路由参数的格式。where 方法接收参数名和一个正则表达式来定义该参数如何被约束:
Route::get('user/{name}', function ($name) {
// name 必须是字母且不能为空
})->where('name', '[A-Za-z]+');
Route::get('user/{id}', function ($id) {
// id 必须是数字
})->where('id', '[0-9]+');
Route::get('user/{id}/{name}', function ($id, $name) {
// 同时指定 id 和 name 的数据格式
})->where(['id' => '[0-9]+', 'name' => '[a-z]+']);
路由缓存
注:路由缓存不会作用于基于闭包的路由。要使用路由缓存,必须将闭包路由转化为控制器路由。
如果你的应用完全基于控制器路由,可以使用 Laravel 的路由缓存,使用路由缓存将会极大降低注册所有应用路由所花费的时间开销,在某些案例中,路由注册速度甚至能提高100倍!想要生成路由缓存,只需执行 Artisan 命令 route:cache:
php artisan route:cache
运行完成后,每次请求都会从缓存中读取路由,所以如果你添加了新的路由需要重新生成路由缓存。因此,只有在项目部署阶段才需要运行 route:cache 命令,本地开发环境完全无此必要。
想要移除缓存路由文件,使用route:clear
命令即可
消息状态写法
@foreach (['danger', 'warning', 'success', 'info'] as $msg)
@if(session()->has($msg))
<div class="flash-message">
<p class="alert alert-{{ $msg }}">
{{ session()->get($msg) }}
</p>
</div>
@endif
@endforeach
Createing方法
creating 用于监听模型被创建之前的事件,created 用于监听模型被创建之后的事件。接下来我们要生成的用户激活令牌需要在用户模型创建之前生成,因此需要监听的是 creating 方法。
在用户模型中添加 creating 方法如下
<?php
namespace App\Models;
.
.
.
class User extends Authenticatable
{
.
.
.
protected $hidden = ['password', 'remember_token'];
public static function boot()
{
parent::boot();
static::creating(function ($user) {
$user->activation_token = str_random(30);
});
}
.
.
.
}
boot 方法会在用户模型类完成初始化之后进行加载,因此我们对事件的监听需要放在该方法中。
模型工厂的写法
例子一:
database/factories/UserFactory.php
<?php
use Faker\Generator as Faker;
/*
|--------------------------------------------------------------------------
| Model Factories
|--------------------------------------------------------------------------
|
| This directory should contain each of the model factory definitions for
| your application. Factories provide a convenient way to generate new
| model instances for testing / seeding your application's database.
|
*/
$factory->define(App\Models\User::class, function (Faker $faker) {
$date_time = $faker->date . ' ' . $faker->time;
static $password;
return [
'name' => $faker->name,
'email' => $faker->safeEmail,
'is_admin' => false,
'activated' => true,
'password' => $password ?: $password = bcrypt('secret'),
'remember_token' => str_random(10),
'created_at' => $date_time,
'updated_at' => $date_time,
];
});
database/seeds/UsersTableSeeder.php
<?php
use Illuminate\Database\Seeder;
use App\Models\User;
class UsersTableSeeder extends Seeder
{
/**
* Run the database seeds.
*
* @return void
*/
public function run()
{
$users = factory(User::class)->times(50)->make();
User::insert($users->makeVisible(['password', 'remember_token'])->toArray());
$user = User::find(1);
$user->name = 'Aufree';
$user->email = 'aufree@yousails.com';
$user->password = bcrypt('password');
$user->is_admin = true;
$user->activated = true;
$user->save();
}
}
再举例子:
database/factories/StatusFactory.php
<?php
use Faker\Generator as Faker;
$factory->define(App\Models\Status::class, function (Faker $faker) {
$date_time = $faker->date . ' ' . $faker->time;
return [
'content' => $faker->text(),
'created_at' => $date_time,
'updated_at' => $date_time,
];
});
创建一个 StatusesTableSeeder 文件来对微博假数据进行批量生成:database/seeds/StatusesTableSeeder.php
通过 app() 方法来获取一个 Faker 容器 的实例,并借助 randomElement 方法来取出用户 id 数组中的任意一个元素并赋值给微博的 user_id,使得每个用户都拥有不同数量的微博。
<?php
use Illuminate\Database\Seeder;
use App\Models\User;
use App\Models\Status;
class StatusesTableSeeder extends Seeder
{
/**
* Run the database seeds.
*
* @return void
*/
public function run()
{
$user_ids = ['1','2','3'];
$faker = app(Faker\Generator::class);
$statuses = factory(Status::class)->times(100)->make()->each(function ($status) use ($faker, $user_ids) {
$status->user_id = $faker->randomElement($user_ids);
});
Status::insert($statuses->toArray());
}
}
在 DatabaseSeeder 类中指定调用微博数据填充文件:
<?php
use Illuminate\Database\Seeder;
use Illuminate\Database\Eloquent\Model;
class DatabaseSeeder extends Seeder
{
/**
* Run the database seeds.
*
* @return void
*/
public function run()
{
Model::unguard();
$this->call(UsersTableSeeder::class);
$this->call(StatusesTableSeeder::class);
Model::reguard();
}
}
重置填充数据库:
php artisan migrate:refresh --seed
模型创建的名字空间
我们新建的模型文件都要统一放置在 app/Models 文件夹下,为此我们在创建一个新的模型对象时,需要在模型名称前面加上 Models 目录。
php artisan make:model Models/SampleModel
友好化和语言转换
$status->created_at->diffForHuman();
我们发现 diffForHumans 为我们生成的时间是英文的,如果要使用中文时间,则需要对 Carbon 进行本地化设置。Carbon 是 PHP DateTime 的一个简单扩展,Laravel 将其默认集成到了框架中。对 Carbon 进行本地化的设置很简单,只在 AppServiceProvider 中调用 Carbon 的 setLocale 方法即可,AppServiceProvider 是框架的核心,在 Laravel 启动时,会最先加载该文件。
app/Providers/AppServiceProvider.php
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Carbon\Carbon;
class AppServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*
* @return void
*/
public function boot()
{
Carbon::setLocale('zh');
}
/**
* Register any application services.
*
* @return void
*/
public function register()
{
//
}
}
Model和View交互示例
resources/views/statuses/_status.blade.php
<li id="status-{{ $status->id }}"> <a href="{{ route('users.show', $user->id )}}"> <img src="{{ $user->gravatar() }}" alt="{{ $user->name }}" class="gravatar"/> </a> <span class="user"> <a href="{{ route('users.show', $user->id )}}">{{ $user->name }}</a> </span> <span class="timestamp"> {{ $status->created_at->diffForHumans() }} </span> <span class="content">{{ $status->content }}</span> </li>
resources/views/users/show.blade.php
@extends('layouts.default')
@section('title', $user->name)
@section('content')
<div class="row">
<div class="col-md-offset-2 col-md-8">
<div class="col-md-12">
<div class="col-md-offset-2 col-md-8">
<section class="user_info">
@include('shared._user_info', ['user' => $user])
</section>
</div>
</div>
<div class="col-md-12">
@if (count($statuses) > 0)
<ol class="statuses">
@foreach ($statuses as $status)
@include('statuses._status')
@endforeach
</ol>
{!! $statuses->render() !!}
@endif
</div>
</div>
</div>
@stop
每个模型符合RESTful框架的定义路由
为了符合RESTful框架,咱们使用resource方法,再次回顾:一个参数为资源名称,第二个参数为控制器名称
Route::resource(‘users’,’UsersController’);
等价为
Route::get('/users', 'UsersController@index')->name('users.index');
Route::get('/users/{user}', 'UsersController@show')->name('users.show');
Route::get('/users/create', 'UsersController@create')->name('users.create');
Route::post('/users', 'UsersController@store')->name('users.store');
Route::get('/users/{user}/edit', 'UsersController@edit')->name('users.edit');
Route::patch('/users/{user}', 'UsersController@update')->name('users.update');
Route::delete('/users/{user}', 'UsersController@destroy')->name('users.destroy');
Route::resource('statuses', 'StatusesController', ['only' => ['store', 'destroy']]);
第三个参数等价为指定只使用RESTful的中的哪几个
添加中间件过滤的位置
在控制器的开头添加中间件的权限过滤,比如需要登陆后才能操作:app/Http/Controllers/StatusesController.php
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Http\Requests;
class StatusesController extends Controller
{
public function __construct()
{
$this->middleware('auth');
}
}
有对应关系的模型创建时的注意
当我们在创建微博的时候,需要通过以下方式来进行创建。这样创建的微博会自动与用户进行关联,还用到了Auth::user()方法
Auth::user()->statuses()->create([
'content' => $request->content
]);
最终在控制器下添加的方法如下:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Http\Requests;
use App\Models\Status;
use Auth;
class StatusesController extends Controller
{
public function __construct()
{
$this->middleware('auth');
}
public function store(Request $request)
{
$this->validate($request, [
'content' => 'required|max:140'
]);
Auth::user()->statuses()->create([
'content' => $request->content
]);
return redirect()->back();
}
}
能更新的字段要添加fillable字段
解决的办法很简单,在微博模型的 fillable 属性中允许更新微博的 content 字段即可。
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Status extends Model
{
protected $fillable = ['content'];
public function user()
{
return $this->belongsTo(User::class);
}
}
动态流
在开始之前,我们需要在用户模型中定义一个 feed 方法,该方法将当前用户发布过的所有微博从数据库中取出,并根据创建时间来倒序排序。在后面我们为用户增加关注人的功能之后,将使用该方法来获取当前用户关注的人发布过的所有微博动态。现在的 feed 方法定义如下:
<?php
namespace App\Models;
.
.
.
class User extends Authenticatable
{
.
.
.
public function feed()
{
return $this->statuses()
->orderBy('created_at', 'desc');
}
}
由于我们在用户模型中已定义好了 feed 方法,因此我们可以在主页对应的控制器动作 home 中使用该方法来获取用户的微博动态。
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Http\Requests;
use App\Http\Controllers\Controller;
use App\Models\Status;
use Auth;
class StaticPagesController extends Controller
{
public function home()
{
$feed_items = [];
if (Auth::check()) {
$feed_items = Auth::user()->feed()->paginate(30);
}
return view('static_pages/home', compact('feed_items'));
}
public function help()
{
return view('static_pages/help');
}
public function about()
{
return view('static_pages/about');
}
}
动态流的局部视图:
@if (count($feed_items)) <ol class="statuses"> @foreach ($feed_items as $status) @include('statuses._status', ['user' => $status->user]) @endforeach {!! $feed_items->render() !!} </ol> @endif
权限控制的删除操作
使用授权策略来搞权限控制:举例子,删除微博,要求当前用户和微博所属用户id相同
$ php artisan make:policy StatusPolicy
app/Policies/StatusPolicy.php
<?php
namespace App\Policies;
use Illuminate\Auth\Access\HandlesAuthorization;
use App\Models\User;
use App\Models\Status;
class StatusPolicy
{
use HandlesAuthorization;
public function destroy(User $user, Status $status)
{
return $user->id === $status->user_id;
}
}
添加完权限还得在:AuthServiceProvider
对授权策略进行配置才能正常使用:
app/Providers/AuthServiceProvider.php
<?php
namespace App\Providers;
use Illuminate\Contracts\Auth\Access\Gate as GateContract;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
class AuthServiceProvider extends ServiceProvider
{
/**
* The policy mappings for the application.
*
* @var array
*/
protected $policies = [
'App\Model' => 'App\Policies\ModelPolicy',
\App\Models\User::class => \App\Policies\UserPolicy::class,
\App\Models\Status::class => \App\Policies\StatusPolicy::class,
];
/**
* Register any application authentication / authorization services.
*
* @param \Illuminate\Contracts\Auth\Access\Gate $gate
* @return void
*/
public function boot(GateContract $gate)
{
$this->registerPolicies($gate);
//
}
}
接下来我们要在用户发布过的每一条微博旁边加上一个删除按钮,因此需要把删除按钮加到渲染单条微博的局部视图上。并且删除按钮必须是微博的作者本人才能看到,我们可以很方便的利用 Laravel 授权策略提供的 @can Blade 命令,在 Blade 模板中做授权判断。
resources/views/statuses/_status.blade.php
@can方法是对有权更新’destroy’的人可以看到
@can('destroy', $status) <form action="{{ route('statuses.destroy', $status->id) }}" method="POST"> {{ csrf_field() }} {{ method_field('DELETE') }} <button type="submit" class="btn btn-sm btn-danger status-delete-btn">删除</button> </form> @endcan
而statuses的删除方法:
public function destroy(Status $status) { $this->authorize('destroy', $status); $status->delete(); session()->flash('success', '微博已被成功删除!'); return redirect()->back(); }
最后还需要进行删除的授权:如果不通过会返回403错误——资源不可o