Laravel 代码简洁之道(Clean Code)
作为生活的一部分,有时候你必须把恶心的事情搞定。编写整洁的代码将帮助您做到这一点!每个人都有不同的风格,代码可以随着时间的推移而改进。
FormRequests
FormRequests 从 Laravel 5.0 开始就存在了。这个我不知道,你可能也不知道。
传统验证
// UserController.php
class UserController extends Controller {
public function create() {
$validator = Validator::make($request->all(), [
'name' => 'required',
'email' => 'unique:users,email',
]);
if($validator->fails()) {
return response()->json(['success' => false])
}
// ...
}
}
让代码不言自明。 每次验证时,您的控制器都会变得臃肿。 通过依赖注入编写可扩展的代码。
$ php artisan make:request CreateUserPost
class CreateUserPost extends FormRequest {
public $rules = [
'name' => 'required',
'email' => ['unique:users','email']
];
public function authorize()
{
return true;
}
public function rules()
{
return $this->rules;
}
}
// UserController.php 现在变成了
class UserController extends Controller {
public function create(CreateUserPost $request)
{
// 如果失败将会抛出 ValidationException
$validatedData = $request->validated();
// ...
}
}
服务层抽象
与在 Controller 中包含所有业务逻辑不同,添加服务层可以提高应用程序的可读性、可重用性和整体结构。
开始
创建一个新的服务将节省您的时间。
// InboxService.php
class InboxService {
public function getMessages(User $user): Mail
{
$messages = (new Mail())
->where('to', $user->id)
->orderBy('created_at', 'DESC')
->get();
return $messages;
}
}
这是一个简单的示例,Controller 已经检查用户查看邮箱的权限(策略)。
现在,服务层负责任何不属于控制器的复杂逻辑,比如获取数据、变异数据、与内部 api 交互等。
注意: 有多种编写模型查询的方法。
- Eloquent: 提供更简单的查询和关联
Model::where(‘id’, $id)->first();
(new Model())->where(‘id’, $id)->first();
- DB: 对于大型数据库
DB::table(‘model’)->where(‘id’, $id)->first();
- Mix: 对于方法链接和包开发有用
$query = Model::query();
// MessageSearchService.php
class MessageSearchService
{
private $query;
public function __construct()
{
$this->query = Message::query();
}
public function getMessages(User $user): Builder
{
return $this->query->where(function ($q) {
$q->where('user_id', '!=', $user->id);
});
}
public function getFavorited(bool $favorited = true): Builder
{
return $this->getMessages()->where(function ($q) {
return $q->where('is_favorited', favorited);
});
}
}
// 例如在一个 Controller 使用
// 将业务逻辑加载到服务层 $searchService;
public function __construct(MessageSearchService $searchService)
{
$this->searchService = $searchService;
}
public function getAllMessages(): Collection
{
return $this->searchService->getMessages(auth()->user())->get();
}
public function getFavoriteMessages(): Collection
{
return $this->searchService->getFavorited()->get();
}
模型范围和查询构造器
模型范围允许您封装和干掉代码,特别是处理数据库。 通过多个服务进行的大量高级查询可能会使您的代码变得臃肿。 您可以快速使代码易于阅读、测试和重用。
// SuperYacht.php
class SuperYacht extends Model
{
/**
* 将查询范围限定为仅获取未读消息。
*/
public function scopeType($query, string $type = 'Express')
{
return $query->where('type', $type);
}
/**
* 将查询范围限定为仅包括活动用户。
* @param array $range Range in meters
*/
public function scopeLength($query, array $range = [50, 200])
{
// 无需 if/else 即可轻松返回查询构建器
return empty($range)
? $query
: $query->whereBetween('length', $range);
}
}
// SuperYachtController.php
class SuperYachtController extends Controller
{
private $defaultRange = [25, 100];
public function show(int $id): ?SuperYacht
{
return SuperYacht::where('id', $id)
->type('Cruiser')
->length($this->defaultRange)
->first();
}
}
原始解决方案的问题
在应用我们的动态和灵活查询范围后,Superyacht正在从代码的重量中下沉。
class SuperYacht extends Model
{
protected $fillable = ['name', 'type', 'length_m', 'length_ft'];
protected $appends = ['is_available'];
public function getIsAvailableAttribute(): bool
{
return (bool) $this->is_available;
}
public function rooms()
{
return $this->hasMany(Room::class);
}
public function scopeType($query, string $type = 'Express')
{
return $query->where('type', $type);
}
...
}
第二种解决方案
将查询范围移动到它自己的 Trait。 当您不需要复杂的服务层时,特征允许您跨模型、中间件、服务等重用代码。
// SearchableTrait.php
trait SearchableTrait
{
public function scopeType($query, string $type = 'Express')
{
return $query->where('type', $type);
}
}
// SuperYacht.php
class SuperYacht extends Model
{
use SearchableTrait;
protected $fillable = [...];
protected $appends = [...];
public function (){}
}
查询生成器命令
相比其他内容,这部分比较高级, 可能需要一些时间来阅读和消化它。
在 AppServiceProvider.php -> boot() 方法中添加以下内容
/**
* Usage: Model::whereLike('name', ['mike', 'joe'])->get();
* Relationships: Model::whereLike('message.type', ['text', 'sms'])->get();
*/
Builder::macro('whereLike', function ($attributes, $terms) {
$this->where(function ($query) use ($attributes, $terms) {
foreach (array_wrap($attributes) as $attribute) {
// 如果数组只有一个元素的话, 把元素的值封装在数组里 例如: $term = [$term];
foreach (array_wrap($terms) as $term) {
// 当 whereLike 包含一个关联值时,搜索这个关联值
$query->when(str_contains($attribute, '.'),
function ($query) use ($attribute, $term) {
[$relationName, $relationAttribute] = explode('.', $attribute);
// 验证当前查询中是否存在这个关联
$query->orWhereHas($relationName, function ($query) use ($relationAttribute, $term) {
$query->where($relationAttribute, 'LIKE', "%{$term}%");
});
},
//当字符串中不包含关联对象时进行的回退
function ($query) use ($attribute, $term) {
$query->orWhere($attribute, 'LIKE', "%{$term}%");
}
);
}
}
});
// 最后返回 $query变量, 所以你可以调用其中的一些方法,比如 ->get(), ->first(), ->where() 等等
return $this;
});
}
从模型调用生成器 Macro
// 常规查询
$message = Message::whereLike('subject', ['Dear', 'Hello!'])->get();// 关联查询
$message = Message::whereLike('type.name', ['text', 'sms'])->get();
结束笔记——编写代码文档可能会让人喘不过气——花时间充分掌握和应用你的知识将使你成为一个更好的开关人员。
本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。
推荐文章: