内容简介:Laravel 因可编写出干净,可用可调试的代码而为广大的 PHP 开发者所熟知。它同样也支持许许多多的功能,有时却未能在文档中体现,或者由于某种原因它们出现过又被移除了。我已经在生产环境中使用 Laravel 2 年了,从中我学到如何把代码变得更好,从我首次使用它以来我都充分发掘它的优势。接下来我将向你展示一些可能对你在用 Laravel 写代码时很有帮助的奥义之招。
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...
以上所述就是小编给大家介绍的《Laravel 5.7 最佳实践和开发技巧分享》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- vue项目实践004~~~一篮子的实践技巧
- iOS中MVP架构实践小技巧
- 45个有用的JavaScript技巧,窍门和最佳实践
- 一文看懂 MySQL 高性能优化技巧实践
- [译] 30 个 Python 最佳实践和技巧,你值得拥有
- 在Spring Cloud中使用OPENFEIGN技巧与最佳实践
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
JUnit Recipes中文版
陈浩等译 / 电子工业 / 2006-9 / 69.00元
《JUnit Recipes中文版:程序员实用测试技巧》主要介绍了在Java开发中使用JUnit进行单元测试的各种方法、原则、技巧与实践。本书出自开发一线专家之手,本着实用的原则,涵盖各类Java开发中应用JUnit的实用技巧,内容丰富、全面深入;无论对于需要应用JUnit进行单元测试的一线Java开发人员,还是JUnit入门、进阶者,本书都是一本不可多得的实用指南。这本书介绍了大量的JUnit实......一起来看看 《JUnit Recipes中文版》 这本书的介绍吧!