Laravel 代码开发最佳实践

咱们这里要讨论的并非 Laravel 版的 SOLID 原则(想要了解更多 SOLID 原则细节查看这篇文章)亦或是设计模式,而是 Laravel 实际开发中容易被忽略的最佳实践。php

内容概览

单一职责原则

一个类和方法只负责一项职责。html

坏代码:前端

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;
    }
}

好代码:laravel

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 类。json

坏代码:设计模式

public function index()
{
    $clients = Client::verified()
        ->with(['orders' => function ($q) {
            $q->where('created_at', '>', Carbon::today()->subWeek());
        }])
        ->get();

    return view('index', ['clients' => $clients]);
}

好代码:api

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',
    ]);

    ....
}

好代码:session

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。

 

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

相关文章
相关标签/搜索