• 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 社区获得帮助的机会会大大降低。不要让你的客户为此付出代价!

    任务标准工具第三方工具
    授权PoliciesEntrust, Sentinel and other packages
    前端编译Laravel MixGrunt, Gulp, 3rd party packages
    开发环境HomesteadDocker
    部署Laravel ForgeDeployer and other solutions
    单元测试PHPUnit, MockeryPhpspec
    浏览器测试Laravel DuskCodeception
    数据库操作EloquentSQL, Doctrine
    模板BladeTwig
    数据操作Laravel collectionsArrays
    表单验证Request classes3rd party packages, validation in controller
    认证Built-in3rd party packages, your own solution
    API 认证Laravel Passport3rd party JWT and OAuth packages
    创建 APIBuilt-inDingo API and similar packages
    数据库结构操作MigrationsWorking with DB structure directly
    局部化Built-in3rd party packages
    实时用户接口Laravel Echo, Pusher3rd party packages and working with WebSockets directly
    Generating testing dataSeeder classes, Model Factories, FakerCreating testing data manually
    生成测试数据Laravel Task SchedulerScripts and 3rd party packages
    数据库MySQL, PostgreSQL, SQLite, SQL ServerMongoDB
     

    遵循 Laravel 命名约定

    遵循 PSR 标准。 另外,请遵循 Laravel 社区接受的命名约定:

    类型规则正确示例错误示例
    Controller单数ArticleControllerArticlesController
    Route复数articles/1article/1
    Named route带点符号的蛇形命名users.show_activeusers.show-active, show-active-users
    Model单数UserUsers
    hasOne or belongsTo relationship单数articleCommentarticleComments, article_comment
    All other relationships复数articleCommentsarticleComment, article_comments
    Table复数article_commentsarticle_comment, articleComments
    Pivot table按字母顺序排列的单数模型名称article_useruser_article, articles_users
    Table column带着模型名称的蛇形命名meta_titleMetaTitle; article_meta_title
    Foreign key带_id 后缀的单数型号名称article_idArticleId, id_article, articles_id
    Primary key-idcustom_id
    Migration-2017_01_01_000_create_xx_table2017_01_01_0000_articles
    Method小驼峰命名getAllget_all
    Method in resource controller具体看表格storesaveArticle
    Method in test class小驼峰命名testGuestCannotSeeArticletest_guest_cannot_see_article
    Variable小驼峰命名$articlesWithAuthor$articles_with_author
    Collection具描述性的复数形式$activeUsers = User::active()->get()$active, $data
    Object具描述性的单数形式$activeUser = User::active()->first()$users, $obj
    Config and language files index蛇形命名articles_enabledArticlesEnabled; articles-enabled
    View蛇形命名show_filtered.blade.phpshowFiltered.blade.php, show-filtered.blade.php
    Config蛇形命名google_calendar.phpgoogleCalendar.php, google-calendar.php
    Contract (interface)形容词或名词AuthenticatableAuthenticationInterface, IAuthentication
    Trait形容词NotifiableNotificationTrait
     

    尽可能使用更短、更易读的语法

    坏:

    $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
  • 相关阅读:
    SpringCloud教程第10篇:高可用的服务注册中心(F版本)
    SpringCloud教程第9篇:Sleuth(F版本)
    requests.session保持会话
    Jmeter Constant Throughput Timer 使用
    Jmeter提取响应数据的结果保存到本地的一个文件
    练习2
    练习1
    一道简单的练习题
    Maven下org.junit.Test无法使用
    [转]解决pycharm无法导入本地包的问题(Unresolved reference 'tutorial')
  • 原文地址:https://www.cnblogs.com/lxwphp/p/15453452.html
Copyright © 2020-2023  润新知