Follow the Eloquent road
Bits & bytes
- Moving up / down the existing stack
- Moving things sideways into new infrastructure
- Deeper integration with Eloquent
Hey! I'm Tim
Developer; Musician; 🐶 lover;
Meet Taz
WordPress developer
functions.php
Laravel developer
Model.php
Like any good adventure...
It's all about the journey
Forms
Adventure::createFromRequest($request)
// Controller
public function store(AdventureRequest $request)
{
$adventure = Adventure::createFromRequest($request);
}
// Model
public static function createFromRequest($request)
{
return $request->user()->adventures()->create($request->validated());
}
// Alongs comes some complexity...
// create adventure ✅
// create character
// Controller
public function store(AdventureRequest $request)
{
$adventure = Adventure::createFromRequest($request);
$character = Character::createFromRequest($request);
}
// Model
public static function createFromRequest($request)
{
// return $request->user()->adventures()->create($request->validated());
return Adventure::create([
'destination' => $request->destination,
'purpose' => $request->purpose,
'starts_at' => $request->starts_at,
'user_id' => $request->user()->id,
]);
}
// Along comes some complexity...
// admin area allows creating adventures for users
// admins can assign an adventure type, users cannot
// Model
public static function createFromRequest($request)
{
return Adventure::create([
'destination' => $request->destination,
'purpose' => $request->purpose,
'starts_at' => $request->starts_at,
'user_id' => $request->user()->id,
]);
}
if ($request instanceof Admin\AdventureRequest) {
//
}
if ($request->is('admin/*')) {
//
}
public static function createFromUserRequest($request) { /** ... */ }
public static function createFromAdminRequest($request) { /** ... */ }
/ ------------- \ / ------------- \
| Request | | Admin\Request |
\ ------------- / \ ------------- /
| |
| |
/ ---------- \ / ---------- \
| Controller | | Controller |
\ ---------- / \ ---------- /
\ /
|
|
/ ----- \
| Model |
\ ----- /
// Controller
public function store(AdventureRequest $request)
{
$adventure = Adventure::create([
'destination' => $request->destination,
'purpose' => $request->purpose,
'starts_at' => $request->starts_at,
'user_id' => $request->user()->id,
]);
}
// Admin\Controller
public function store(Admin\AdventureRequest $request)
{
$adventure = Adventure::create([
'destination' => $request->destination,
'purpose' => $request->purpose,
'starts_at' => $request->starts_at,
'user_id' => $request->user_id,
'type' => $request->type,
]);
}
// AdventureRequest
public function adventurePayload()
{
return collect($this->validated())
->only([
'destination',
'purpose',
'starts_at',
])
->merge([
'user_id' => $this->user()->id,
])
->toArray();
}
// Admin\AdventureRequest
public function adventurePayload()
{
return collect($this->validated())
->only([
'destination',
'purpose',
'starts_at',
'user_id',
'type',
])
->toArray();
}
// Controller
public function store(AdventureRequest $request)
{
return Adventure::create([
'destination' => $request->destination,
'purpose' => $request->purpose,
'starts_at' => $request->starts_at,
'user_id' => $request->user()->id,
]);
}
// Controller (admin + user)
public function store(AdventureRequest $request)
{
$adventure = Adventure::create($request->adventurePayload());
}
// Controller (admin + user)
public function store(AdventureRequest $request)
{
$adventure = Adventure::create($request->adventurePayload());
$character = Character::create($request->characterPayload());
}
</forms>
Abilities
$user->isAdmin()
if ($user->isModerator()) {
//
}
if ($user->isModerator() || $user->isAdmin()) {
//
}
if ($user->isModerator() || $user->isAdmin() || $user->isSuperAdmin()) {
//
}
if ($user->isModerator() || $user->isAdmin() || $user->isSuperAdmin() || $user->isGary()) {
//
}
class User extend Model
{
public function isGary() { /*...*/ }
public function isModerator() { /*...*/ }
public function isAdmin() { /*...*/ }
public function isSuperAdmin() { /*...*/ }
public function isLittleLessThanAdminLittleMoreThanModerator() { /*...*/ }
}
public function isAdmin() { /*...*/ }
public function isNotAdmin() { /*...*/ }
$user->isAdmin() /** feels like */ $user instanceof Admin
// View
@if(request()->user()->isAdmin() || request()->user()->isSuperAdmin() || request()->user()->isGary())
<form action="dispatch-flying-monkeys" method="POST">
<button>
Fly! Fly! Fly!
</button>
</form>
@endif
// Controller
if (! ($request->user()->isAdmin() || $request->user()->isSuperAdmin() || $request->user()->isGary())) {
abort(Response::HTTP_FORBIDDEN);
}
FlyingMonkeys::dispatchTo(Destination::hauntedForest());
/ ------- \ .
| Request | .
\ ------- / .
| .
| .
/ ---------- \ / ---------- \
| Controller | | Controller |
\ ---------- / \ ---------- /
|
|
/ ---- \
| View |
\ ---- /
/ ------- \ .
| Request | .
\ ------- / .
| .
| .
/ ---------- \ / ---------- \
| Controller | | Controller |
\ ---------- / \ ---------- /
|
|
/ ---- \ / ---- \
| Gate | | View |
\ ---- / \ ---- /
// AuthServiceProvider
Gate::define('dispatch-flying-monkeys', function ($user) {
// "Gary" is our CEO's son. Apparenty he needs to
// be able to dispatch the flying monkeys every now
// and then...
$isGary = $user->id === config('acme.garys_user_id');
return $isGary || $user->hasRole([
Role::admin(),
Role::superAdmin(),
]);
});
// View
@if(request()->user()->isAdmin() || request()->user()->isSuperAdmin() || request()->user()->isGary())
<form action="dispatch-flying-monkeys" method="POST">
<button>
Fly! Fly! Fly!
</button>
</form>
@endif
// View
@can('dispatch-flying-monkeys')
<form action="dispatch-flying-monkeys" method="POST">
<button>
Fly! Fly! Fly!
</button>
</form>
@endcan
// Controller
if (! ($request->user()->isAdmin() || $request->user()->isSuperAdmin() || $request->user()->isGary())) {
abort(Response::HTTP_FORBIDDEN);
}
FlyingMonkeys::dispatchTo(Destination::hauntedForest());
// Controller
if ($request->user()->cannot('dispatch-flying-monkeys')) {
abort(Response::HTTP_FORBIDDEN);
}
FlyingMonkeys::dispatchTo(Destination::hauntedForest());
Route::post('dispatch-flying-monkeys')->middleware('can:dispatch-flying-monkeys');
// Controller
FlyingMonkeys::dispatchTo(Destination::hauntedForest());
</abilities>
/ ------------------------------------- \
| Adventure::createFromRequest() |
| Adventure::createFromUserRequest() |
| Adventure::createFromAdminRequest() | => $request->adventurePayload();
| if ($request->is('admin/*')) |
| if ($request instanceof AdminRequest) |
\ ------------------------------------- /
/ ------------------------------------- \
| $user->isAdmin() |
| $user->isModerator() | => $user->can('dispatch-flying-monkeys');
| $user->isSuperAdmin() |
\ ------------------------------------- /
Intermission
Take a breath. Have a drink.
p.s. don't mess the rest of it up
❤️ always, past Tim
Eloquent
Digging deeper
$witch = Witch::create($attributes);
assert($witch instanceof Eloquent\Model);
Eloquent
---------------------
| |
| / ----- \ |
| | Model | |
| \ ----- / |
| |
---------------------
$collection = Witch::whereWicked()->get();
assert($collection instanceof Eloquent\Collection);
Eloquent
---------------------------------------
| |
| / ----- \ / ---------- \ |
| | Model | | Collection | |
| \ ----- / \ ---------- / |
| |
---------------------------------------
$builder = Witch::whereWicked();
assert($builder instanceof Eloquent\Builder);
Eloquent
------------------------------------------------------
| |
| / ------- \ / ----- \ / ---------- \ |
| | Builder | | Model | | Collection | |
| \ ------- / \ ----- / \ ---------- / |
| |
------------------------------------------------------
$familyMembers = FamilyMember::inRandomOrder()->take(3)->get();
// do some work...
$familyMembers->toQuery()->update([
//
]);
Collections (🚀)
$familyMembers->each(/** ... */)
// Throughout your app...
$familyMembers
->except($dorothy)
->each(fn($member) => $member->takeShelter($stormCellar));
$familyMembers
->except($dorothy)
->each(fn($member) => $member->takeShelter($stormCellar));
$familyMembers
->except($dorothy)
->each(fn($member) => $member->takeShelter($stormCellar));
// Never...
$familyMember = FamilyMember::first();
$familyMember->takeShelter($stormCellar);
class FamilyMember extends Model
{
public function newCollection(array $models = [])
{
return new Eloquent\Collection($models);
}
public function takeShelter($location) { /** ... */ }
}
class FamilyMemberCollection extends Eloquent\Collection
{
//
}
class FamilyMember extends Model
{
public function newCollection(array $models = [])
{
return new FamilyMemberCollection($models);
}
public function takeShelter($location) { /** ... */ }
}
class FamilyMember extends Model
{
public function newCollection(array $models = [])
{
return new FamilyMemberCollection($models);
}
public function takeShelter($location)
{
// step 1
// step 2
}
}
class FamilyMemberCollection extends Collection
{
public function takeShelter($location)
{
$this->each(function ($familyMember) use ($location) {
// step one...
// step two...
});
}
}
$familyMembers
->except($dorothy)
->each(fn($member) => $member->takeShelter($stormCellar));
$familyMembers
->except($dorothy)
->takeShelter($stormCellar);
// Before
$totalCost = $invoices->reduce(function ($total, $invoice) {
return $invoice->cost->add($total)
, new Money(0));
// After
$totalCost = $invoices->totalCost();
// For a full walkthough of the benifits of custom eloquent collections,
// check out my talk (and all the others!) from LaraconAU last year.
// @see https://tim.macdonald.au/giving-collections-a-voice
</collections>
Builders
Witch::whereWicked()
class Witch extends Model
{
public function scopeWhereWicked($buider) { /** ... */ }
public function scopeWhereGood($builder) { /** ... */ }
public function scopeWhereDead($builder) { /** ... */ }
public function scopeWhereAlive($builder) { /** ... */ }
}
class Witch extends Model
{
public function scopeWhereFromTheNorth($builder) { /** ... */ }
public function scopeWhereFromTheEast($builder) { /** ... */ }
public function scopeWhereFromTheSouth($builder) { /** ... */ }
public function scopeWhereFromTheWest($builder) { /** ... */ }
}
// Model
public function scopeWhereWicked($builder)
{
$builder->where('alignment', Alignment::WICKED);
}
class WitchBuilder extends Eloquent\Builder
{
//
}
class Witch extends Eloquent\Model
{
public function newEloquentBuilder($builder)
{
return new WitchBuilder($builder);
}
}
// Model
public function scopeWhereWicked($builder)
{
$builder->where('alignment', Alignment::WICKED);
}
// Builder
public function whereWicked()
{
return $this->where('alignment', Alignment::WICKED);
}
Witch::whereWicked()
->whereFromTheWest()
->whereAlive();
// Caveat...
public function scopeWhereGoodOrWicked($builder)
{
$builder->where(/** ... */)->orWhere(/** ... */);
}
// - - - - - - - - - - - - - - - - - - - - - - - - //
Witch::whereGoodOrWicked();
Witch::where(function ($builder) {
$builder->where(/** ... */)->orWhere(/** ... */);
});
// Builder...
public function whereGoodOrWicked()
{
return $this->where(function ($builder) {
$builder->where(/** ... */)->orWhere(/** ... */);
});
}
// Compose via atomic queries...
Witch::where(function ($builder) {
$builder->whereWicked()->orWhere->whereGood();
});
// For a full walkthough of creating dedicated query builders for your
// eloquent models, check out this blog post.
// @see https://tim.macdonald.au/dedicated-eloquent-model-query-builders
</builders>
Eloquent
------------------------------------------------------
| |
| / ------- \ / ----- \ / ---------- \ |
| | Builder | | Model | | Collection | |
| \ ------- / \ ----- / \ ---------- / |
| |
------------------------------------------------------