Demystifying the Laravel Magic

Some code feels magical

Laravel magic

  • Route Model Binding
  • Validation
  • Macros
  • Higher Order Messages
  • Scheduler
  • Eloquent
  • Packages
  • Mails
  • And much more...

 (or at least seems to be magical?)

How is all
this working?

why bother?

  • Confidence
  • Improvisation
  • Replication
  • Growth

(When I know it's working well and will work)

Route Model Binding

use App\User;

Route::get('users/{user}', function (User $user) {
    return $user->email;
});

// Otherwise, we would have to do
$user = User::find($userId);
return $user->email;

// Open - https://example.com/users/1
// Response - gauravmakhecha@gmail.com

Validation

$request->validate([
    'title' => 'required|unique:posts|max:255',
    'author.name' => 'required',
    'author.description' => 'required',
]);

// Otherwise would have to put tons of
// `if` conditions before our main logic.

// This won't let the user specify same title
// or skip the author details.

Scheduler

$schedule->command('mumbai:meetup')
    ->weekends()
    ->hourly()
    ->timezone('India/Kolkata')
    ->between('11:00', '14:00')
;

// Run `mumbai:meetup` command
// hourly
// on weekends
// between 11 AM and 2 PM
// in the indian timezone.

// And don't forget `Vada Pav`

Eloquent

App\Flight::withCount('passengers')
    ->with('routes')
    ->whereHas('routes', function ($query) {
    	$query->where('over_the_ocean', false);
    })
    ->where('is_domestic', 1)
    ->orWhere('cost', '<=', 1000)
    ->orderBy('name')
    ->limit(10)
    ->get()
;

// Similar to plain English, isn't it?

Macros

// Inside a service provider

public function boot()
{
    Response::macro('caps', function ($val) {
        return Response::make(strtoupper($val));
    });
}


// Somewhere in the app

return response()->caps('foo');

Higher Order Messages

$attendees = Attendee::where('blind_bird', 1)
    ->get()
;

// instead of
$attendees->each(function ($attendee) {
    $attendee->sayThankYou();
});

$attendees->each->sayThankYou();

// average, avg, contains, each, every,
// filter, first, flatMap, groupBy, keyBy,
// map, max, min, partition, reject, some,
// sortBy, sortByDesc, sum, and unique.

Packages

Service Providers

- Ignition
- Laravel Debugbar
- Laravel Passport
- Laravel Horizon
- Laravel Telescope
- All packages by Spatie

Mails

Mail::to($user)->send(new OrderShipped($order));

// Sends an email based on the configuration.

// I can't even think how would I consider
// config based emails manually

Service Container

Static Proxies

Auth::login($user);

// instead of
new(\Illuminate\Auth\AuthManager)->login($user);

// Best thing: These can be mocked for testing.

Facades

Service providers are the central place of all Laravel application bootstrapping. Your own application, as well as all of Laravel's core services are bootstrapped via service providers.

Service Providers

Let's dive into the framework

and go through a request lifecycle to understand them.
$kernel = $app->make(
    Illuminate\Contracts\Http\Kernel::class
);

$response = $kernel->handle(
    $request = Illuminate\Http\Request::capture()
);

$response->send();
public/index.php
public function handle($request)
{
    try {
        $request->enableHttpMethodParameterOverride();

        $response = $this->sendRequestThroughRouter(
            $request
        );
    }
    ...
    return $response;
}
Illuminate/Foundation/Http/Kernel.php
index.php -> Kernel.php@handle()
protected function sendRequestThroughRouter($request)
{
    $this->app->instance('request', $request);
    Facade::clearResolvedInstance('request');
 
    $this->bootstrap();
 
    ...
}
Illuminate/Foundation/Http/Kernel.php
index.php -> Kernel.php@handle() -> @sendRequestThroughRouter()
public function bootstrap()
{
    if (! $this->app->hasBeenBootstrapped()) {
        $this->app->bootstrapWith(
            $this->bootstrappers()
        );
    }
}
Illuminate/Foundation/Http/Kernel.php
index.php -> Kernel.php@handle() -> @sendRequestThroughRouter() -> @bootstrap()
protected $bootstrappers = [
    \Ill.\Found.\Bootstrap\LoadEnvironmentVariables::cl.,
    \Ill.\Found.\Bootstrap\LoadConfiguration::class,
    \Ill.\Found.\Bootstrap\HandleExceptions::class,
    \Ill.\Found.\Bootstrap\RegisterFacades::class,
    \Ill.\Found.\Bootstrap\RegisterProviders::class,
    \Ill.\Found.\Bootstrap\BootProviders::class,
];
Illuminate/Foundation/Http/Kernel.php
'providers' => [
    Ill.\Auth\AuthServiceProvider::class,
    Ill.\Broadcasting\BroadcastServiceProvider::class,
    // Lots of service providers by the framework
    // ...

    App\Providers\AppServiceProvider::class,
    App\Providers\AuthServiceProvider::class,
    App\Providers\EventServiceProvider::class,
    App\Providers\RouteServiceProvider::class,
],
config/app.php
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    public function register()
    {
        //
    }

    public function boot()
    {
        //
    }
}
app/Providers/AppServiceProvider.php

Service Provider scope

Additionally, it can bind things to the Service container

}

Package

Development
// What can we do in service providers?

- Load/merge configuration files
- Load routes
- Load migrations
- Load translations
- Load views
- Add commands
- Load assets

Breaking slide

aka Breaking news

Some of our open-source packages (https://github.com/freshbitsweb/)

Service Container

The Laravel service container is a powerful tool for managing class dependencies and performing dependency injection.

require __DIR__.'/../vendor/autoload.php';

// Turn On The Lights
$app = require_once __DIR__.'/../bootstrap/app.php';
public/index.php
$app = new Illuminate\Foundation\Application(
    $_ENV['APP_BASE_PATH'] ?? dirname(__DIR__)
);

return $app;
bootstrap/app.php
  • Auth
  • Broadcasting
  • Console
  • Cookie
  • Database
  • Filesystem
  • Mail
  • Support
  • Validation
  • Many more...

INTERFACES/Contracts

1st use case

$this->app->bind(
    'Illuminate\Contracts\Cache\Repository',
    'Illuminate\Cache\FileStore'
);
in a service provider...
$this->app->when(PhotoController::class)
          ->needs(Filesystem::class)
          ->give(Storage::disk('local'));

$this->app->when([VideoController::class])
          ->needs(Filesystem::class)
          ->give(Storage::disk('s3'));
use Ill.\Contracts\Cache\Repository as CacheContract;
...

public function __construct(CacheContract $cache)
{
    $this->cache = $cache;
}
...

public function write($sessionId, $data)
{
    return $this->cache->put(...);
}
Illuminate/Session/CacheBasedSessionHandler.php

Resolve the class in your app using the service container instead of creating a new instance manually.

 

And create a dummy implementation of a class when testing your application.

Mocking

2nd use case

use App\Services\Salesforce;

// instead of
$salesforce = new Salesforce();

$salesforce = resolve(Salesforce::class);

$salesforce->addLead($user);

// or
public function __construct(Salesforce $salesforce)
{
    $this->salesforce = $salesforce;
}
somewhere in your app...
use App\Services\Salesforce;
use Tests\Mocks\Salesforce as SalesforceMock;
...

public function setUp()
{
    parent::setUp();

    // Mock Salesforce calls
    $this->app->bind(Salesforce::class, function () {
        return new SalesforceMock;
    });
}
Tests/TestCase.php

The service container is, after all, a container, right?

 

We can put anything in there as per our requirement as far as we use it wisely.

Arbitrary

3rd use case

// Set the binding to login Rider automatically
app()->singleton('impersonate_rider', function () {
    return true;
});
app/Http/Controllers/API/ResetPasswordController.php
public function validateForPassportPasswordGrant($password)
{
    if (app()->offsetExists('impersonate_rider')) {
        return true;
    }
    ...
}
app/Rider.php

Facades

Laravel facades serve as "static proxies" to underlying classes in the service container, providing the benefit of a terse, expressive syntax while maintaining more testability and flexibility than traditional static methods.

Cache::get('my_lottery_number');
class Cache extends Facade
{
    /**
     * Get the registered name of the component.
     *
     * @return string
     */
    protected static function getFacadeAccessor()
    {
        return 'cache';
    }
}
Illuminate/Support/Facades/Cache.php
public function registerCoreContainerAliases()
{
    foreach ([
        'auth' => [\Illuminate\Auth\AuthManager::class],
        'cache' => [\Ill.\Cache\CacheManager::class],
        'files' => [\Ill.\Filesystem\Filesystem::class],
        'session' => [\Ill.\Session\SessionManager::c.],
        ...
    ] as $key => $aliases) {
        foreach ($aliases as $alias) {
            $this->alias($key, $alias);
        }
    }
}
Illuminate/Foundation/Application.php
public static function __callStatic($method, $args)
{
    // Gets instance from container using the 'key'
    $instance = static::getFacadeRoot();
    ...

    return $instance->$method(...$args);
}
Illuminate/Support/Facades/Facade.php
// This means that
Cache::get('my_lottery_number');
// is same as
(new \Ill.\Cache\CacheManager())->get('my_lottery_number');
public function testOrderShipping()
{
    Mail::fake();

    // Perform order shipping...

    Mail::assertSent(
        OrderShipped::class,
        function ($mail) use ($order) {
            return $mail->order->id === $order->id;
        }
    );

    Mail::assertNotSent(AnotherMailable::class);
}
Tests/Feature/ExampleTest.php
public function testGetIndex()
{
    Cache::shouldReceive('get')
                ->once()
                ->with('my_lottery_number')
                ->andReturn('420');

    $response = $this->get('/users');

    // ...
}
Tests/Feature/UserControllerTest.php

Service Providers

Service Container

Facades

There is much more in the framework to bring magic into your development. Keep hunting...

They help make apps Readable, Maintainable, Well-tested, and Well-documented.

in closing

Thank you

Made with Slides.com