Follow the Eloquent road

Bits & bytes

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 |     |
|     \ ------- /    \ ----- /    \ ---------- /     |
|                                                    |
------------------------------------------------------

Thanks!

https://tim.macdonald.au

twitter.com/timacdonald87

/