Intermediate Laravel Tips and Tricks

Service providers

Cleaner code through facades and the container

  • The container is an awesome tool that powers most of the helpers of the framework:
    • Constructor injection
    • Method injection
    • Facades
    • and much more...

The container

What is a dependency inversion container?

  • Registry of classes, interfaces and simple names bound to other classes or instances
Registration name Target name
Request Illuminate/Http/Request
Mail Illuminate/Mail/System

What you might have done

public function myMethod() {

    Mail::send('template', function(MailMessage $message) {

        $message->setSubject(Request::get('subject');

        $message->to(Request::get('to'));

    });

}

Facade usage

Using the container

Constructor injection

public function __construct(Mail $mail) {

    $this->mail = $mail;

}

Using the container

public function myMethod(Request $request) {

    $this->mail->send('template', function() use ($request) {

        $this->setSubject($request->get('subject');

        $this->to($request->get('to'));

    });

}

Method injection

  • Less static binding to a specific class name / alias
  • Know exactly where each dependency you use comes from
  • Easier for code insight, relies less on magic and alias scanning
  • Easier versionning of classes through the container's "when, on, give" methods

Why is it better?

app::when(Controller::Class)
    ->needs('SomeName')
    ->give(SomeClassV2::class);

app::when(AnOldController::Class)
    ->needs('SomeName')
    ->give(SomeClassV1::class);

Not at all, just less direct.

So facades are bad?

Why use them then?

They are a simpler and cleaner approach to accessing globally available libraries.

They are much better than singletons

or static bindings!

Facade vs singleton

class MySingleton {
    
    public static function myAwesomeStaticFunction() { ... }

}

MySingleton::myAwesomeStaticFunction();
  • Stuck to using your class name
  • Induces static context
  • Induces static binding resolution in your code

Facade vs singleton

class MyClass {
    
    public function myAwesomeFunction() { ... }

}

That::myAwesomeFunction();
  • Uses aliases (Core concept of facades) which diminishes static binding resolution in your code
  • Calls are non static and are made against an instance of your class. (Good for testing)
  • It's just a module of code that:
    • binds classes in the container
    • offers configuration support
    • usually provides a facade to make quick and easy access in your code

So what is a service provider?

Example

'aliases' => [

        'Riddles' => App\ServicesProviders\RiddlesServiceProvider
                        \Facades\Riddles::class,

]
class Riddles extends Facade
{
    /**
     * Get the registered name of the component.
     *
     * @return string
     */
    protected static function getFacadeAccessor()
    {
        return 'Riddles';
    }
}

Example

class RiddlesServiceProvider extends ServiceProvider
{

    /**
     * Register bindings in the container.
     *
     * @return void
     */
    public function register()
    {
        $this->app->bind('Riddles', function ($app) {
            return new RiddleRepository();
        });
    }

}

Example

class RiddleRepository {
    
    public function getARiddle() : Riddle { ... }

}

$riddle = Riddle::getARiddle();
  • Decouples modules in your code
  • Allows cleaner access to resources in your code through dependency inversion and facades
  • Less static context compared to singletons
  • Better testability

Advantages

  • Do manual bindings to override usage of a class

Other uses for the container

$this->app->bind(
    EmailAggregatorInterface::class, ReviewerIDBasedEmailAggregator::class
);

$this->app->bind(
    EmailTargetInterface::class, LessonReviewCreatorEmailTarget::class
);

$this->app->bind(
    EmailDeciderInterface::class, ImmediatelySentEmailDecider::class
);
  • Use tagging to index important classes together

Other uses for the container

$this->app->tag(
    JiraSummaryConsoleCommand::class, ['ConsoleCommand']
);

$this->app->tag(
    JiraSprintsConsoleCommand::class, ['ConsoleCommand']
);

$this->app->tag(
    JiraBugsConsoleCommand::class, ['ConsoleCommand']
);

You can use the container outside of laravel

Hint !!!

composer require illuminate/container
$ioc = new Illuminate\Container\Container();

The queues

Or distributing workload

What is the problem here?

// Send an email for each user that asked for news

foreach($users as $user) {

    Mail::send('template', function() use ($user) {

        $this->setTo($user->email);

        $this->setSubject(Request::get('subject');

    });

}

It locks up your response!

  • Instead, you should leverage queuing capabilities :
    • Easy with Laravel's queue api
    • Makes your app more responsive
    • Allows you to distribute workload to different servers
    • Adds value to your app (delay, job isolation, priority)

Why use queues?

How do i use it?

Mail::queue('template', function(MailMessage $message){

    $message->to('someone@else.com');

});

dispatch(new UploadProcessingJob(
    $fileToProcess,
    $uploadingUser
));

Service container can define jobs and can even queue their own jobs...

What does a job look like?

class SendReminderEmail extends Job implements ShouldQueue
{
    use InteractsWithQueue, SerializesModels;

    protected $user;

    public function __construct(User $user)
    {
        $this->user = $user;
    }

    public function handle(Mailer $mailer)
    {
        $mailer->send(/* ... */);

        $this->user->reminders()->create(...);
    }

}

Distribute to many servers

php artisan queue:work --queue=emails
php artisan queue:work redis --queue=jobs
php artisan queue:work sqs --queue=emails
php artisan queue:work --sleep 3

Server 1

Server 2

Server 3

Runs web requests

Runs the email queue

Runs the upload processing job queue

Priorizing/classifying jobs

$job->onQueue('low');

$job->onConnection('slow-queues');
  • Prioritize your jobs by running multiple daemons
  • Run more daemons on one queue to make it more responsive
  • Queue slow jobs or customers with free accounts on one connection and the paying customers on another

Delaying jobs

$checkTranscodingJobIsDone->delay(Carbon::now()->addMinutes(10));

$sendAppreciationEmail->delay(Carbon::now()->addDays(5));
  • Delays are useful to queue stuff to happen in the future or to prevent overload on a queue
  • AWS SQS doesn't support more than 15m delays, you have to use a different service if you want to queue emails on a daily basis
  • Media transcoding job
  • Caching system update jobs
  • Heavy calculation or processing job
  • Web scanner that crawls a site
  • etc

Types of jobs you could use

You can use the queue outside of laravel

Hint !!!

composer require illuminate/queue

Note: Not an easy thing to do though!

Event dispatching

Programming reactively instead of sequentialy

  • Events are small code pieces describing something happening in your application
  • Dispatching an event triggers listeners which execute more code related to that event
  • Listeners don't run sequentialy nor run immediately

What is an event

  • Creates smaller units of code
  • Disconnects different parts of your app
  • Makes testing easier as everything is a small unit
  • Create magical effects
  • Can lead to many different classes

Good and Bad

First you need to create event objects

How to use them

<?php
namespace App\Events;

class AnswerCreatedEvent extends Event
{

    use Illuminate\Queue\SerializesModels;

    public $answer;

    public function __construct(App\Answer $answer)
    {

        $this->answer = $answer;
    }

}

Then you need to create event listeners

How to use them

<?php
namespace App\Listeners;

class NotifyClubOwnerOnAnswerCreatedListener {}

class NotifyRiddleOwnerOnAnswerCreatedListener {}

class AwardEarlyBirdBadgeOnEarlyAnswersListener {}

class AwardProcrastinatorBadgeOnLateAnswersListener {}

/**
 * Each class implements
 */
public function handle(App/Events/AnswerCreatedEvent $event) {
    
    // Do something

}

Finaly, you need to bind events and listeners

How to use them

class EventServiceProvider extends ServiceProvider
{
    
    protected $listen = [
        AnswerCreatedEvent::class => [
            NotifyClubOwnerOnAnswerCreatedListener::class,
            NotifyRiddleOwnerOnAnswerCreatedListener::class,
            AwardEarlyBirdBadgeOnEarlyAnswersListener::class,
            AwardProcrastinatorBadgeOnLateAnswersListener::class,
        ],
    ];

}

Once all is bounded, you can fire events away!

Triggering events

<?php

$answer = Answer::create([
    'answer'         => $answer,
    'club_riddle_id' => $clubRiddle->id,
    'user_id'        => $user->id,
]);

Event::fire(new AnswerCreatedEvent($answer));

This will fire the handle method of all 4 listeners in no specific order and not necessarily now...

Listeners can be queued

  • In a production environment, listeners are queued if they implement ShouldQueue
class NotifyOwnerThatAnswerWasPosted implements ShouldQueue {}
  • If a listeners is queued, it means it could get delayed

Do not assume that a listener has run at any moment unless you have data backing this decision.

You can use the events outside of laravel

Hint !!!

composer require illuminate/events

Note: Not an easy thing to do though!

Eloquent

Deeper down the
rabbit hole

Supercharging a scope

public function scopeWithoutActiveRiddleFor($query, Carbon $date)
{
    
    $query
        ->select('clubs')
        ->leftJoin('clubs_riddles', function (JoinClause $on) use ($date) {
            
            $on->on('clubs_riddles.club_id', '=', 'clubs.id');
            $on->where('clubs_riddles.opens_at', '>=', $date->copy()->startOfDay()->tz('UTC'));
            $on->where('clubs_riddles.opens_at', '<=', $date->copy()->endOfDay()->tz('UTC'));
        })
        ->whereNull('clubs_riddles.riddle_id');
}
  • You can use complex query elements in scopes
  • Remember to ::select if you add tables which create duplicate fields

M:M Pivots

public function achievements()
{
    return $this->belongsToMany(Achievement::class)->withPivot(['current']);
}

public function badges()
{
    return $this->belongsToMany(Badge::class)->withPivot('amount');
}
  • When using a M:M relationship, you can use "withPivot" to get information from the M:M table

M:M Pivots

$value = $record->pivot->column;
  • Extract pivot values
$riddle->creator->badges()->updateExistingPivot(
    $badge->id,
    ['amount' => $record->pivot->amount + 1]
);
  • Update/Create a pivot
$riddle->creator->badges()->attach(
    $badge,
    ['amount' => 1]
);

Supercharging a relation

public function currentAchievement()
{
    return $this->belongsToMany(Achievement::class)
                ->where('current', 1);
}
  • All relationship methods actually return a query, thus you can add as many things as you want in there such as joins, wheres, etc

Model events

class Answer
{
    use Notifiable;

    protected $events = [
        'created' => AnswerCreatedEvent::class,
    ];
}
$answer = Answer::create([
    'answer'         => $answer,
    'club_riddle_id' => $clubRiddle->id,
    'user_id'        => $user->id,
]);

Event::fire(new AnswerCreatedEvent($answer));

Accessors

public function getScoreAttribute($score)
{
    return $score * 100
}

public function getFirstNameAttribute($value)
{
    return ucfirst($value);
}
  • Accessors transform existing values on get
  • You can call the accesor using the original name of the property without changing a thing

Mutators

public function setFirstNameAttribute($value)
{
    $this->attributes['first_name'] = strtolower($value);
}
  • Modifies the value being set into the attribute
  • You can also modify more than one value with this

Virtual properties

protected $appends = array('availability');

public function getAvailabilityAttribute()
{
    return $this->calculateAvailability();  
}

public function setAvailabilityAttribute($value)
{
    $this->attributes['availability'] = $value;  
}
  • Not a real property but you can now use it with
    $obj->availability
  • $appends is used to provide more data when serializing, if you don't, the value is lost and never serialized.
  • Returning an object instead of a response will JSON serialize that object.

Serialization

protected $appends = ['someVirtualProperty'];
protected $hidden = ['password'];
  • This will prevent returning the hashed password
  • This will return the virtual property

Question?

Mathieu Dumoulin

Senior Dev @ Learning Bird

Twitter: @thecrazycodr
LinkedIn: @crazycoders
GitHub: @crazycodr
Email: thecrazycodr@gmail.com

Open source contributions

Contact me

Standard-Exceptions 2.0: GitHub

Intermediate Laravel Tips and Tricks

By Mathieu Dumoulin

Intermediate Laravel Tips and Tricks

This presentation covers some of the more advanced features of Laravel and explains how to use them and in which contexts. This presentation assumes a little understanding and experience with Laravel and will go into Service providers, Facades, the Container, the Queues, Event driven programming and even explore some deep features of the ORM that you might not have used yet.

  • 649
Loading comments...

More from Mathieu Dumoulin