Laravel 最佳實踐

單一職責原則

一個類和一個方法應該只有一個責任。javascript

例如:php

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;
    }
}
複製代碼

更優的寫法:java

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類中。laravel

例如:git

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

    return view('index', ['clients' => $clients]);
}
複製代碼

更優的寫法:github

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 類中.sql

例子:數據庫

public function store(Request $request) {
    $request->validate([
        'title' => 'required|unique:posts|max:255',
        'body' => 'required',
        'publish_at' => 'nullable|date',
    ]);

    ....
}
複製代碼

更優的寫法:json

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',
        ];
    }
}
複製代碼

業務代碼要放到服務層中

控制器必須遵循單一職責原則,所以最好將業務代碼從控制器移動到服務層中。api

例子:

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

原文連接:github.com/alexeymezen…

相關文章
相關標籤/搜索