Laravel 的十八个最佳实践
本文翻译改编自 Laravel 的十八个最佳实践
这篇文章并不是什么由 Laravel 改编的 SOLID 原则、模式等。
只是为了让你注意你在现实生活的 Laravel 项目中最常忽略的内容。
单一责任原则
一个类和一个方法应该只有一个职责。
错误的做法:
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 isVerifiedClient()
{
    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 来查询,请将所有与数据库相关的逻辑放入 Eloquent 模型或存储库类中。
坏:
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)
尽可能重用代码。 SRP(单一职责原则)正在帮助你避免重复。当然,这也包括了 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 而不是 Query Builder 和原生的 SQL 查询。要优先于数组的集合
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 模板中执行查询并使用关联加载(N + 1 问题)
不好的地方在于,这对于 100 个用户来说,等于执行 101 个 DB 查询:
@foreach (User::all() as $user)
    {{ $user->profile->name }}
@endforeach下面的做法,对于 100 个用户来说,仅仅只执行 2 个 DB 查询:
$users = User::with('profile')->get();
...
@foreach ($users as $user)
    {{ $user->profile->name }}
@endforeach与其花尽心思给你的代码写注释,还不如对方法或变量写一个描述性的名称
坏:
if (count((array) $builder->getQuery()->joins) > 0)好:
// 确定是否有任何连接。
if (count((array) $builder->getQuery()->joins) > 0)最好:
if ($this->hasJoins())不要把 JS 和 CSS 放在 Blade 模板中,也不要将任何 HTML 放在 PHP 类中
坏:
let article = `{{ json_encode($article) }}`;好:
<input id="article" type="hidden" value="{{ json_encode($article) }}">
Or
<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 功能和社区软件包,而不是其他第三方软件包和工具。因为将来与你的应用程序一起工作的开发人员都需要学习新的工具。另外,使用第三方软件包或工具的话,如果遇到困难,从 Laravel 社区获得帮助的机会会大大降低。不要让你的客户为此付出代价!
| 任务 | 标准工具 | 第三方工具 | 
|---|---|---|
| 授权 | Policies | Entrust, Sentinel and other packages | 
| 前端编译 | Laravel Mix | Grunt, Gulp, 3rd party packages | 
| 开发环境 | Homestead | Docker | 
| 部署 | Laravel Forge | Deployer and other solutions | 
| 单元测试 | PHPUnit, Mockery | Phpspec | 
| 浏览器测试 | Laravel Dusk | Codeception | 
| 数据库操作 | Eloquent | SQL, Doctrine | 
| 模板 | Blade | Twig | 
| 数据操作 | Laravel collections | Arrays | 
| 表单验证 | Request classes | 3rd party packages, validation in controller | 
| 认证 | Built-in | 3rd party packages, your own solution | 
| API 认证 | Laravel Passport | 3rd party JWT and OAuth packages | 
| 创建 API | Built-in | Dingo API and similar packages | 
| 数据库结构操作 | Migrations | Working with DB structure directly | 
| 局部化 | Built-in | 3rd party packages | 
| 实时用户接口 | Laravel Echo, Pusher | 3rd party packages and working with WebSockets directly | 
| Generating testing data | Seeder classes, Model Factories, Faker | Creating testing data manually | 
| 生成测试数据 | Laravel Task Scheduler | Scripts and 3rd party packages | 
| 数据库 | MySQL, PostgreSQL, SQLite, SQL Server | MongoDB | 
遵循 Laravel 命名约定
遵循 PSR 标准。 另外,请遵循 Laravel 社区接受的命名约定:
| 类型 | 规则 | 正确示例 | 错误示例 | 
|---|---|---|---|
| Controller | 单数 | ArticleController | |
| Route | 复数 | articles/1 | |
| Named route | 带点符号的蛇形命名 | users.show_active | |
| Model | 单数 | User | |
| hasOne or belongsTo relationship | 单数 | articleComment | |
| All other relationships | 复数 | articleComments | |
| Table | 复数 | article_comments | |
| Pivot table | 按字母顺序排列的单数模型名称 | article_user | |
| Table column | 带着模型名称的蛇形命名 | meta_title | |
| Foreign key | 带_id 后缀的单数型号名称 | article_id | |
| Primary key | - | id | |
| Migration | - | 2017_01_01_000_create_xx_table | |
| Method | 小驼峰命名 | getAll | |
| Method in resource controller | 具体看表格 | store | |
| Method in test class | 小驼峰命名 | testGuestCannotSeeArticle | |
| Variable | 小驼峰命名 | $articlesWithAuthor | |
| Collection | 具描述性的复数形式 | $activeUsers = User::active()->get() | |
| Object | 具描述性的单数形式 | $activeUser = User::active()->first() | |
| Config and language files index | 蛇形命名 | articles_enabled | |
| View | 蛇形命名 | show_filtered.blade.php | |
| Config | 蛇形命名 | google_calendar.php | |
| Contract (interface) | 形容词或名词 | Authenticatable | |
| Trait | 形容词 | Notifiable | 
尽可能使用更短、更易读的语法
坏:
$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 容器或 facades 代替新的 Class
新的 Class 语法创建类时,不仅使得类与类之间紧密耦合,还加重了测试的复杂度。推荐改用 IoC 容器或 facades。
坏:
$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 模板中最小化 vanilla PHP 的使用。
- 转载:https://learnku.com/articles/12762/eighteen-best-practices-of-laravel
Laravel 的十八个最佳实践的更多相关文章
- JavaScript基础笔记(十四)最佳实践
		最佳实践 一)松散耦合 1.解耦HTML/JavaScript: 1)避免html种使用js 2)避免js种创建html 2.解耦CSS/JS 操作类 3.解耦应用逻辑和事件处理 以下是要牢记的应用和 ... 
- 轻量ORM-SqlRepoEx (十六)最佳实践之Dapper(2)
		简介:SqlRepoEx是 .Net平台下兼容.NET Standard 2.0人一个轻型的ORM.解决了Lambda转Sql语句这一难题,SqlRepoEx使用的是Lambda表达式,所以,对c#程 ... 
- Java日志体系(八)最佳实践
		java常用日志框架关系 Log4j 2与Log4j 1发生了很大的变化,Log4j 2不兼容Log4j 1. Logback必须配合Slf4j使用.由于Logback和Slf4j是同一个作者,其兼容 ... 
- 轻量ORM-SqlRepoEx (十四)最佳实践之Dapper(1)
		简介:SqlRepoEx是 .Net平台下兼容.NET Standard 2.0人一个轻型的ORM.解决了Lambda转Sql语句这一难题,SqlRepoEx使用的是Lambda表达式,所以,对c#程 ... 
- (十八)TableView实践(多组汽车品牌展示)
		对于多组数据,可能会用到模型的嵌套. 例如多组汽车,每组是一个模型,组内有多辆车的信息,每辆车的信息也是一个模型,相当于模型中有模型. 可以看到,每个item是一个字典,这要创建一个模型,而模型内部的 ... 
- 软件工程-XP方法十二个最佳实践
- 轻量ORM-SqlRepoEx (十五)最佳实践之数据映射(Map)
		简介:SqlRepoEx是 .Net平台下兼容.NET Standard 2.0人一个轻型的ORM.解决了Lambda转Sql语句这一难题,SqlRepoEx使用的是Lambda表达式,所以,对c#程 ... 
- JMeter 十四:最佳实践
		参考:http://jmeter.apache.org/usermanual/best-practices.html 1. 总是使用最新版本的JMeter 2. 使用合适数目的Thread Threa ... 
- 【原创】Docker实战 Dockerfile最佳实践&&容器之间通信
		官方最佳实践文档 https://docs.docker.com/develop/develop-images/dockerfile_best-practices/#from Docker实战(三十) ... 
随机推荐
- # linux文件系统(inode block superblock)
			先说一下格式化:每种操作系统所设置的文件属性/权限并不相同,为了存放这些文件所需的数据,因此就需要将分区格式化,以成为操作系统能够利用的文件系统格式.linux的文件格式为Ext2/Ext3,现在好像 ... 
- 【vue】vue中实现导出excel
			1.安装依赖 cnpm install -S file-saver xlsx cnpm install -D script-loader 2.例如在src文件夹中新建一个名为vendor(vendor ... 
- 网站建设部署与发布--笔记4-部署mysql
			部署MySQL 1.更新操作系统 $ yum update -y 2.安装mysql数据库,在CentOS 7.2 中,使用了mariadb替代了官方的mysql $ yum install mari ... 
- 11 python初学 (文件)
			对文件的操作分为 3 步: 打开文件: f = open('望月怀古', 'r', encoding='utf8') # 路径可以写绝对路径,也可以写相对路径: 操作 关闭文件: f.close() ... 
- StackExchange.Redis使用配置
			转自:http://www.cnblogs.com/deosky/p/4848403.html Configurationredis有很多不同的方法来配置连接字符串 , StackExchange.R ... 
- Web组件流畅拖动效果
			拖动效果,可以形象的帮助用户处理一些问题,比如Windows删除文件,只需将文件拖动至回收站即可.比起右键显得更形象,我觉得更好玩一点^_^.当然,在其他许多方面,其实也有用到拖动效果,只是他们不是那 ... 
- javascript状态机及在工作流中的应用
			#javascript状态机及在工作流中的应用 ##状态机 什么叫状态机(Finite State Machine),书面上的解释可以自己借助搜索引擎寻找到.通俗地来讲是一个状态定义.查找.切换和事件 ... 
- html设置 hight100%问题
			Web浏览器在计算有效宽度时会考虑浏览器窗口的打开宽度.如果你不给宽度设定任何缺省值,那浏览器会自动将页面内容平铺填满整个横向宽度.但是高度的计算方式完全不一样.事实上,浏览器根本就不计算内容的高度, ... 
- elasticsearch(6.2.3)安装Head插件
			一.安装elasticsearch,参照:https://www.cnblogs.com/dyh004/p/8872443.html 二.安装nodejs,参照:https://www.runoob. ... 
- 01 Django REST Framework 介绍
			01-Django REST Framework的介绍 Django REST框架是一个用于构建Web API的强大而灵活的工具包. 您可能希望使用REST框架的一些原因: 1. Web可浏览API对 ... 
