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

相關文章
相關標籤/搜索