还记得以前咱们的文章《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