Laravel 5.7 最佳实践和开发技巧分享

49次阅读

共计 7050 个字符,预计需要花费 18 分钟才能阅读完成。

Laravel 因可编写出干净,可用可调试的代码而为广大的 PHP 开发者所熟知。它同样也支持许许多多的功能,有时却未能在文档中体现,或者由于某种原因它们出现过又被移除了。
我已经在生产环境中使用 Laravel 2 年了,从中我学到如何把代码变得更好,从我首次使用它以来我都充分发掘它的优势。接下来我将向你展示一些可能对你在用 Laravel 写代码时很有帮助的奥义之招。
*
查询数据时使用本地范围
Laravel 有一种非常棒的方式来使用 查询构造器 编写查询。就像这样:
$orders = Order::where(‘status’, ‘delivered’)->where(‘paid’, true)->get();
很不错。这让我专注于编写更友好的代码而不是 SQL 语句。但如果用 本地范围,我们可以让这行代码变得更好些。
当查询数据时,本地范围 允许我们创建自己的 查询构造器 链式方法。举个例子,取代 ->where(),我们可以用更简洁的 ->delivered() 和 ->paid()。
首先在 Order 模型,我们加入一些方法:
class Order extends Model
{

public function scopeDelivered($query) {
return $query->where(‘status’, ‘delivered’);
}
public function scopePaid($query) {
return $query->where(‘paid’, true);
}
}
当声明本地范围时,你应该使用 scope[Something] 来命名。这样 Laravel 便会知道这是一个本地范围并且可以在查询构造器中使用。请确保你在方法中传入了第一个参数 $query,也就是由 Laravel 自动注入的查询构造器实例。
$orders = Order::delivered()->paid()->get();
对于可接受额外参数的查询,你可以使用动态范围。每个范围都允许你传入额外的参数。
class Order extends Model
{

public function scopeStatus($query, string $status) {
return $query->where(‘status’, $status);
}
}
$orders = Order::status(‘delivered’)->paid()->get();
在本文的后面,你会知道为什么数据库字段应该使用 蛇形命名,但这里有第一个原因:Laravel 默认用 where[Something] 来替换 scope[Something]。所以作为 scopeStatus 范围的代替,你可以这样做:
Order::whereStatus(‘delivered’)->paid()->get();
对于 where[Something],Laravel 会搜索 蛇形命名 版本的数据库字段。如果你的数据库中有个 status 字段,你可以用上面那个例子。如果有个 shipping_status 字段,你可以用:
Order::whereShippingStatus(‘delivered’)->paid()->get();
由你决定!
必要的时候使用请求类
Laravel 提供了一种优秀的方式来验证表单提交的数据。如果你需要它,不管是 POST 还是 GET 请求,它都可以验证。
在控制器中,你可以这样做:
public function store(Request $request)
{
$validatedData = $request->validate([
‘title’ => ‘required|unique:posts|max:255’,
‘body’ => ‘required’,
]);

// 如果这篇博客的内容无效……
}
但是当控制器中已经有很多代码时,再把验证表单数据的代码加进去就会显得很凌乱。你想尽可能地减少控制器的代码 —— 至少这是我在控制器中写很多逻辑时想到的第一件事。
Laravel 提供了一种很萌的方式来验证表单请求,那就是创建并使用专门的 请求类 而不是用原始的 Request。你只需要创建你的请求类:
php artisan make:request StoreBlogPost
在 app/Http/Requests/ 目录中可以找到你刚创建的请求类:
class StoreBlogPostRequest extends FormRequest
{
public function authorize()
{
return $this->user()->can(‘create.posts’);
}
public function rules()
{
return [
‘title’ => ‘required|unique:posts|max:255’,
‘body’ => ‘required’,
];
}
}
现在,你应该用新创建的 App\Http\Requests\StoreBlogPostRequest 来代替原先的 Illuminate\Http\Request 类:
use App\Http\Requests\StoreBlogPostRequest;

public function store(StoreBlogPostRequest $request)
{
// 如果这篇博客的内容无效……
}
请求类中的 authorize() 方法应返回一个布尔值。如果返回了 false,它会抛出一个 403 异常,请确保你在  app/Exceptions/Handler.php 的 render() 方法中捕获了这个异常:
public function render($request, Exception $exception)
{
if ($exception instanceof \Illuminate\Auth\Access\AuthorizationException) {
//
}

return parent::render($request, $exception);
}
请求类中还有一个 messages() 方法,当验证失败时,它会返回一个包含了错误信息的数组:
class StoreBlogPostRequest extends FormRequest
{
public function authorize()
{
return $this->user()->can(‘create.posts’);
}

public function rules()
{
return [
‘title’ => ‘required|unique:posts|max:255’,
‘body’ => ‘required’,
];
}

public function messages()
{
return [
‘title.required’ => ‘The title is required.’,
‘title.unique’ => ‘The post title already exists.’,

];
}
}
@if ($errors->any())
@foreach ($errors->all() as $error)
{{$error}}
@endforeach
@endif
如果你想得到某个字段的验证信息,你可以这样做(当这个字段验证通过时 $errors->has() 会返回一个 false):
<input type=”text” name=”title” />
@if ($errors->has(‘title’))
<label class=”error”>{{$errors->first(‘title’) }}</label>
@endif
魔术范围
构建查询时,可以使用已有的魔术范围:
根据 created_at 倒序查询:
User::latest()->get();
根据任意字段倒序查询:
User::latest(‘last_login_at’)->get();
随机查询(即 SQL 语句中的 ORDER BY RAND())
User::inRandomOrder()->get();
使用关联关系代替冗长的查询(或者写得不好的查询)
你是否曾经为了获取更多的信息而在查询语句中使用大量的 join 操作?即使在使用查询构造器的情况下,编写这样的 SQL 语句也是困难的,但是数据模型已经使用 关联关系 来实现同样的功能。由于文档提供了太多的信息,因此刚开始时你可能对关联关系并不熟悉,但是这些内容可以帮助你更好的理解事物的运行原理,同时让你的程序运行得更加顺畅。
通过 这里 查询关联关系的文档。
为耗时的任务使用任务系统
Laravel 的任务 是后台运行程序必用的功能强大的工具。

你要发送电子邮件? 任务系统。
你要广播一个消息? 任务系统。
你要处理一张图片? 任务系统。

任务系统能够帮助你实现,在执行上述这些任务时,减少你的用户的应用加载时间。这些任务可以被放进命名的队列,它们能够被安排优先级,Laravel 几乎在所有可能的地方都实现了队列:无论在后台执行一些 PHP 任务,或者发送消息,或者广播事件,队列都在这些场景中出现。
你可以在 这里 查询队列的文档。
在使用队列时,我喜欢使用 Laravel Horizon,因为它很容易安装,它能够通过 Supervisor 工具或者配置文件实现后台运行,同时我能够告诉 Horizon 我希望每个队列产生多少个进程。
遵守数据库标准 & 访问器
Laravel 从一开始就教给你变量和方法应使用像 $camelCase camelCase() 这样的小驼峰命名而数据库字段应使用像 snake_case 这样的蛇形命名。为什么呢?因为这有助于我们构造更好的 访问器。
访问器是可以直接在模型中构造的自定义字段。如果我们的数据库包含了 first_name、last_name、age 这几个字段,我们可以增加一个叫做 name 的自定义字段来把 first_name 和 last_name 拼接起来。别担心,这个 name 不会被写入到数据库。它只是某个模型的自定义属性。所有的访问器,和 范围 一样,都有自定义命名语法:getSomethingAttribute:
class User extends Model
{

public function getNameAttribute(): string
{
return $this->first_name.’ ‘.$this->last_name;
}
}
当使用 $user->name,访问器会返回拼接好的字符串。
*
默认情况下,用 dd($user) 是看不到 name 属性的,但是通过 $appends 变量我们可以使它一直可用:
class User extends Model
{
protected $appends = [
‘name’,
];

public function getNameAttribute(): string
{
return $this->first_name.’ ‘.$this->last_name;
}
}
现在每次 dd($user),我们都可以看到 name 了。(不过仍然,这个属性不是从数据库取得的,而是每次使用时将 first_name 和 last_name 拼接得到的)。
要注意下,如果你数据库里已经有 name 这个字段了,那情况就会有点不一样:$appends 数组里的 name 元素就不需要了,然后访问器需要传入一个参数,这个参数就是数据库中的 name(也就是说我们用不着再使用 $this 了)。
举个例子,我们也许想用 ucfirst() 来使名字的首字母转为大写:
class User extends Model
{
protected $appends = [
//
];

public function getFirstNameAttribute($firstName): string
{
return ucfirst($firstName);
}

public function getLastNameAttribute($lastName): string
{
return ucfirst($lastName);
}
}
现在当我们用 $user->first_name,它会返回一个首字母大写的字符串。
由于这个特性,数据库字段最好是用 snake_case 这种蛇形命名。
不要在配置文件中存储模型相关的静态数据
我喜欢把与模型相关的静态数据存放在模型文件中。让我们一起来看一下。
不要像下面这样:
BettingOdds.php
class BettingOdds extends Model
{

}
config/bettingOdds.php
return [
‘sports’ => [
‘soccer’ => ‘sport:1’,
‘tennis’ => ‘sport:2’,
‘basketball’ => ‘sport:3’,

],
];
使用下面的方式访问:
config(‘bettingOdds.sports.soccer’);
我更喜欢这样做:
BettingOdds.php
class BettingOdds extends Model
{
protected static $sports = [
‘soccer’ => ‘sport:1’,
‘tennis’ => ‘sport:2’,
‘basketball’ => ‘sport:3’,

];
}
然后访问它们:
BettingOdds::$sports[‘soccer’];
为什么这样?因为这样有益于后续操作:
class BettingOdds extends Model
{
protected static $sports = [
‘soccer’ => ‘sport:1’,
‘tennis’ => ‘sport:2’,
‘basketball’ => ‘sport:3’,

];
public function scopeSport($query, string $sport)
{
if (! isset(self::$sports[$sport])) {
return $query;
}

return $query->where(‘sport_id’, self::$sports[$sport]);
}
}
现在我们可以使用范围查询:
BettingOdds::sport(‘soccer’)->get();
使用集合替代原始的数组处理
在过去,我们通常以一种原始的方式使用数组:
$fruits = [‘apple’, ‘pear’, ‘banana’, ‘strawberry’];
foreach ($fruits as $fruit) {
echo ‘I have ‘. $fruit;
}
现在,我们可以使用一种高级的方法(译者注:集合的方式)处理数组中的数据。我们可以过滤、转换、遍历和修改数组中数据:
$fruits = collect($fruits);
$fruits = $fruits->reject(function ($fruit) {
return $fruit === ‘apple’;
})->toArray();
[‘pear’, ‘banana’, ‘strawberry’]
想要了解细节, 请查看 集合的文档.
当使用 查询构造器时,->get() 方法返回一个 Collection 实例。但要注意别搞混了 Collection 和 Query builder:

从 Query Builder 中,我们无法获取任何数据.。但我们有大量的查询相关的方法可以使用:orderBy(), where(),等等。
最终调用 ->get() 方法之后,数据被获取到,内存空间被消耗。它返回一个 Collection 实例。某些查询构造器不可用或者说可用但是方法名不同,关于这些请查阅 所有集合的方法。

如果你能在 Query Builder 层次过滤数据,就去做吧!不要依赖于等到结果 Collection 实例返回时再过滤 — 你将会消耗更多的内存空间。使用 Limit 限制结果条数,在 DB 层使用索引来加快查询。
善用扩展包、不要重复造轮子
如下是一些我在用的扩展包:

Laravel Blade Directives

Laravel CORS(跨域请求时,将你的路由限制为指定域名访问)

Laravel Tag Helper(在 Blade 内更方便地使用 HTML 标签)

Laravel Sluggable(在 Eloquent 模型内生成 Slug 时十分实用)

Laravel Responder(更容易地构建 JSON API)

Image Intervention(处理图片)

Horizon(使用少量配置即可管理队列)

Socialite(使用少量配置即可集成第三方社交媒体登录)

Passport(集成 OAuth 路由)

Spatie’s ActivityLog(追踪模型的修改活动)

Spatie’s Backup(备份文件和数据库)

Spatie’s Blade-X(定义你自己的 HTML 标签;可与 Laravel Tag Helper 结合)

Spatie’s Media Library(快速将模型与文件关联)

Spatie’s Response Cache(缓存控制器的完整响应内容)

Spatie’s Collection Macros(给集合添加更多宏)

以下是我(原文作者)编写的一些扩展包:

Befriended(类似社交媒体的点赞、收听、屏蔽操作)

Schedule(创建日程表并检查某个时间点是否可用)

Rating(为模型增加评分功能)

Guardian(易于使用的权限系统)

太难理解?联系我吧!
如果你有更多关于 Laravel 的问题,如果你需要运维方面的帮助,或者只是想说声 谢谢,你可以在 Twitter @rennokki 上找到我!
转自 PHP / Laravel 开发者社区 https://laravel-china.org/top…

正文完
 0