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

Made with Slides.com