我们这里要讨论的并不是 Laravel 版的 SOLID 原则(想要了解更多 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 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,持续更新中。

转载:https://laravelacademy.org/post/8464.html

Laravel 代码开发最佳实践(持续更新)的更多相关文章

  1. Laravel 代码开发最佳实践

    我们这里要讨论的并不是 Laravel 版的 SOLID 原则(想要了解更多 SOLID 原则细节查看这篇文章)亦或是设计模式,而是 Laravel 实际开发中容易被忽略的最佳实践. 内容概览 单一职 ...

  2. Salesforce 开发整理(五)代码开发最佳实践

    在Salesforce项目实施过程中,对项目代码的维护可以说占据极大的精力,无论是因为项目的迭代,还是需求的变更,甚至是项目组成员的变动,都不可避免的需要维护之前的老代码,而事实上,几乎没有任何一个项 ...

  3. Web前端开发最佳实践(2):前端代码重构

    前言 代码重构是业内经常讨论的一个热门话题,重构指的是在不改变代码外部行为的情况下进行源代码修改,所以重构之前需要考虑的是重构后如何才能保证外部行为不改变.对于后端代码来说,可以通过大量的自动化测试来 ...

  4. Web前端开发最佳实践(9):CSS代码太太乱,重复代码太多?你需要精简CSS代码

    前言 提高网站整体加载速度的一个重要手段就是提高代码文件的网络传输速度.之前提到过,所有的代码文件都应该是经过压缩了的,这可提高网络传输速度,提高性能.除了压缩代码之外,精简代码也是一种减小代码文件大 ...

  5. Web前端开发最佳实践(3):前端代码和资源的压缩与合并

    一般在网站发布时,会压缩前端HTML.CSS.JavaScript代码及用到的资源文件(主要是图片文件),目的是加快文件在网络中的传输,让网页更快的展现.当然,CDN分发.缓存等方式也是加快代码或资源 ...

  6. [转]Android开发最佳实践

    ——欢迎转载,请注明出处 http://blog.csdn.net/asce1885 ,未经本人同意请勿用于商业用途,谢谢—— 原文链接:https://github.com/futurice/and ...

  7. Android和PHP开发最佳实践

    Android和PHP开发最佳实践 <Android和PHP开发最佳实践>基本信息作者: 黄隽实丛书名: 移动应用开发技术丛书出版社:机械工业出版社ISBN:9787111410508上架 ...

  8. iOS应用开发最佳实践

    <iOS应用开发最佳实践> 基本信息 作者: 王浩    出版社:电子工业出版社 ISBN:9787121207679 上架时间:2013-7-22 出版日期:2013 年8月 开本:16 ...

  9. Android开发最佳实践

    Android开发最佳实践 摘要 ●使用 Gradle 和它推荐的工程结构 ●把密码和敏感数据放在gradle.properties ●不要自己写 HTTP 客户端,使用Volley或OkHttp库 ...

  10. web前端开发最佳实践笔记

    一.文章开篇 由于最近也比较忙,一方面是忙着公司的事情,另外一方面也是忙着看书和学习,所以没有时间来和大家一起分享知识,现在好了,终于回归博客园的大家庭了,今天我打算来分享一下关于<web前端开 ...

随机推荐

  1. Stram流 - 随笔

    函数式编程 注重函数 - 关注对数据进行了什么操作 流 中间操作 去重 authors.stream() .distinct() ; 查询指定匹配 .filter(new Predicate<A ...

  2. Websocket是什么?

    一. WebSocket是什么? Websocket是一种网络通信协议,是一个在计算机里专门在[两点]之间传输数据的约定和规范. 二. 为什么存在WebSocket? 因为 HTTP 协议有一个缺陷: ...

  3. 【BFS】算法模板与思路

    1.BFS算法的基础理论是什么? BFS算法名叫宽度优先搜索,虽然我能理解深度优先搜索,但我却不是很能理解宽度优先搜索. 一个很关键的点在于:宽度优先搜索是一个迭代的算法,不是递归的算法. 与DFS之 ...

  4. Java学习笔记(二)环境

     卸载JDK 1.删除java的安装目录 2.删除JAVA_HOME 3.删除path下关于java的目录 4.java -version 配置环境变量 1.我的电脑-->右键-->属性 ...

  5. DB help

    using Dapper; using System; using System.Collections; using System.Collections.Generic; using System ...

  6. jxg项目Day5-关于项目打包

    springboot项目中手动打包,加以下依赖: <parent> <artifactId>spring-boot-dependencies</artifactId> ...

  7. JavaScript for in循环,for of循环

    一.JavaScript for/in 语句循环遍历对象的属性 var person={fname:"Bill",lname:"Gates",age:56}; ...

  8. 第一天1h

    //摄氏度和华氏度之间的换算//20211120//ZhangWenjing#include<stdio.h>int main(void){ int f = 0; int c = 0; s ...

  9. 基于工业4g网关的危化品运输车监控方案

    工业的发展立足于各种各样原材料的加工和应用,而其中就包括一些油料.化学品和易燃易爆货物,针对此类货物的运输,需要着重关注其安全性和稳定性,否则就容易造成严重的人身和财产损失.得益于物联网技术的发展,现 ...

  10. NX二次开发 批量导出X_T(包括部件里的装配体)

    VS2019 NX1946 UF_initialize(); string filepath = strDir + "\\";//拼接路径 //获取图层过滤 PropertyLis ...