Custom Laravel - a talk by Matt Stauffer

Slides

(Talk notes)

Intro

My old talks

Other links

Custom Laravel 101

Config

Default controllers

Default providers

Default middleware

Custom middleware groups


class Kernel extends HttpKernel
{
    protected $middlewareGroups = [
        'web' => [
            \App\Http\Middleware\EncryptCookies::class,
            // ...
        ],

        'api' => [
            'throttle:60,1',
            'bindings',
        ],

        'admin' [
            'web',
            'some-other-awesome-middleware',
        ]
    ];
}

Custom middleware


// In your middleware
class KeepOutBadPeople
{
    public function handle($request, Closure $next)
    {
        if ($this->isBad($request->person)) {
            abort(404);
        }

        return $next($request);
    }
}

// In App/Http/Kernel.php
protected $middleware = [
    // Put here to apply globally
];

protected $middlewareGroups = [
    'web' => [
        // Put here to apply to just certain groups
    ],
];

protected $routeMiddleware = [
    // Put here to allow it to be conditionally applied
];

Exceptions

Includes

Frontend

GIF BREAK

Custom Laravel 201

Tests

Helpers

Advanced container binding/re-binding

Packages & Publishing

Custom Facades

  1. Step 1. Create the Façade class extending Illuminate\Support\Facades\Facade
  2. Step 2. Implement getFacadeAccessor() method; return a string that would resolve out of the container
  3. Step 3. Register it in config/app.php@aliases

Example:


// FitbitServiceProvider.php
public function register()
{
    $this->app->singleton('fitbit', function () {
        // ...
    });
}

// Somewhere
class Fitbit extends Illuminate\Support\Facades\Facade
{
    public function getFacadeAccessor() { return 'fitbit'; }
}

// In consuming code (controller, etc.)
Fitbit::nonStaticClassOnFitbitClass();

Swapping Facades and bindings in tests

Custom validation

Macros


// In a service provider
Response::macro('jsend', function ($body, $status = 'success') {
    if ($status == 'error') {
        return Response::json(['status' => $status, 'message' =>  $body]);
    }

    return Response::json(['status' => $status, 'data' => $body]);
});

// In a route
return response()->jsend(Order::all());

Blade Directives

View Composers


// In a service provider
// Class-based composer
View::composer(
    'minigraph', 'App\Http\ViewComposers\GraphsComposer'
);

// Closure-based composer
View::composer('minigraph', function ($view) {
    return app('reports')->graph()->mini();
});

Custom route model bindings


// In a service provider
Route::bind('user', function ($value) {
    return App\User::public()
        ->where('name', $value)
        ->first();
});

// In your binding
Route::get('profile/{user}', function (App\User $user) {
    //
});

Form requests


// Traditional request injection
public function index(Request $request) {}

// Form Request Validation
public function store(StoreCommentRequest $request) {}

// Form Request
class StoreCommentRequest extends FormRequest {
    public function authorize() { return (bool)rand(0,1); }
    public function rules () { return ['validation rules here']; }
}

... but you can also create custom methods!


class StoreCommentRequest extends FormRequest
{
    public function sanitized()
    {
        return array_merge($this->all(), [
            'body' => $this->sanitizeBody($this->input('body'))
        ]);
    }

    protected function sanitizeBody($body)
    {
        // Do stuff
        return $body;
    }
}

GIF BREAK!

Custom Laravel 301

Arbitrary route bindings


// In a service provider
Route::bind('topic', function ($value) {
    return array_get([
        'automotive' => 'I love cars!',
        'pets' => 'I love pets!',
        'fashion' => 'I love clothes!'
    ], $value, 'undefined');
});

// In your routes file
Route::get('list/{topic}', function ($topic) {
    //
});

// In a service provider
Route::bind('repository', function ($value) {
    return app('github')->findRepository($value);
});

// In your routes file
Route::get('repositories/{repository}', function ($repo) {
    //
});

Custom request objects


class MyMagicalRequest extends Request
{
    // Add methods, properties, etc.
}

// Type-hint in a route
public function index(MyMagicalRequest $request) {}

// Or even re-bind globally in public/index.php
$response = $kernel->handle(
    // $request = Illuminate\Http\Request::capture()
    $request = App\MyMagicalRequest::capture()
);

// (but, remember, macros!)

Custom response objects


class MyMagicalResponse extends Response
{
    // Add methods, properties, etc.
}

// Return from a route
public function index()
{
    return MyMagicalResponse::forUser(User::find(1337));
}

// Or macro it
Response::macro('magical', function ($user) {
    return MyMagicalResponse::forUser($user);
});

return response()->magical($user);

Custom response objects: with Responsable in 5.5


class Thing implements Responsable
{
    public function toResponse()
    {
        return 'USER RESPONSE FOR THING NUMBER ' . $this->id;
    }
}

Custom Eloquent collections


class ItemCollection extends Collection
{
    public function price()
    {
        return $this->sum('amount') + $this->sumTax();
    }

    public function sumTax()
    {
        return $this->reduce(function ($carry, $item) {
            return $carry + $item->tax();
        }, 0);
    }
}

class Item
{
    public function newCollection(array $models = [])
    {
        return new ItemCollection($models);
    }
}

Hijacking index.php


if ($response->getStatusCode() != 404) {
    $response->send();
    $kernel->terminate($request, $response);
    exit;
}

// CodeIgniter or whatever else

GIF BREAK!

Independent study

Custom Homestead config


composer require laravel/homestead --dev
php vendor/bin/homestead make
# generates Homestead.yaml:

ip: 192.168.10.10
memory: 2048
cpus: 1
provider: virtualbox
authorize: ~/.ssh/id_rsa.pub
keys:
    - ~/.ssh/id_rsa
folders:
    -
        map: /Users/mattstauffer/Sites/live-from-new-york-its-me
        to: /home/vagrant/Code/live-from-new-york-its-me
sites:
    -
        map: live-from-new-york-its-me
        to: /home/vagrant/Code/live-from-new-york-its-me/public
databases:
    - homestead
name: live-from-new-york-its-me
hostname: live-from-new-york-its-me

Lambo pro tip

Lambo with config and after scripts ❤️🏎️
https://mattstauffer.co/blog/lambo-config-and-after-scripts-for-even-better-laravel-app-creation

Commonly-installed packages

https://mattstauffer.co/blog/what-packages-do-you-install-on-every-laravel-application-you-create