Laravel 代码开发最佳实践(持续更新)
我们这里要讨论的并不是 Laravel 版的 SOLID 原则(想要了解更多 SOLID 原则细节查看这篇文章)亦或是设计模式,而是 Laravel 实际开发中容易被忽略的最佳实践。
内容概览
- 单一职责原则
- 胖模型,瘦控制器
- 验证
- 业务逻辑应该放到服务类
- DRY(Don't Repeat Yourself,不要重复造轮子)
- 优先使用 Eloquent 而不是查询构建器和原生 SQL 查询,优先使用集合而不是数组
- 批量赋值
- 不要在 Blade 模板中执行查询 & 使用渴求式加载(避免 N+1 问题)
- 注释代码
- 不要把 JS 和 CSS 代码放到 Blade 模板里面,不要在 PHP 类中写 HTML 代码
- 使用配置、语言文件、常量而不是在代码中写死
- 使用社区接受的标准 Laravel 工具
- 遵循 Laravel 命名约定
- 使用更短的、可读性更好的语法
- 使用 IoC 容器或门面而不是创建新类
- 不要直接从
.env文件获取数据 - 以标准格式存储日期,使用访问器和修改器来编辑日期格式
- 其他好的实践
单一职责原则
一个类和方法只负责一项职责。
坏代码:
public function getFullNameAttribute()
{
if (auth()->user() && auth()->user()->hasRole('client') && auth()->user()->isVerified()) {
return 'Mr. ' . $this->first_name . ' ' . $this->middle_name . ' ' $this->last_name;
} else {
return $this->first_name[0] . '. ' . $this->last_name;
}
}
好代码:
public function getFullNameAttribute()
{
return $this->isVerifiedClient() ? $this->getFullNameLong() : $this->getFullNameShort();
}
public function isVerfiedClient()
{
return auth()->user() && auth()->user()->hasRole('client') && auth()->user()->isVerified();
}
public function getFullNameLong()
{
return 'Mr. ' . $this->first_name . ' ' . $this->middle_name . ' ' . $this->last_name;
}
public function getFullNameShort()
{
return $this->first_name[0] . '. ' . $this->last_name;
}
胖模型、瘦控制器
如果你使用的是查询构建器或原生 SQL 查询的话将所有 DB 相关逻辑都放到 Eloquent 模型或 Repository 类。
坏代码:
public function index()
{
$clients = Client::verified()
->with(['orders' => function ($q) {
$q->where('created_at', '>', Carbon::today()->subWeek());
}])
->get();
return view('index', ['clients' => $clients]);
}
好代码:
public function index()
{
return view('index', ['clients' => $this->client->getWithNewOrders()]);
}
Class Client extends Model
{
public function getWithNewOrders()
{
return $this->verified()
->with(['orders' => function ($q) {
$q->where('created_at', '>', Carbon::today()->subWeek());
}])
->get();
}
}
验证
将验证逻辑从控制器转移到请求类。
坏代码:
public function store(Request $request)
{
$request->validate([
'title' => 'required|unique:posts|max:255',
'body' => 'required',
'publish_at' => 'nullable|date',
]);
....
}
好代码:
public function store(PostRequest $request)
{
....
}
class PostRequest extends Request
{
public function rules()
{
return [
'title' => 'required|unique:posts|max:255',
'body' => 'required',
'publish_at' => 'nullable|date',
];
}
}
业务逻辑需要放到服务类
一个控制器只负责一项职责,所以需要把业务逻辑都转移到服务类中。
坏代码:
public function store(Request $request)
{
if ($request->hasFile('image')) {
$request->file('image')->move(public_path('images') . 'temp');
}
....
}
好代码:
public function store(Request $request)
{
$this->articleService->handleUploadedImage($request->file('image'));
....
}
class ArticleService
{
public function handleUploadedImage($image)
{
if (!is_null($image)) {
$image->move(public_path('images') . 'temp');
}
}
}
DRY
尽可能复用代码,单一职责原则可以帮助你避免重复,此外,尽可能复用 Blade 模板,使用 Eloquent 作用域。
坏代码:
public function getActive()
{
return $this->where('verified', 1)->whereNotNull('deleted_at')->get();
}
public function getArticles()
{
return $this->whereHas('user', function ($q) {
$q->where('verified', 1)->whereNotNull('deleted_at');
})->get();
}
好代码:
public function scopeActive($q)
{
return $q->where('verified', 1)->whereNotNull('deleted_at');
}
public function getActive()
{
return $this->active()->get();
}
public function getArticles()
{
return $this->whereHas('user', function ($q) {
$q->active();
})->get();
}
优先使用 Eloquent 和 集合
通过 Eloquent 可以编写出可读性和可维护性更好的代码,此外,Eloquent 还提供了强大的内置工具如软删除、事件、作用域等。
坏代码:
SELECT *
FROM `articles`
WHERE EXISTS (SELECT *
FROM `users`
WHERE `articles`.`user_id` = `users`.`id`
AND EXISTS (SELECT *
FROM `profiles`
WHERE `profiles`.`user_id` = `users`.`id`)
AND `users`.`deleted_at` IS NULL)
AND `verified` = '1'
AND `active` = '1'
ORDER BY `created_at` DESC
好代码:
Article::has('user.profile')->verified()->latest()->get();
批量赋值
关于批量赋值细节可查看对应文档。
坏代码:
$article = new Article;
$article->title = $request->title;
$article->content = $request->content;
$article->verified = $request->verified;
// Add category to article
$article->category_id = $category->id;
$article->save();
好代码:
$category->article()->create($request->all());
不要在 Blade 执行查询 & 使用渴求式加载
坏代码:
@foreach (User::all() as $user)
{{ $user->profile->name }}
@endforeach
好代码:
$users = User::with('profile')->get();
...
@foreach ($users as $user)
{{ $user->profile->name }}
@endforeach
注释你的代码
坏代码:
if (count((array) $builder->getQuery()->joins) > 0)
好代码:
// Determine if there are any joins.
if (count((array) $builder->getQuery()->joins) > 0)
最佳:
if ($this->hasJoins())
将前端代码和 PHP 代码分离:
不要把 JS 和 CSS 代码写到 Blade 模板里,也不要在 PHP 类中编写 HTML 代码。
坏代码:
let article = `{{ json_encode($article) }}`;
好代码:
<input id="article" type="hidden" value="{{ json_encode($article) }}">
或者
<button class="js-fav-article" data-article="{{ json_encode($article) }}">{{ $article->name }}<button>
在 JavaScript 文件里:
let article = $('#article').val();
使用配置、语言文件和常量取代硬编码
坏代码:
public function isNormal()
{
return $article->type === 'normal';
}
return back()->with('message', 'Your article has been added!');
好代码:
public function isNormal()
{
return $article->type === Article::TYPE_NORMAL;
}
return back()->with('message', __('app.article_added'));
使用被社区接受的标准 Laravel 工具
优先使用 Laravel 内置功能和社区版扩展包,其次才是第三方扩展包和工具。这样做的好处是降低以后的学习和维护成本。
| 任务 | 标准工具 | 第三方工具 |
|---|---|---|
| 授权 | 策略类 | Entrust、Sentinel等 |
| 编译资源 | Laravel Mix | Grunt、Gulp等 |
| 开发环境 | Homestead | Docker |
| 部署 | Laravel Forge | Deployer等 |
| 单元测试 | PHPUnit、Mockery | Phpspec |
| 浏览器测试 | Laravel Dusk | Codeception |
| DB | Eloquent | SQL、Doctrine |
| 模板 | Blade | Twig |
| 处理数据 | Laravel集合 | 数组 |
| 表单验证 | 请求类 | 第三方扩展包、控制器中验证 |
| 认证 | 内置功能 | 第三方扩展包、你自己的解决方案 |
| API认证 | Laravel Passport | 第三方 JWT 和 OAuth 扩展包 |
| 创建API | 内置功能 | Dingo API和类似扩展包 |
| 处理DB结构 | 迁移 | 直接操作DB |
| 本地化 | 内置功能 | 第三方工具 |
| 实时用户接口 | Laravel Echo、Pusher | 第三方直接处理 WebSocket的扩展包 |
| 生成测试数据 | 填充类、模型工厂、Faker | 手动创建测试数据 |
| 任务调度 | Laravel Task Scheduler | 脚本或第三方扩展包 |
| DB | MySQL、PostgreSQL、SQLite、SQL Server | MongoDB |
遵循 Laravel 命名约定
遵循 PSR 标准。此外,还要遵循 Laravel 社区版的命名约定:
| What | How | Good | Bad |
|---|---|---|---|
| 控制器 | 单数 | ArticleController | ArticlesController |
| 路由 | 复数 | articles/1 | article/1 |
| 命名路由 | 下划线+'.'号分隔 | users.show_active | users.show-active,show-active-users |
| 模型 | 单数 | User | Users |
| 一对一关联 | 单数 | articleComment | articleComments,article_comment |
| 其他关联关系 | 复数 | articleComments | articleComment,article_comments |
| 数据表 | 复数 | article_comments | article_comment,articleComments |
| 中间表 | 按字母表排序的单数格式 | article_user | user_article,article_users |
| 表字段 | 下划线,不带模型名 | meta_title | MetaTitle; article_meta_title |
| 外键 | 单数、带_id后缀 | article_id | ArticleId, id_article, articles_id |
| 主键 | - | id | custom_id |
| 迁移 | - | 2017_01_01_000000_create_articles_table | 2017_01_01_000000_articles |
| 方法 | 驼峰 | getAll | get_all |
| 资源类方法 | 文档 | store | saveArticle |
| 测试类方法 | 驼峰 | testGuestCannotSeeArticle | test_guest_cannot_see_article |
| 变量 | 驼峰 | $articlesWithAuthor | $articles_with_author |
| 集合 | 复数 | $activeUsers = User::active()->get() | $active, $data |
| 对象 | 单数 | $activeUser = User::active()->first() | $users, $obj |
| 配置和语言文件索引 | 下划线 | articles_enabled | ArticlesEnabled; articles-enabled |
| 视图 | 下划线 | show_filtered.blade.php | showFiltered.blade.php, show-filtered.blade.php |
| 配置 | 下划线 | google_calendar.php | googleCalendar.php, google-calendar.php |
| 契约(接口) | 形容词或名词 | Authenticatable | AuthenticationInterface, IAuthentication |
| Trait | 形容词 | Notifiable | NotificationTrait |
使用缩写或可读性更好的语法
坏代码:
$request->session()->get('cart');
$request->input('name');
好代码:
session('cart');
$request->name;
更多示例:
| 通用语法 | 可读性更好的 |
|---|---|
Session::get('cart') |
session('cart') |
$request->session()->get('cart') |
session('cart') |
Session::put('cart', $data) |
session(['cart' => $data]) |
$request->input('name'), Request::get('name') |
$request->name, request('name') |
return Redirect::back() |
return back() |
is_null($object->relation) ? $object->relation->id : null } |
optional($object->relation)->id |
return view('index')->with('title', $title)->with('client', $client) |
return view('index', compact('title', 'client')) |
$request->has('value') ? $request->value : 'default'; |
$request->get('value', 'default') |
Carbon::now(), Carbon::today() |
now(), today() |
App::make('Class') |
app('Class') |
->where('column', '=', 1) |
->where('column', 1) |
->orderBy('created_at', 'desc') |
->latest() |
->orderBy('age', 'desc') |
->latest('age') |
->orderBy('created_at', 'asc') |
->oldest() |
->select('id', 'name')->get() |
->get(['id', 'name']) |
->first()->name |
->value('name') |
使用 IoC 容器或门面
自己创建新的类会导致代码耦合度高,且难于测试,取而代之地,我们可以使用 IoC 容器或门面。
坏代码:
$user = new User;
$user->create($request->all());
好代码:
public function __construct(User $user)
{
$this->user = $user;
}
....
$this->user->create($request->all());
不要从直接从 .env 获取数据
传递数据到配置文件然后使用 config 辅助函数获取数据。
坏代码:
$apiKey = env('API_KEY');
好代码:
// config/api.php
'key' => env('API_KEY'),
// Use the data
$apiKey = config('api.key');
以标准格式存储日期
使用访问器和修改器来编辑日期格式。
坏代码:
{{ Carbon::createFromFormat('Y-d-m H-i', $object->ordered_at)->toDateString() }}
{{ Carbon::createFromFormat('Y-d-m H-i', $object->ordered_at)->format('m-d') }}
好代码:
// Model
protected $dates = ['ordered_at', 'created_at', 'updated_at']
public function getMonthDayAttribute($date)
{
return $date->format('m-d');
}
// View
{{ $object->ordered_at->toDateString() }}
{{ $object->ordered_at->monthDay }}
其他好的实践
不要把任何业务逻辑写到路由文件中。
在 Blade 模板中尽量不要编写原生 PHP。
声明:本文整理自 GitHub 项目 https://github.com/nonfu/laravel-best-practices,持续更新中。
Laravel 代码开发最佳实践(持续更新)的更多相关文章
- Laravel 代码开发最佳实践
我们这里要讨论的并不是 Laravel 版的 SOLID 原则(想要了解更多 SOLID 原则细节查看这篇文章)亦或是设计模式,而是 Laravel 实际开发中容易被忽略的最佳实践. 内容概览 单一职 ...
- Salesforce 开发整理(五)代码开发最佳实践
在Salesforce项目实施过程中,对项目代码的维护可以说占据极大的精力,无论是因为项目的迭代,还是需求的变更,甚至是项目组成员的变动,都不可避免的需要维护之前的老代码,而事实上,几乎没有任何一个项 ...
- Web前端开发最佳实践(2):前端代码重构
前言 代码重构是业内经常讨论的一个热门话题,重构指的是在不改变代码外部行为的情况下进行源代码修改,所以重构之前需要考虑的是重构后如何才能保证外部行为不改变.对于后端代码来说,可以通过大量的自动化测试来 ...
- Web前端开发最佳实践(9):CSS代码太太乱,重复代码太多?你需要精简CSS代码
前言 提高网站整体加载速度的一个重要手段就是提高代码文件的网络传输速度.之前提到过,所有的代码文件都应该是经过压缩了的,这可提高网络传输速度,提高性能.除了压缩代码之外,精简代码也是一种减小代码文件大 ...
- Web前端开发最佳实践(3):前端代码和资源的压缩与合并
一般在网站发布时,会压缩前端HTML.CSS.JavaScript代码及用到的资源文件(主要是图片文件),目的是加快文件在网络中的传输,让网页更快的展现.当然,CDN分发.缓存等方式也是加快代码或资源 ...
- [转]Android开发最佳实践
——欢迎转载,请注明出处 http://blog.csdn.net/asce1885 ,未经本人同意请勿用于商业用途,谢谢—— 原文链接:https://github.com/futurice/and ...
- Android和PHP开发最佳实践
Android和PHP开发最佳实践 <Android和PHP开发最佳实践>基本信息作者: 黄隽实丛书名: 移动应用开发技术丛书出版社:机械工业出版社ISBN:9787111410508上架 ...
- iOS应用开发最佳实践
<iOS应用开发最佳实践> 基本信息 作者: 王浩 出版社:电子工业出版社 ISBN:9787121207679 上架时间:2013-7-22 出版日期:2013 年8月 开本:16 ...
- Android开发最佳实践
Android开发最佳实践 摘要 ●使用 Gradle 和它推荐的工程结构 ●把密码和敏感数据放在gradle.properties ●不要自己写 HTTP 客户端,使用Volley或OkHttp库 ...
- web前端开发最佳实践笔记
一.文章开篇 由于最近也比较忙,一方面是忙着公司的事情,另外一方面也是忙着看书和学习,所以没有时间来和大家一起分享知识,现在好了,终于回归博客园的大家庭了,今天我打算来分享一下关于<web前端开 ...
随机推荐
- 2003031126-石升福-python数据分析第四周作业-第二次作业
项目 matplotlib 博客名称 2003031126-石升福-Python数据分析第四周作业 班级链接 20级数据班 作业链接 第二次作业 要求 每道题要有题目,代码(使用插入代码,不会插入代码 ...
- ybtoj 12F
求值的话改为求解前缀和的值,通过两个前缀和相减即可得到每个值. 每次询问相当于给一个方程. 一共有 $n$ 个未知数,因此需要 $n$ 个方程,同时每个数都必须至少在方程中出现一次. 最小生成树求解即 ...
- jjencode|JS混淆手工解析编码过程以及自定义混淆变体改装
此前发布过关于aaencode的混淆编码,此篇文章继续说同作者的jjencode混淆编码的具体过程. 介绍 首先是jjencode的作者提供的编码测试页:http://utf-8.jp/public/ ...
- Java VSCode 基础教学
VSCode 超全设置1.下载2.插件安装3.项目创建4.设置5.快捷键6.优化7.导出 Jar 包 VSCode 超全设置 VSCode(Visual Studio Code) 是一款 Micros ...
- C#中静态字段声明时赋值与构造函数中赋值
C#中静态字段是属于类的,访问的时候:用类名点成员名的方式. 赋值初始化的时候,可以在声明的时候赋值,也可以在静态构造函数中赋值. 如果在声明中.静态构造函数中都赋值,那么最终该成员的值,是取自哪一个 ...
- python-GUI-pyqt5之文件加密解密工具
pyqt5的文件加密解密程序,用到base64,rsa和aes进行混合解密,代码比较杂乱,可自行整理,仅供学习参考之用,如需转载,请联系博主或附上博客链接,下面直接干货. 程序截图如下: # -*- ...
- UniDBGrid控件的几个功能优化
内容自动换行 默认UniDBGrid的cell内的内容如果超出表格列宽并不自动换行和调整行高,给客户带来极大的不方便,通过修改ServerModule的属性CustomCSS可以实现. <sty ...
- CSS伪类三角形
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- 手把手带你玩转Linux
今天这篇文章带你走进Linux世界的同时,带你手把手玩转Linux,加深对Linux系统的认识. 一.搞好Linux工作必须得不断折腾,说白了,只是动手力量必须强.我在初学Linux的那片,家中三台计 ...
- 网络安全(中职组)-B模块:Web安全应用-2
Web安全应用-2 任务环境说明: √ 服务器场景: match_win03-3-1(关闭链接) √ 服务器场景操作系统:Windows Server 2003 1. 使用渗透机Kali Linux, ...