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 ArticlesController
路由 複數 articles/1 article/1
路由命名 帶點符號的蛇形命名 users.show_active users.show-active, show-active-users
模型 單數 User Users
hasOne或belongsTo關係 單數 articleComment articleComments, article_comment
所有其他關係 複數 articleComments articleComment, article_comments
表單 複數 article_comments article_comment, articleComments
透視表 按字母順序排列模型 article_user user_article, articles_users
數據表字段 使用蛇形並且不要帶表名 meta_title MetaTitle; article_meta_title
模型參數 蛇形命名 $model->created_at $model->createdAt
外鍵 帶有_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
資源控制器 table 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
內容 (interface) 形容詞或名詞 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) ? 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 代碼。

原文作者:ikidnapmyself

原文鏈接:https://github.com/alexeymeze...

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章