還記得以前咱們的文章《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會議視頻,併爲你們作了精心翻譯和譯製。這樣,只要你稍微有點基礎,稍微有點想當大神的上進心,你也能夠實際接觸第一手的國際大牛演講,也能夠跟着最棒的大神進行學習,也能夠所以在最短的時間裏突破自我,往後成長爲真正的國際大神。編程
{id="AttributeCasting"}json
laravel裏能夠轉換model屬性的數據類型,經過使用$casts,默認的,created_at
和updated_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
你也能夠用它來轉換一些更復雜的類型,好比array
和collection
,這常常用在將json
字段的數據,轉換成PHP裏的array
或者laravel裏的collection
。
{id="CustomCasting"}
其實屬性轉換的邏輯,咱們也能夠在accessor
和mutator
中來作,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
關係裏,可使用associate
和disassociate
方法:
// 關聯
$setting->user()->associate($user);
// 取消關聯
$setting->user()->disassociate($user);
複製代碼
在many-to-many的關係裏,你可使用attach
和detach
方法:
// 關聯
$user->settings()->attach($setting);
// 取消關聯
$user->settings()->detach($setting);
複製代碼
在many-to-many的關係裏,還有toggle
和sync
方法,它們均可以幫你避免手動寫一些雜亂的邏輯。
{id="Pivots"}
另外一個跟many-to-many關係相關的是中間表的數據,由於這個時候咱們有個中間表(pivot table),因此常見的需求是,使用這個中間表來存儲一些額外的信息。
好比說,假設User
和Team
這兩個是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下全部成員的額外信息,假設是在後臺頁面上,咱們想展現成員的審覈狀態,同時帶上一個成員加入這個團隊的時間信息。
咱們就可使用withPivot
和withTimestamps
方法來獲取這些額外信息,可是我也可使用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,加上獨特的方法,等等等等,也就會給你增長不少意想不到的便利。
{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
你能夠調用其count
、iteration
、first
、last
、even
、odd
、depth
已經更多相似屬性,這對於咱們掌控遍歷邏輯頗有用。
{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次。
因此,儘可能在只是真正用到這個數據的視圖上分享數據,或者將數據分享給更高層級的視圖上,好比分享給layout視圖。若是你確實須要針對不少個視圖分享數據,能夠嘗試使用單例模式來調取數據:
固然,也能夠利用上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自動去處理相應邏輯。
{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