Laravel 2019優雅技巧和最佳實踐(高級篇)

想要更好的閱讀體驗和更多的乾貨分享,請直接移步個人原文出處吧:www.pilishen.com/posts/some-…

還記得以前咱們的文章《Laravel 2018使用數據分析——Laravel你用對了嗎?學對了嗎?》嗎?那個是到2018年7月,基於你們的laravel使用數據,所作的分析和建議,若是你尚未看過那個,而且尚未遵循其中的最佳實踐,那麼首先落實好它。無論你laravel用了多久,PHP用了多久,若是那裏面的數據分析和建議對你來講還很陌生,還都跟你本身實際用的不是一回事,那麼基本說明你的laravel用的就是不規範、不優雅的,你就可能真的須要好好系統學習一下了。laravel

那麼一年過去了,除去那些基本的,咱們Laravel Shift的做者Jason McCreary在今年的laravel國際會議Laracon US 2019上,又做了題爲《Laracon US 2019 - Some Shifty Bits》的主題分享,分享了這一年來更高級的一些laravel優雅建議和最佳實踐,一塊兒來看看你都用上了沒。程序員

須要注意的是,可能下面的一些條目,看上去缺少細節,這是由於這只是一個文字稿,具體詳細的代碼展開過程是在他《Laracon US 2019 - Some Shifty Bits》視頻裏演示的,因此這裏有些地方就沒有再額外贅述了,單純的文字並不能很好展現代碼的展開和重構細節。算法

想真正跟着這些國際大神學習,學習他們的編程思惟,學習他們的代碼編寫細節和習慣,仍是建議要看他們的活生生的、直觀的視頻,這樣你獲得的纔不僅是一些「死的」「下腳料」,而是可以用到你整個職業生涯的習慣、思惟、模式等更有用的東西,更容易讓你成爲真正優秀的程序員的東西。數據庫

固然了,這就須要極好的英語和較紮實的基礎,才能從現場視頻中聽懂他們到底在說啥,才能真正領會他們的演講主旨,可能這對大部分人來講都太難。不過沒關係,咱們pilishen.com已經推出了【國際IT專場】這個頻道,收集了精彩的國際IT會議視頻,併爲你們作了精心翻譯和譯製。這樣,只要你稍微有點基礎,稍微有點想當大神的上進心,你也能夠實際接觸第一手的國際大牛演講,也能夠跟着最棒的大神進行學習,也能夠所以在最短的時間裏突破自我,往後成長爲真正的國際大神。編程

(一)Model屬性轉換(映射)

{id="AttributeCasting"}json

laravel裏能夠轉換model屬性的數據類型,經過使用$casts,默認的,created_atupdated_at這兩個時間屬性就會被轉換成Carbon實例。sass

咱們也能夠設置額外的,須要轉換的屬性。好比說,有個Setting model,它從屬於User model,咱們想着將它的外鍵(foreign key)自動轉換成integer類型,假設還有個active字段,也想着轉換成boolean類型。bash

class Setting extends Model
{
    protected $casts = [
        'user_id' => 'integer',
        'active' => 'boolean',
    ];
}
複製代碼

這樣當咱們獲取相應屬性的時候,它們就會自動被轉換或映射成相應的類型,這不僅是發生在屬性讀取階段,更重要的是,在咱們設置它們的值的時候,在寫入的時候,也會進行相應的類型轉換。session

$setting = Setting::first();

// 讀取屬性時
dump($setting->user_id);   // 1
dump($setting->active);    // false

// 設置屬性時
$setting->user_id = request()->input('user_id');
$setting->active = 1;

dump($setting->user_id);   // 5, not "5"
dump($setting->active);    // true, not 1
複製代碼

它的好處是,當咱們request當中的數值,不是理想的類型時,好比request傳過來的極可能是一個字符「5」,由於咱們設置了cast,它就能自動避免期間的一些混亂。app

你也能夠用它來轉換一些更復雜的類型,好比arraycollection,這常常用在將json字段的數據,轉換成PHP裏的array或者laravel裏的collection

(二)自定義的屬性轉換

{id="CustomCasting"}

其實屬性轉換的邏輯,咱們也能夠在accessormutator中來作,accessor是當咱們讀取一個屬性時作些什麼,mutator是當咱們寫入一個屬性時作什麼,固然這期間咱們能夠進行數據轉換。

假設咱們data字段的數據,默認存的是用|號拼接起來的字符,咱們想着在獲取和設置時能轉換成array來操做:

class Setting extends Model
{
    public function getDataAttribute($value)
    {
        return explode('|', $value);
    }

    public function setDataAttribute($value)
    {
        $this->attributes['data'] = implode('|', $value);
    }
}
複製代碼

須要注意的是,因爲自身的一些限制,你並不能在它們上面直接執行一些PHP相應數據類型的操做,好比這裏,既然data讀出來的是array,那麼假設我想進行一個合併array的操做,下面的這種方式就並不會更新背後的數值:

$setting = Setting::first();

dump($setting->data);  // [1, 2, 3]

$setting->data += [4];   //不會更改數據庫中的內容

dump($setting->data);  // 仍然是 [1, 2, 3]

$setting->save();      // 1|2|3
複製代碼

固然了,這種狀況下,你能夠設置個變量,對其進行更改了之後,再將這個變量整個賦值給相應的字段,相似這樣:

$user = App\User::find(1);

$options = $user->options;   // 設置變量

$options['key'] = 'value';  // 更改變量

$user->options = $options;  // 整個賦值回去

$user->save();
複製代碼

(三)模型關係

{id="ModelRelationships"}

不少人喜歡直接修改外鍵的方式,來設置兩個模型之間的關係,相似這樣:

$setting->user_id = $user->id;
複製代碼

更優雅的方式,是利用上laravel提供的一些模型關係關聯的方法,好比belongsTo關係裏,可使用associatedisassociate方法:

// 關聯
$setting->user()->associate($user);

// 取消關聯
$setting->user()->disassociate($user);
複製代碼

在many-to-many的關係裏,你可使用attachdetach方法:

// 關聯
$user->settings()->attach($setting);

// 取消關聯
$user->settings()->detach($setting);
複製代碼

在many-to-many的關係裏,還有togglesync方法,它們均可以幫你避免手動寫一些雜亂的邏輯。

(四)中間表

{id="Pivots"}

另外一個跟many-to-many關係相關的是中間表的數據,由於這個時候咱們有個中間表(pivot table),因此常見的需求是,使用這個中間表來存儲一些額外的信息。

好比說,假設UserTeam這兩個是many-to-many關係,一個user能夠在多個team中,一個team能夠擁有不少個user。

可是呢,咱們想着有些額外信息,好比記錄一個user是否被批准加入某個team,這塊信息存到哪裏呢?固然,咱們能夠將其存到中間表裏,而後在關係調取時,咱們能夠獲取相應中間表的信息。

好比User模型裏,咱們想獲取只有那些被批准加入了的Team:

class User extends Authenticatable
{
    public function teams()
    {
        return $this->belongsToMany(Team::class)
            ->wherePivot('approved', 1);
    }
}
複製代碼

在關係的另外一端,咱們想着獲取一個team下全部成員的額外信息,假設是在後臺頁面上,咱們想展現成員的審覈狀態,同時帶上一個成員加入這個團隊的時間信息。

咱們就可使用withPivotwithTimestamps方法來獲取這些額外信息,可是我也可使用using方法來聲明一個類,用這個類來表明這塊數據,能夠把這個想象成一個數據映射(cast)。

class Team extends Model
{
    public function members()
    {
        return $this->belongsToMany(User::class)
            ->using(Membership::class)
            ->withPivot(['id', 'approved'])
            ->withTimestamps();
    }
}
複製代碼

來看看這個Membership類,它擴展的是Pivot類,而不是咱們一般的Model類,Pivot類背後也擴展了Model類,因此咱們能夠說Membership也是一個Model,只不過是一個Pivot Model,你能夠說它是中間表模型。

它其中跟咱們普通的model同樣,也能夠設置table屬性,也能夠設置自增屬性incrementing,等等。

並且這裏我還定義了它跟user和team的關係,而且讓它們默認加載了(with)。

class Membership extends Pivot
{
    protected $table = 'team_user';

    public $incrementing = true;

    protected $with = ['user', 'team'];

    public function user()
    {
        return $this->belongsTo(User::class);
    }

    public function team()
    {
        return $this->belongsTo(Team::class);
    }
}
複製代碼

想象一下,多了一個Model出來,就能多出來多少便利呢,咱們能夠在這個中間表模型上,加上accessor,加上mutator,加上events,加上獨特的方法,等等等等,也就會給你增長不少意想不到的便利。

(五)Blade視圖裏的簡便命令

{id="BladeDirectives"}

不少人在應用裏,依然只是用最基本的blade命令,好比,只是用@if標籤,其實還有不少更簡單、更優雅的命令:

@if(isset($records))
@isset($records)     // 更優雅

@if(empty($records))
@empty($records)     // 更優雅

@if(Auth::check())   
@auth                // 更優雅

@if(!Auth::check())
@guest               // 更優雅
複製代碼

此外,還有兩個常常在Form表單裏用的:

@method('PUT')

@csrf
複製代碼

(六)遍歷指針追蹤

{id="LoopIterationTracking"}

常常咱們會在視圖裏用@foreach來遍歷數據,當你須要對其中的數據進行更多的掌控時,能夠其中的$loop變量,它是@foreach內的一個內置的變量,經過$loop你能夠調用其countiterationfirstlastevenodddepth已經更多相似屬性,這對於咱們掌控遍歷邏輯頗有用。

(七)通配符形式的View Composers

{id="Wildcard"}

在使用 view composers時,要注意一個可能的性能影響。可能你會爲了圖省事,簡單地在view composer裏使用*號來匹配全部的頁面,儘管這是一種很好的共享數據的方式,可是也要注意,這樣可能影響到你的性能。

View::composer('*', function ($view) {
    $settings = Setting::where('user_id', request()->user()->id)->get()
    $view->with('settings', $settings);
});
複製代碼

當你這樣經過回調函數來分享數據時,其中的邏輯,就會在每個視圖裏都被調用一次,這包括layouts,partials,component等。

因此若是你的模板調用了7個其餘的視圖,那麼這個也會被執行7次。

file

因此,儘可能在只是真正用到這個數據的視圖上分享數據,或者將數據分享給更高層級的視圖上,好比分享給layout視圖。若是你確實須要針對不少個視圖分享數據,能夠嘗試使用單例模式來調取數據:

file

固然,也能夠利用上cache,一個道理。

(八)異常渲染

{id="RenderingExceptions"}

我常常見人們把try/catch代碼塊放到controller裏,我我的很不喜歡這樣,它們老是看上去很繁重、亂糟糟的:

try {
    if ($connection->isGitLab()) {
        GitLabClient::addCollaborator($connection->access_token, $repository);
    }
} catch (GitLabClientException $exception) {
    Log::error(Connection::GITLAB . ' failed to connect to: ' . $request->input('repository') . ' with code: ' . $exception->getCode());
    return redirect()->back()->withInput($request->input());
}
複製代碼

咱們能夠經過利用laravel框架提供的自定義exception渲染來移除這種需求,你能夠在那個exception類裏定義個render()方法,當發生了這個異常時,laravel默認就會調用它。

這意味着你能夠移除掉上面異常catch裏的那些邏輯,將它們放到GitLabClientException裏,讓laravel自動去處理相應邏輯。

(九)API Responses

{id="Responses"}

對返回的響應進行必定的格式處理,也是很常見的需求,尤爲是在API裏。

Shift的數據分析顯示,像Fractal這種專門格式響應的組件,是比較受歡迎的。但,其實laravel默認有提供這些功能的。

好比,你能夠建立個Resource類,在這個類裏,你能夠具體定義或格式化你的某個model的各類屬性,甚至也能夠定義響應返回的header信息等。

定義好格式後,須要的時候就能夠將model實例傳給它,再將它直接返回。不僅是單個的model實例能夠變動格式,laravel也提供多個model的collection集合返回格式。

具體的能夠參考文檔上的示例://laravel.com/docs/5.8/eloquent-resources#concept-overview

(十)用戶認證邏輯

{id="AuthenticationBehavior"}

若是你應用裏改了不少框架自己的核心源碼,那麼着就會致使你後期的版本升級很困難,或者說你徹底不敢升級。十有八九,你都不須要非得改動源碼的,laravel每每都提供了無縫擴展的方式或地方,你只須要找到它,作相應改動,就不會影響到核心邏輯了。

好比,你想要有額外的用戶認證邏輯,想更改默認的用戶認證響應,這個時候不要直接改寫AuthenticatesUser這個trait裏的sendLoginResponse()方法,laravel其實提供了authenticated()方法,專門是用來讓你寫本身的驗證成功返回邏輯的:

protected function sendLoginResponse(Request $request)
{
    $request->session()->regenerate();

    $this->clearLoginAttempts($request);

    return $this->authenticated($request, $this->guard()->user())
            ?: redirect()->intended($this->redirectPath());
}
複製代碼

能夠看到,只有當authenticated()爲空,或者返回爲null的時候,就會執行默認的重定向跳轉,但若是你修改了authenticated()方法,它自己裏面沒有任何邏輯,那麼這時候返回不爲空了,就會在認證成功後,直接調用你修改了的authenticated()方法裏的邏輯了。

因此老是要留意相似的預留的接口、方法或事件等,而不是上來就去覆寫核心邏輯。

(十一)權限驗證邏輯

{id="AuthorizationLogic"}

常常,權限的驗證,也會貫穿你的整個應用裏。好比說,我有個video controller,它裏面有相應邏輯,來確保特定的用戶能觀看到特定的視頻。

class VideosController extends Controller
{
    public function show(Request $request, Video $video)
    {
        $user = $request->user();

        $this->ensureUserCanViewVideo($user, $video);

        $user->last_viewed_video_id = $id;
        $user->save();

        return view('videos.show', compact('video'));
    }

    private function ensureUserCanViewVideo($user, $video)
    {
        if ($video->lesson->isFree() || $video->lesson->product_id <= $user->order->product_id) {
            return;
        }

        abort(403);
    }
}
複製代碼

這裏咱們檢查lesson是不是免費的,或者當前用戶是否購買了包含這個lesson的課程,不然的話,咱們就拋出一個403停止的異常。

問題是,咱們應用裏,並不僅是這一個地方須要這種檢查邏輯,咱們可能middleware和視圖裏都須要如出一轍的邏輯,如何避免重複呢?

laravel提供了一種封裝權限驗證邏輯的方式,經過 Gates 和 Policies,Gates是通常意義上的權限檢查,policies管的更可能是一個model的CURD邏輯是否有權限。

這裏我用gate來作演示,它經過使用一個回調函數,當驗證經過時就返回true,驗證失敗了就返回false。

我也能夠給這個gate起個簡單的名字,便於後期調用它,同時也能夠給它聲明須要的參數,方便具體驗證邏輯的執行。

Gate::define('watch-video', function ($user, \App\Lesson $lesson) {
    return $lesson->isFree() || $lesson->product_id <= optional($user->order)->product_id;
});
複製代碼

這樣,每一個須要相似檢查的地方,我就能夠經過Gate facade來調用我定義的這塊驗證邏輯。固然了,若是你是在視圖裏,也會有一系列簡單的blade標籤,好比@can

class VideosController extends Controller
{
    public function show(Request $request, Video $video)
    {
        abort_unless(Gate::allows('watch-video', $video), 403);

        $user = $request->user();
        $user->last_viewed_video_id = $video->id;
        $user->save();

        return view('videos.show', compact('video'));
    }
}
複製代碼

這樣的話,我就能夠移除本身的權限封裝方法,同時藉助abort_unless()這個輔助函數,也即除非Gate::allows('watch-video', $video)驗證失敗,返回爲false時,就403停止異常。

(十二)請求籤名

{id="SignedRequests"}

另外一個認證相關的,是在laravel裏建立一個簽名了的url,這些url裏包含相應數據,同時使用了HMAC算法,來確保它們沒有被更改掉。它也能夠有生效時間,在必定時間內點擊纔能有效。

laravel不只能夠自動生成臨時的url和簽名的url,並且可以驗證它們的有效性,提供了用於檢查的middleware。

好比這裏,我使用簽名url來容許成員加入特定團隊組。

class TeamController extends Controller {
    public function __construct() {
        $this->middleware('signed')->only('show');
    }

    public function edit(Request $request) {
        $team = Team::firstOrCreate([
            'user_id' => $request->user()->id
        ]);

        $signed_url = URL::temporarySignedRoute('team.show', now()->addHours(24), [$team->id]);  

        return view('team.edit', compact('team', 'signed_url'));
    }
}
複製代碼

(十三)響應和路由輔助函數

{id="ResponseandRoute Helpers"}

有一些很優雅的響應和路由相關的輔助函數,咱們一塊兒來看一下,看看它們的先後對比效果:

// 以前
Route::get('/', ['uses' => 'HomeController@index', 'middleware' => ['auth'], 'as' => 'home']);
Route::resource('user', 'UserController', ['only' => ['index']]);

// 以後
Route::get('/', 'HomeController@index')->middleware('auth')->name('home');
Route::resource('user', 'UserController')->only('index');

// 以前
response(null, 204);
response('', 200, ['X-Header' => 'whatever'])

// 以後
response()->noContent();
response()->withHeaders(['X-Header' => 'whatever']);
複製代碼

此篇是咱們laravel高級課程《Laravel底層實戰兼核心源碼解析》的擴展文章,該篇中的不少要點,其實在咱們這個課程裏早就有相關講解了。

更進一步的,若是你更厲害,或者更願意學習,未來想成爲行業大神,那麼咱們還給你準備了更高級的【國際IT專場會議】,在這裏咱們爲你翻譯整理了IT界的各大國際會議,PHP的做者、laravel的做者、symfony的做者等等國際頂尖大牛親自給你講解IT技術,多學習幾個之後,你以爲本身離大神還遠嗎?專場連接://www.pilishen.com/casts

file
file
相關文章
相關標籤/搜索