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模型或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(); } }
使用自定义request类来进行验证
把验证规则放到 request 类中.
例子:
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的 scopes 方法来实现代码。
例子:
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(); }
使用orm而不是纯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->validated());
不要在模板中查询,尽量使用惰性加载
例子 (对于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($article)"> or <button class="js-fav-article" data-article="@json($article)">{{ $article->name }}<button>
在javascript文件中加上:
let article = $('#article').val();
当然最好的办法还是使用专业的php的js包传输数据。
在代码中使用配置、语言包和常量,而不是使用硬编码
例子:
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 或者其他扩展包 |
资源编译工具 | laravel mix | grunt, gulp, 或者其他第三方包 |
开发环境 | homestead | docker |
部署 | laravel forge | deployer 或者其他解决方案 |
自动化测试 | phpunit, mockery | phpspec |
页面预览测试 | laravel dusk | codeception |
db操纵 | eloquent | sql, doctrine |
模板 | blade | twig |
数据操纵 | laravel集合 | 数组 |
表单验证 | request classes | 他第三方包,甚至在控制器中做验证 |
权限 | built-in | 他第三方包或者你自己解决 |
api身份验证 | laravel passport | 第三方的jwt或者 oauth 扩展包 |
创建 api | built-in | dingo api 或者类似的扩展包 |
创建数据库结构 | migrations | 直接用 db 语句创建 |
本土化 | built-in | 第三方包 |
实时消息队列 | laravel echo, pusher | 使用第三方包或者直接使用websockets |
创建测试数据 | seeder classes, model factories, faker | 手动创建测试数据 |
任务调度 | laravel task scheduler | 脚本和第三方包 |
数据库 | mysql, postgresql, sqlite, sql server | mongodb |
遵循laravel命名约定
来源 psr standards.
另外,遵循laravel社区认可的命名约定:
对象 | 规则 | 更优的写法 | 应避免的写法 |
---|---|---|---|
控制器 | 单数 | articlecontroller | |
路由 | 复数 | articles/1 | |
路由命名 | 带点符号的蛇形命名 | users.show_active | |
模型 | 单数 | user | |
hasone或belongsto关系 | 单数 | articlecomment | |
所有其他关系 | 复数 | articlecomments | |
表单 | 复数 | article_comments | |
透视表 | 按字母顺序排列模型 | article_user | |
数据表字段 | 使用蛇形并且不要带表名 | meta_title | |
模型参数 | 蛇形命名 | $model->created_at | |
外键 | 带有_id后缀的单数模型名称 | article_id | |
主键 | - | id | |
迁移 | - | 2017_01_01_000000_create_articles_table | |
方法 | 驼峰命名 | getall | |
资源控制器 | store | ||
测试类 | 驼峰命名 | testguestcannotseearticle | |
变量 | 驼峰命名 | $articleswithauthor | |
集合 | 描述性的, 复数的 | $activeusers = user::active()->get() | |
对象 | 描述性的, 单数的 | $activeuser = user::active()->first() | |
配置和语言文件索引 | 蛇形命名 | articles_enabled | |
视图 | 短横线命名 | show-filtered.blade.php | |
配置 | 蛇形命名 | google_calendar.php | |
内容 (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) ? null : $object->relation->id |
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容器来创建实例 而不是直接new一个实例
创建新的类会让类之间的更加耦合,使得测试越发复杂。请改用ioc容器或注入来实现。
例子:
$user = new user; $user->create($request->validated());
更优的写法:
public function __construct(user $user) { $this->user = $user; } .... $this->user->create($request->validated());
避免直接从 .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 getsomedateattribute($date) { return $date->format('m-d'); } // view {{ $object->ordered_at->todatestring() }} {{ $object->ordered_at->some_date }}
其他的一些好建议
永远不要在路由文件中放任何的逻辑代码。
尽量不要在blade模板中写原始 php 代码。
原文作者:
原文链接:https://github.com/alexeymezenin/laravel-best-practices/
上一篇: Java读取大文件,超越内存限制
下一篇: Java读取写入文件所以方法含大文件读取