给大家提供一些 Laravel 使用的小技巧, 当然你有任何想法欢迎 PR !
Github 地址:https://github.com/LaravelDaily/laravel-tips
当你的控制器仅有一个方法的时候,不妨尝试下 __invoke()
方法来,让当前控制器变身为 “invokable” 控制器。
想要了解单行为控制器的好处以及用法请参考:
单行为控制器的简单使用:
定义路由,如下:
Route::get('user/{id}', 'ShowProfile');
通过命令创建单行为控制器:
php artisan make:controller ShowProfile --invokable
修改控制器代码,如下:
class ShowProfile extends Controller { public function __invoke($id) { return view('user.profile', [ 'user' => User::findOrFail($id) ]); } }
你可以通过 redirect()
重定向到某个路由或者某个链接,但是当你希望重定向到一个特定方法,并且需要携带一些参数的时候,action 方法可以帮你实现,代码如下:
return redirect()->action('SomeController@method', ['param' => $value]);
当你在接口执行了某些逻辑,但是不需要返回内容的时候,你可以直接返回 204 状态码。 在 Laravel 中这很容易实现:
Tips: 204 状态码说明
public function reorder(Request $request) { foreach ($request->input('rows', []) as $row) { Country::find($row['id'])->update(['position' => $row['position']]); } return response()->noContent(); }
Laravel 的 Eloquent Orm 提供了 whereDay()
, whereMonth()
, whereYear()
, whereDate()
和 whereTime()
供你对日期进行筛选。简单例子:
$products = Product::whereDate('created_at', '2018-01-31')->get(); $products = Product::whereMonth('created_at', '12')->get(); $products = Product::whereDay('created_at', '31')->get(); $products = Product::whereYear('created_at', date('Y'))->get(); $products = Product::whereTime('created_at', '=', '14:13:58')->get();
你如果仅仅是操作数字类型字段的增加(减少),你可以尝试下 increment()
(decrement()
) 方法:
Post::find($post_id)->increment('view_count'); User::find($user_id)->increment('points', 50);
你可以使用 make:observer
来创建一个观察者,然后通过修改模型事件 creating()
来设定当前用户的信息,代码如下 :
详细使用可以查阅 Laravel 中的模型事件与 Observer
class PostObserver { public function creating(Post $post) { $post->user_id = auth()->id(); } }
当使用软删除删除数据之后,你可以通过以下方法批量恢复删除的数据:
Post::withTrashed()->where('author_id', 1)->restore();
默认的 Model::all()
会返回所有字段,不管你是否有需要,当然你也可以通过给 all()
传递参数来指定返回的列。如下:
$users = User::all(['id', 'name', 'email']);
findOrFail()
方法,可以在查询没有符合条件数据的时候直接抛出异常。
$user = User::where('email', 'povilas@laraveldaily.com')->firstOrFail();
在 Eloquent 查询中使用 select 可以设置查询的字段,然后你可以通过 “as” 给字段重命名为你想要的名称:
$users = DB::table('users')->select('name', 'email as user_email')->get();
由于 Eloquent 中,get 方法将返回 Collection 类型的数据,所以在通过 get 方法获取数据之后,你可以直接通过 map()
方法来处理:
$users = User::where('role_id', 1)->get()->map(function (User $user) { $user->some_column = some_function($user); return $user; });
默认的,laravel 会在 migration 以及 model 中添加 timestamps 相关字段(created_at,updated_at),如果你不想使用他,你可以在migrate 中移除相关字段,或者在 model 中设置 timestamps
属性,将该属性设置为 false
即可实现:
class Company extends Model { public $timestamps = false; }
当你想要在已存在的数据库中使用 Laraevl 的 Eloquent Orm的时候,你的时间字段又不同于 Laravel 默认字段,这时候怎么处理呢?你可以通过给以下常量重新赋值来设置当前使用的时间戳字段:
class Role extends Model{ const CREATED_AT = 'create_time'; const UPDATED_AT = 'update_time'; }
在此之前你是这样实现的:
User::orderBy('created_at', 'desc')->get();
当然,你也可以使用更简单的方式:
User::latest()->get();
默认 latest()
会通过 created_at
进行排序。
这里还有个反方法可以用: oldest()
将会通过 created_at
字段进行升序排序。
User::oldest()->get();
当然你也可以通过传递参数来执行想要进行排序的字段,如下:
$lastUpdatedUser = User::newest('updated_at')->first();
当你在创建记录的时候,你同时希望对某个字段进行自增操作的时候,你可以在 boot()
方法中注册 creating 方法来实现。举个例子,现在你有个 “position” 字段,你希望在你完成新增操作的时候同时对该字段 +1 ,实现代码如下:
class Country extends Model { protected static function boot() { parent::boot(); Country::creating(function($model) { $model->position = Country::max('position') + 1; }); } }
whereRaw()
使查询效率更快使用 whereRaw()
方法,通常可以使查询效率更高。例如你想获取 30+ 天未登录的用户,你可以通过以下实现:
User::where('active', 1) ->whereRaw('TIMESTAMPDIFF(DAY, created_at, updated_at) > ?', 30) ->get();
你可以同时创建多个作用域,当然可以在一个查询中使用多个作用域。
模型中定义:
public function scopeActive($query) { return $query->where('active', 1); } public function scopeRegisteredWithinDays($query, $days) { return $query->where('created_at', '>=', now()->subDays($days)); }
controller 中使用:
$users = User::registeredWithinDays(30)->active()->get();
如果你想使用 whereDate()
查询今天的记录,你可以通过直接 now()
而无需进行 ->toDateString()
,因为他会自动转换。
// Instead of $todayUsers = User::whereDate('created_at', now()->toDateString())->get(); // 不需要转换 now() $todayUsers = User::whereDate('created_at', now())->get();
你可以通过任意你想实现的方式进行分组,下面是通过对姓名首字符的分组方法:
$users = User::all()->groupBy(function($item) { return $item->name[0]; });
如果你希望设置一个不可更新的列,你可以通过以下方式实现:
class User extends Model { public function setEmailAttribute($value) { if ($this->email) { return; } $this->attributes['email'] = $value; } }
Eloquent 方法 find()
可以通过传递数组参数实现多个记录的查询:
// Will return Eloquent Model $user = User::find(1); // Will return Eloquent Collection $users = User::find([1,2,3]);
如果你不想在数据表中使用自增列要怎么办?下面叫你如何实现
数据迁移:
Schema::create('users', function (Blueprint $table) { // $table->increments('id'); $table->uuid('id')->unique(); });
模型:
class User extends Model { public $incrementing = false; protected $keyType = 'string'; protected static function boot() { parent::boot(); User::creating(function ($model) { $model->setId(); }); } public function setId() { $this->attributes['id'] = Str::uuid(); } }
你可以在你的关联中使用 orderBy对关联进行排序:
public function products() { return $this->hasMany(Product::class); } public function productsByName() { return $this->hasMany(Product::class)->orderBy('name'); }
如果你在使用模型关联的时候经常需要额外的进行 where 查询,你可以直接通过对关联进行条件查询,实现方式如下:
Model:
public function comments() { return $this->hasMany(Comment::class); } public function approved_comments() { return $this->hasMany(Comment::class)->where('approved', 1); }
你可以在各种地方使用 RAW DB 查询,包括 groupBy()
和 havingRaw()
后面:
Product::groupBy('category_id')->havingRaw('COUNT(*) > 1')->get();
Eloquent has()
方法可以通过 books.ratings
的方式,而使查询可以做用于更深层的关联上!
// Author -> hasMany(Book::class); // Book -> hasMany(Rating::class); $authors = Author::has('books.ratings')->get();
在一对多关联中, hasMany()
允许通过 has()
方法进行关联数据条数的限制:
// Author -> hasMany(Book::class) $authors = Author::has('books', '>', 5)->get();
你可以通过 belongsTo
关联, to avoid fatal errors when calling it like {{ $post->user->name }}
if $post->user doesn’t exist.
public function user() { return $this->belongsTo('App\User')->withDefault(); }
如果你定义了 hasMany()
关联,你可以通过 saveMany()
来一次性创建多条关联数据:
$post = Post::find(1); $post->comments()->saveMany([ new Comment(['message' => 'First comment']), new Comment(['message' => 'Second comment']), ]);
通过 with 你可以获取关联表甚至特定字段信息:
$users = App\Book::with('author:id,name')->get();
你可以使用他获取更深一层次的关联:
$users = App\Book::with('author.country:id,name')->get();
例如你想在更新评论信息的时候想同时更新上级关联的帖子信息的 updated_at 字段,你可以通过指定 $touch 属性实现:
class Comment extends Model{ protected $touches = ['post']; }
永远不要在未检测关联是否存在的情况下使用 $model->relationship->field
。
It may be deleted for whatever reason, outside your code, by someone else’s queued job etc.
可以通过 if-else
,在 Blade 中使用 {{ $model->relationship->field ?? '' }}
, 或者 {{ optional($model->relationship)->field }}
进行检测。
如果你定义了 hasMany()
关联,当你想要统计关联数据的数量的时候,尝试下 withCount
吧。例子, 假如你有一个 Post 模型,该模型会有多个 Comments 关联,看下如何使用 withCount()
吧:
public function index(){ $users = User::withCount(['posts', 'comments'])->get(); return view('users', compact('users')); }
然后在模板文件中使用 {relationship}_count
属性就可以获取相应的数据统计了:
@foreach ($users as $user) <tr> <td>{{ $user->name }}</td> <td class="text-center">{{ $user->posts_count }}</td> <td class="text-center">{{ $user->comments_count }}</td> </tr> @endforeach
如果你想要在加载关联的时候做一些条件限制,可以通过回调函数实现。例如你想获取国家的前3个城市,可以通过以下代码实现:
$countries = Country::with(['cities' => function($query) { $query->orderBy('population', 'desc'); $query->take(3);}])->get();
你即可以通过 $with
属性设置模型始终会加载的关联,也可以在构造函数中动态处理加载项:
class ProductTag extends Model { protected $with = ['product']; public function __construct() { parent::__construct(); $this->with = ['product']; if (auth()->check()) { $this->with[] = 'user'; } } }
定义 belongsTo
关联和 hasMany
关联,这样可以在创建关联数据的时候有由系统填充关联字段信息:
// 如果定义了 Post -> belongsTo(User), and User -> hasMany(Post)... // 以前你需要这样创建文章信息... Post::create([ 'user_id' => auth()->id(), 'title' => request()->input('title'), 'post_text' => request()->input('post_text'), ]); // 现在,你可以这样 auth()->user()->posts()->create([ 'title' => request()->input('title'), 'post_text' => request()->input('post_text'), ]);
使用 as 可以重命名您的关联:
模型中定义:
public function podcasts() { return $this->belongsToMany('App\Podcast') ->as('subscription') ->withTimestamps(); }
控制器中使用:
$podcasts = $user->podcasts();foreach ($podcasts as $podcast) { // instead of $podcast->pivot->created_at ... echo $podcast->subscription->created_at; }
通过修改迁移文件的时间戳前缀可以达到排序迁移执行数据的目的。例如:将2018_08_04_070443_create_posts_table.php
修改为 2018_07_04_070443_create_posts_table.php
( 修改 2018_08_04
为 2018_07_04
).
迁移中 timestamps()
和 timestampsTz()
都可以设定时区。
Schema::create('employees', function (Blueprint $table) { $table->increments('id'); $table->string('name'); $table->string('email'); $table->timestampsTz(); });
另外还有 dateTimeTz()
, timeTz()
, timestampTz()
, softDeletesTz()
.
一些特定值得迁移类型:
$table->geometry('positions'); $table->ipAddress('visitor'); $table->macAddress('device'); $table->point('position'); $table->uuid('id');
更多类型查阅: official documentation.
通过 timestamp()
设定列为时间戳类型,通过useCurrent()
设定该列的默认值。
$table->timestamp('created_at')->useCurrent(); $table->timestamp('updated_at')->useCurrent();
通过 $loop
判断该数据是否是最先/最后的数据:
@foreach ($users as $user) @if ($loop->first) This is the first iteration. @endif @if ($loop->last) This is the last iteration. @endif <p>This is user {{ $user->id }}</p> @endforeach
当然他还有其他可用属性例如: $loop->iteration
和$loop->count
.
详情阅读: official documentation.
在你加载 view 之前你可以判断 view 是否存在,从而避免不必要的错误显示。
if (view()->exists('custom.page')) { // Load the view }
你可以提供多个 view 供选择:
return view()->first(['custom.dashboard', 'dashboard'], $data);
快速创建特定错误的页面,你只要以该错误code命名并创建相应的 blade 即可。例如: 500(resources/views/errors/500.blade.php)/403(resources/views/errors/403.blade.php),系统会通过相应的 code 自动匹配错误显示页面。
可以直接在路由中绑定需要显示的 view:
// 在 controller 绑定 view Route::get('about', 'TextsController@about'); class TextsController extends Controller { public function about() { return view('texts.about'); } } // 直接在路由中绑定 Route::view('about', 'texts.about');
在 Blade 中直接使用 auth()
可以判断以及获取当前登录的用户信息:
例子:
@if(auth()->user()) // 用户已登录 @endif
或者更简单的方式:
@auth // 鉴权通过 @endauth
相反的方法 @guest
:
@guest // 鉴权未通过 @endguest
Blade 循环嵌套,可以通过 $loop
获取上层数据:
@foreach ($users as $user) @foreach ($user->posts as $post) @if ($loop->parent->first) This is first iteration of the parent loop. @endif @endforeach @endforeach
Laravel 提供了简单的方式自定义 Blade 指令,只需在 app/Providers/AppServiceProvider.php
注册自己的指令即可。例子:你想要使用新的标签替换 <br>
标签:
<textarea>@br2nl($post->post_text)</textarea>
将该指令添加到 AppServiceProvider 的 boot()
方法中:
public function boot() { Blade::directive('br2nl', function ($string) { return "<?php echo preg_replace('/\<br(\s*)?\/?\>/i', \"\n\", $string); ?>"; }); }
当你不确定 Blade 的组件是否存在的时候可以使用一下方法:
仅仅在存在时加载该组件:
@includeIf('partials.header')
仅仅在有权限的时候加载该组件
@includeWhen(auth()->user()->role_id == 1, 'partials.header')
当该组件不存在时,会加载默认组件:
@includeFirst('adminlte.header', 'default.header')
路由组可以使用嵌套:
Route::group(['prefix' => 'account', 'as' => 'account.'], function() { Route::get('login', 'AccountController@login'); Route::get('register', 'AccountController@register'); Route::group(['middleware' => 'auth'], function() { Route::get('edit', 'AccountController@edit'); }); });
你可以通过动态子域名创建相应的路由:
Route::domain('{username}.workspace.com')->group(function () { Route::get('user/{id}', function ($username, $id) { // }); });
Auth::routes()
下包含了那些路由呢?
你可以在 /vendor/laravel/ui/src/AuthRouteMethods.php
找到:
public function auth() { return function ($options = []) { // Authentication Routes... $this->get('login', 'Auth\LoginController@showLoginForm')->name('login'); $this->post('login', 'Auth\LoginController@login'); $this->post('logout', 'Auth\LoginController@logout')->name('logout'); // Registration Routes... if ($options['register'] ?? true) { $this->get('register', 'Auth\RegisterController@showRegistrationForm')->name('register'); $this->post('register', 'Auth\RegisterController@register'); } // Password Reset Routes... if ($options['reset'] ?? true) { $this->resetPassword(); } // Password Confirmation Routes... if ($options['confirm'] ?? class_exists($this->prependGroupNamespace('Auth\ConfirmPasswordController'))) { $this->confirmPassword(); } // Email Verification Routes... if ($options['verify'] ?? false) { $this->emailVerification(); } }; }
Laravel 7 之前, 存在与文件 /vendor/laravel/framework/src/illuminate/Routing/Router.php
。
你可以通过绑定 user 参数 Route::get('api/users/{user}', function (App\User $user) { … }
- 不仅仅可以通过自增 ID 字段筛选,也可以使用 {user}
的 username
字段筛选,只要在模型中这样处理即可:
public function getRouteKeyName() { return 'username'; }
在此之前,你可能这样写:
Route::get('page', 'PageController@action');
上面写法在编辑器中无法从 route 直接跳转到相应的 controller 中,尝试一下方法:
Route::get('page', [\App\Http\Controllers\PageController::class, 'action']);
现在你可以在编辑器中直接点击 PageController 编辑器会根据路径自动找到相关文件(这需要你的编辑器支持,像 phpstorm)。
如果你你不想在路由匹配失败时显示 404 错误页面,你可以通过回调函数设置默认显示内容,这样在路由匹配失败的时候会直接显示你自定义的内容:
Route::group(['middleware' => ['auth'], 'prefix' => 'admin', 'as' => 'admin.'], function () { Route::get('/home', 'HomeController@index'); Route::resource('tasks', 'Admin\TasksController'); }); // 其他路由 Route::fallback(function() { return 'Hm, why did you land here somehow?'; });
我们可以直接在路由定义时使用 “where” 方法验证路由参数。一个非常典型的情况是在路由的前面加上语言设置, 像 fr/blog
和 en/article/333
这样。我们如何确保这两个首字母不用于语言以外的其他字符串?
routes/web.php
:
Route::group([ 'prefix' => '{locale}', 'where' => ['locale' => '[a-zA-Z]{2}'] ], function () { Route::get('/', 'HomeController@index'); Route::get('article/{id}', 'ArticleController@show'); });
你可以通过 throttle:60,1
来限制单位时间内请求某个可连接的此处:
Route::middleware('auth:api', 'throttle:60,1')->group(function () { Route::get('/user', function () { // }); });
你可以针对不同用户组进行不同限制:
// maximum of 10 requests for guests, 60 for authenticated users Route::middleware('throttle:10|60,1')->group(function () { // });
Also, you can have a DB field users.rate_limit and limit the amount for specific user:
Route::middleware('auth:api', 'throttle:rate_limit,1')->group(function () { Route::get('/user', function () { // }); });
你可以通过 route 方法第二个参数,以数组的形式将query 参数绑定到路由:
Route::get('user/{id}/profile', function ($id) { // })->name('profile'); $url = route('profile', ['id' => 1, 'photos' => 'yes']); // Result: /user/1/profile?photos=yes
你可以验证上传图片的尺寸:
['photo' => 'dimensions:max_width=4096,max_height=4096']
可以自定义现有规则的错误消息,只需要创建相应的语言文件即可 resources/lang/xx/validation.php
:
'custom' => [ 'email' => [ 'required' => 'We need to know your e-mail address!', ], ],
您可以通过 before/after
规则来验证日期,并将各种字符串作为参数传递,例如:tomorrow
, now
, yesterday
。 例如:'start_date' => 'after:now'
。 它在底层使用 strtotime()
验证。
$rules = [ 'start_date' => 'after:tomorrow', 'end_date' => 'after:start_date' ];
如果你的验证规则需要同时满足额外的条件,在 FormRequest
方法中定义 withValidator()
添加你的条件即可:
use Illuminate\Validation\Validator; class StoreBlogCategoryRequest extends FormRequest { public function withValidator(Validator $validator) { if (auth()->user()->is_admin) { $validator->addRules(['some_secret_password' => 'required']); } } }
通过FormRequest
的 messages()
方法,你可以自定义验证错误提示信息:
class StoreUserRequest extends FormRequest { public function rules() { return ['name' => 'required']; } public function messages() { return ['name.required' => 'User name should be real name']; } }
验证前置操作,如果你需要在验证之前对某些字段做一些处理, prepareForValidation()
可以实现该功能:
protected function prepareForValidation() { $this->merge([ 'slug' => Illuminate\Support\Str::slug($this->slug), ]); }
默认情况下,laraevl 会在处理完成所有验证规则之后,将错误信息一并返回;当然如果你想在遇到第一个错误的时候就终止继续往下的验证你可以通过添加一下规则: bail
$request->validate([ 'title' => 'bail|required|unique:posts|max:255', 'body' => 'required', ]);
@can
Blade 指令可以检测当前用户是否有某个权限,但是当场景复杂点要怎么处理?比如当我在同时拥有多个权限才能进行某个操作的时候? @canany
指令了解下:
@canany(['update', 'view', 'delete'], $post) // 当前用户拥有以下其权限: 更新,显示或者删除文章 @elsecanany(['create'], \App\Post::class) // 当前用户无法创建文章 @endcanany
你可以在 Eloquent 中使用 Null 过滤,但是在集合( collection )中,Null 过滤将不会生效:
// 这样可以 $messages = Message::where('read_at is null')->get(); // 这样无法正常返回 $messages = Message::all(); $unread_messages = $messages->where('read_at is null')->count(); // 这样也可以 $unread_messages = $messages->where('read_at', '')->count();
如果你想根据某些条件对结果进行分组处理,并且该条件列并非数据库字段,你可以通过 GroupBy 回调实现。
例如,如果你想按注册日期对用户进行分组,代码如下:
$users = User::all()->groupBy(function($item) { return $item->created_at->format('Y-m-d'); });
⚠️ Notice: User::all()
将返回集合类型数据,因此这是在集合上进行的 GroupBy
操作
当你在查询结果上使用 ->all()
或者 ->get()
方法的时候,该操作是针对结果集合进行的操作,不会再额外的进行查询操作。
$users = User::all(); echo 'Max ID: ' . $users->max('id'); echo 'Average age: ' . $users->avg('age'); echo 'Total budget: ' . $users->sum('budget');
Auth::once()
可以用于构建无状态请求,他不会产生任何 cookie信息,这很有利于接口的开发:
if (Auth::once($credentials)) { // }
在更新用户密码的同时更新用户 token 很有必要:
模型:
public function setPasswordAttribute($value){ $this->attributes['password'] = $value; $this->attributes['api_token'] = Str::random(100); }
开发过程中,你可能需要测试邮件发送,但是你有不希望测试邮件真正发送到客户邮箱,这时候你可以通过配置 .env
参数 MAIL_DRIVER=log
这样,邮件将会以文本的形式存储到 storage/logs/laravel.log
,而不会真正发送出去。
如果你使用 Mailables 为发送邮件提供服务,那么你可以直接在浏览器中浏览邮件预览,而不必真正的发送:
Route::get('/mailable', function () { $invoice = App\Invoice::find(1); return new App\Mail\InvoicePaid($invoice); });
如果你在使用 toMail() 发送邮件通知的时候,未指定相关主题(subject)laravel 提供了默认参数可以使用:
因此,你只需要有:
class UserRegistrationEmail extends Notification { // }
然后你就可以在注册完成收到一封注册邮件提醒。
你可以通过 $user->notify()
发送通知给特定用户,你也可以通过 Notification::route()
发送给任何其他需要通知的地方:
Notification::route('mail', 'taylor@example.com') ->route('nexmo', '5555555555') ->route('slack', 'https://hooks.slack.com/services/...') ->notify(new InvoicePaid($invoice) );
当我们创建我们的 Artisan 命令的时候,可以通过 $this->confirm()
, $this->anticipate()
, $this->choice()
等方法询问并要求输入参数:
// 确认框 if ($this->confirm('Do you wish to continue?')) { // } // 附加可选项 $name = $this->anticipate('What is your name?', ['Taylor', 'Dayle']); // 附加默认值 $name = $this->choice('What is your name?', ['Taylor', 'Dayle'], $defaultIndex);
页面维护模式实现如下:
php artisan down
执行上面命令后,再访问站点的时候会得到 503 错误信息。
你也可以通过提供参数实现而外的效果:
php artisan down --message="Upgrading Database" --retry=60 --allow=127.0.0.1
当维护完成,运行一下命令上线吧!
php artisan up
有时候,你某些数据的插入/修改需要基于其他数据的完成,这时你可以通过回调方法实现,例子如下:
$factory->afterCreating(App\User::class, function ($user, $faker) { $user->accounts()->save(factory(App\Account::class)->make()); });
Faker能生成的不仅仅是文本,还可以生成指定大小的图像:
$factory->define(User::class, function (Faker $faker) { return [ 'name' => $faker->name, 'email' => $faker->unique()->safeEmail, 'email_verified_at' => now(), 'password' => bcrypt('password'), 'remember_token' => Str::random(10), 'avatar' => $faker->image(storage_path('images'), 50, 50) ]; });
你可以通过 Log::info()
来记录日志,或者 info()
快捷方法可以实现同样的效果。
Log::info('User failed to login.', ['id' => $user->id]);
你可以直接通过在查询结果之后的 ->dd()
操作来打印结果。
// Instead of $users = User::where('name', 'Taylor')->get(); dd($users); // Do this $users = User::where('name', 'Taylor')->get()->dd();
【相关推荐:laravel视频教程】
以上就是如何快速上手 Laravel ?100 个实用小技巧分享的详细内容,更多请关注php中文网其它相关文章!