Slide 1

Slide 1 text

customlaravel.com by @stauffermatt

Slide 2

Slide 2 text

Before we start... ! customlaravel.com by @stauffermatt

Slide 3

Slide 3 text

customlaravel.com by @stauffermatt

Slide 4

Slide 4 text

One more thing... customlaravel.com by @stauffermatt

Slide 5

Slide 5 text

A brief history of my talks. customlaravel.com by @stauffermatt

Slide 6

Slide 6 text

Why Modern PHP Is Amazing And How You Can Use It Today PeersConf 2014 customlaravel.com by @stauffermatt

Slide 7

Slide 7 text

Sharing Laravel Laracon Eu 2014 customlaravel.com by @stauffermatt

Slide 8

Slide 8 text

Leveraging Laravel Laracon US 2015 customlaravel.com by @stauffermatt

Slide 9

Slide 9 text

Empathy Gives You Superpowers Laracon Eu 2015 customlaravel.com by @stauffermatt

Slide 10

Slide 10 text

Mastering the Illuminate Container Laracon Online 2017 customlaravel.com by @stauffermatt

Slide 11

Slide 11 text

Custom Laravel Laracon US 2017 customlaravel.com by @stauffermatt

Slide 12

Slide 12 text

Wherefore "Custom Laravel"? customlaravel.com by @stauffermatt

Slide 13

Slide 13 text

PART I The soul of customization customlaravel.com by @stauffermatt

Slide 14

Slide 14 text

Laravel: We've got the best defaults customlaravel.com by @stauffermatt

Slide 15

Slide 15 text

Laravel: Convention over configuration customlaravel.com by @stauffermatt

Slide 16

Slide 16 text

Laravel: Convention and over (when helpful) configuration customlaravel.com by @stauffermatt

Slide 17

Slide 17 text

Customizing Laravel: the basics ▸ Built-to-be-modified base code (/app/*) ▸ The most flexible container you've ever seen, and Illuminate Contracts ▸ Macros, façades, re-binding, and more ▸ Environment variables and conditional config ▸ It's Just PHP™ customlaravel.com by @stauffermatt

Slide 18

Slide 18 text

What you choose to customize matters. customlaravel.com by @stauffermatt

Slide 19

Slide 19 text

“Taking it too far is a rite of passage” - Jeffrey Way customlaravel.com by @stauffermatt

Slide 20

Slide 20 text

TwentyPercent.fm ! macros... ? (an anecdote) customlaravel.com by @stauffermatt

Slide 21

Slide 21 text

One of the hardest things to discover in any new app is: "where/how is this happening?" customlaravel.com by @stauffermatt

Slide 22

Slide 22 text

Prioritize Discoverability customlaravel.com by @stauffermatt

Slide 23

Slide 23 text

Write your apps so anyone who has read the Laravel docs can jump into them easily customlaravel.com by @stauffermatt

Slide 24

Slide 24 text

LARAVELUPANDRUNNING.COM buy customlaravel.com by @stauffermatt

Slide 25

Slide 25 text

Write your apps so anyone who has read the Laravel docs can jump into them easily customlaravel.com by @stauffermatt

Slide 26

Slide 26 text

The opposite of discoverability is... ! CUSTOMIZATION DEBT ! customlaravel.com by @stauffermatt

Slide 27

Slide 27 text

customlaravel.com by @stauffermatt

Slide 28

Slide 28 text

Discoverability caveat Just because something is less discoverable doesn't mean it's bad. Sometimes the best tool isn't the most discoverable. Sometimes it's us that needs to change, not the tool. customlaravel.com by @stauffermatt

Slide 29

Slide 29 text

Discoverability Disasters ▸ Overriding default methods like create() ▸ Modifying the request object ▸ Route::controller ▸ Custom façades that aren't façades ▸ Anything makes you go "that's so clever" instead of "that's so simple" customlaravel.com by @stauffermatt

Slide 30

Slide 30 text

customlaravel.com by @stauffermatt

Slide 31

Slide 31 text

Tag::create(request()->all()); customlaravel.com by @stauffermatt

Slide 32

Slide 32 text

How to customize Laravel, and how to do it responsibly customlaravel.com by @stauffermatt

Slide 33

Slide 33 text

PART II Customization tips & tricks customlaravel.com by @stauffermatt

Slide 34

Slide 34 text

customlaravel.com by @stauffermatt

Slide 35

Slide 35 text

Preface: the container and service providers customlaravel.com by @stauffermatt

Slide 36

Slide 36 text

The container in 5 minutes ▸ registering things into it ▸ resolving things out of it ▸ things Laravel resolves using it ▸ autowiring vs. explicit ▸ service providers ▸ aliases and Facades customlaravel.com by @stauffermatt

Slide 37

Slide 37 text

Registering things into the container ▸ Key value store: put this thing at this address ▸ "Address": a string, usually a fully-qualified class name or a shortcut string like "logger" ▸ "Thing": usually either a class name, a Closure, or an object app()->bind('address', Thing::class); app()->bind('logger', Logger::class); customlaravel.com by @stauffermatt

Slide 38

Slide 38 text

Resolving things out of the container ▸ Ask for the "thing" at the given "address" ▸ "give me the thing for address 'logger'" ▸ app()->make('logger') returns an instance of Logger ▸ Also can resolve by type hinting a container-resolved class like a Controller customlaravel.com by @stauffermatt

Slide 39

Slide 39 text

Things Laravel resolves using the container ▸ Anything Laravel creates itself it creates using the container ▸ Controllers, middleware, etc. ▸ Why we can type hint dependencies in a controller ▸ Also some methods; controller methods, firing jobs, etc. class MyController { public function __construct(APIClient $client) { customlaravel.com by @stauffermatt

Slide 40

Slide 40 text

Autowiring vs. explicit binding ▸ The container knows how by default; ask for "App\Thing" and it tries new App\Thing ▸ If App\Thing has dependencies it'll do the same for each ▸ If all dependencies are easy to resolve then you're good! If not.. ▸ app()->bind(Thing::class, function () { return new App\Thing('my-api-code'); }); customlaravel.com by @stauffermatt

Slide 41

Slide 41 text

Aliases and Facades ▸ You can "alias" one address to another: app()->alias('logger', SomeOtherLogger::class) ▸ "When I ask for SomeOtherLogger, give me 'logger'" ▸ Façades say "make a new class; let me call statics on it and proxy them through to an instance of another class" customlaravel.com by @stauffermatt

Slide 42

Slide 42 text

customlaravel.com by @stauffermatt

Slide 43

Slide 43 text

Back to the show customlaravel.com by @stauffermatt

Slide 44

Slide 44 text

Custom Laravel 101 customlaravel.com by @stauffermatt

Slide 45

Slide 45 text

Config ▸ Make custom config file keys (services.mailchimp.key) ▸ Make custom config files (config/chat.php) ▸ Make up your own .env variables and import into your config ▸ Important: Don't directly reference env() inline! customlaravel.com by @stauffermatt

Slide 46

Slide 46 text

Default controllers ▸ Default auth controllers provide customization hooks: ▸ Customize views; e.g. auth.passwords.email ▸ Customize return routes; e.g. LoginController@$redirectTo ▸ Override protected methods; e.g. validateEmail(), sendResetLinkResponse(), validateLogin() customlaravel.com by @stauffermatt

Slide 47

Slide 47 text

Default controllers (cont) ▸ Override intended "hook" methods; e.g. LoginController@authenticated(), LoginController@username() ▸ Some are placed in your default controller (RegisterController@create) but all are adjustable customlaravel.com by @stauffermatt

Slide 48

Slide 48 text

Default providers ▸ AppServiceProvider ! a.k.a. core hacking for newbs class AppServiceProvider extends ServiceProvider { public function boot() { // } public function register() { // } customlaravel.com by @stauffermatt

Slide 49

Slide 49 text

Default providers (cont) ▸ AuthServiceProvider@boot and EventServiceProvider@boot class AuthServiceProvider extends ServiceProvider { protected $policies = [ 'App\Model' => 'App\Policies\ModelPolicy', ]; public function boot() { $this->registerPolicies(); // } customlaravel.com by @stauffermatt

Slide 50

Slide 50 text

Default providers (cont) ▸ RouteServiceProvider@boot, @map ! class RouteServiceProvider extends ServiceProvider { public function boot() { // parent::boot(); } public function map() { $this->mapApiRoutes(); $this->mapWebRoutes(); // } protected function mapWebRoutes() { Route::middleware('web') ->namespace($this->namespace) ->group(base_path('routes/web.php')); } customlaravel.com by @stauffermatt

Slide 51

Slide 51 text

Pro tip Any time the stock files have //, that's a sign you should consider customizing there. class AuthServiceProvider extends ServiceProvider { public function boot() { $this->registerPolicies(); // } customlaravel.com by @stauffermatt

Slide 52

Slide 52 text

Default middleware ▸ EncryptCookies@except, TrimStrings@except, VerifyCsrfTokens@except ▸ RedirectIfAuthenticated: redirect('/home') class EncryptCookies or TrimStrings or VerifyCsrfTokens { protected $except = [ // ]; customlaravel.com by @stauffermatt

Slide 53

Slide 53 text

Custom middleware groups ▸ api and web out of the box ▸ Add more in App\Http\Kernel@$middlewareGroups customlaravel.com by @stauffermatt

Slide 54

Slide 54 text

Custom middleware groups (cont) class Kernel extends HttpKernel { protected $middlewareGroups = [ 'web' => [ \App\Http\Middleware\EncryptCookies::class, // ... ], 'api' => [ 'throttle:60,1', 'bindings', ], 'admin' [ 'web', 'some-other-awesome-middleware', ] ]; customlaravel.com by @stauffermatt

Slide 55

Slide 55 text

Custom middleware ▸ Make your own: watch for "guards", etc. in your controller methods ▸ php artisan make:middleware KeepOutBadPeople ▸ Apply globally, add to a custom middleware group, make route optional customlaravel.com by @stauffermatt

Slide 56

Slide 56 text

Custom middleware (cont) class KeepOutBadPeople { public function handle($request, Closure $next) { if ($this->isBad($request->person)) { abort(404); } return $next($request); } customlaravel.com by @stauffermatt

Slide 57

Slide 57 text

Exceptions ▸ Custom exceptions (e.g. TryingToHackUsWhatTheHeckException) ▸ Custom global exception handling rules customlaravel.com by @stauffermatt

Slide 58

Slide 58 text

Exceptions (cont) Customize exception handling: Pre-5.5 App\Exceptions\Handler@report; check for instance type and respond Customize exception handling: 5.5+ Define how to report an exception on the exception; @report() customlaravel.com by @stauffermatt

Slide 59

Slide 59 text

Includes ▸ Extract chunks of code to includes ▸ See: routes/web.php from before; also console.php ▸ What about schedule.php? customlaravel.com by @stauffermatt

Slide 60

Slide 60 text

Includes (cont) // From this: protected function schedule(Schedule $schedule) { // $schedule->command('inspire') // ->hourly(); } // To this: protected function schedule(Schedule $schedule) { require base_path('schedule.php'); } customlaravel.com by @stauffermatt

Slide 61

Slide 61 text

Frontend ▸ php artisan make:auth ▸ 5.5 Frontend presets ▸ Default files are just that; defaults! customlaravel.com by @stauffermatt

Slide 62

Slide 62 text

GIF BREAK customlaravel.com by @stauffermatt

Slide 63

Slide 63 text

customlaravel.com by @stauffermatt

Slide 64

Slide 64 text

Custom Laravel 201 customlaravel.com by @stauffermatt

Slide 65

Slide 65 text

Tests ▸ Add assertions to the base test class; e.g. assertUserIsAdmin ▸ Re-bind/mock classes, migrate/seed in setUp ▸ Custom variables in phpunit.xml ▸ Test-specific .env.testing ▸ RefreshDatabase migration trait, coming in 5.5 customlaravel.com by @stauffermatt

Slide 66

Slide 66 text

Helpers ▸ Bring back helpers.php! ▸ What works for your domain? sync(), etc. { "autoload": { "files": ["helpers.php"] } } customlaravel.com by @stauffermatt

Slide 67

Slide 67 text

Advanced container binding/re-binding ▸ Get familiar with app()->bind() and its cousins ▸ Step 1. Bind your own class app()->bind(MyClass::class, Closure) ▸ Step 2. Bind your class to interfaces app()->bind(MyInterface::class, ClosureOrClassName) customlaravel.com by @stauffermatt

Slide 68

Slide 68 text

Advanced container binding/re-binding (cont) ▸ Step 3. Bind your class to a global shortcut app()->bind('myalias', ClosureOrClassName) ▸ Step 4. Re-bind other classes/interfaces to that global shortcut app()->alias('myalias', SomeContractClassName) customlaravel.com by @stauffermatt

Slide 69

Slide 69 text

An example class MyContainer extends Container { public function storagePath() { return $this->basePath . '/storageOMGyay'; } } customlaravel.com by @stauffermatt

Slide 70

Slide 70 text

Advanced container binding/re-binding (cont) Example from Bugsnag docs: $this->app->singleton('bugsnag.logger', function (Container $app) { return new LaravelLogger($app['bugsnag']); }); $this->app->singleton('bugsnag.multi', function (Container $app) { return new MultiLogger([$app['log'], $app['bugsnag.logger']]); }); Bind: $this->app->alias('bugsnag.logger', \Illuminate\Contracts\Logging\Log::class); $this->app->alias('bugsnag.logger', \Psr\Log\LoggerInterface::class); customlaravel.com by @stauffermatt

Slide 71

Slide 71 text

Contracts Interfaces to all major Laravel components Illuminate\Contracts\*

Slide 72

Slide 72 text

Breathe customlaravel.com by @stauffermatt

Slide 73

Slide 73 text

Packages & Publishing ▸ Packages often allow you to "publish" config and view files ▸ E.g. customize Spark views or Media-Library config by running: ▸ php artisan vendor:publish ▸ Note: 5.5 vendor:publish = ! customlaravel.com by @stauffermatt

Slide 74

Slide 74 text

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

Slide 75

Slide 75 text

Façade 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::nonStaticMethodOnFitbitClass(); customlaravel.com by @stauffermatt

Slide 76

Slide 76 text

Swapping Façades and bindings in tests ▸ Swappable façades e.g.: Cache::shouldReceive('get')->once()->with('key')- >andReturn('value') ▸ Shout out to MailThief ❤ ▸ Swap `em yourself! customlaravel.com by @stauffermatt

Slide 77

Slide 77 text

Custom validation: pre-5.5 // In a service provider: Validator::extend('repo', function ($attribute, $value, $parameters, $validator) { return app(Client::class)->validRepository($url); }); // or Validator::extend('repo', 'RepoValidator@validate'); // .. in your language file: [ "repo" => "The given repository is invalid.", ] customlaravel.com by @stauffermatt

Slide 78

Slide 78 text

Custom validation: 5.5+ class ValidRepository implements Rule { public function __construct($source, $branch) { $this->source = $source; $this->branch = $branch; } public function passes($attribute, $value) { if (! $this->source instanceof Source) return false; return $this->source->client()->validRepository( $value, $this->branch ); } public function message() { return 'The given repository is invalid.'; } customlaravel.com by @stauffermatt

Slide 79

Slide 79 text

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()); customlaravel.com by @stauffermatt

Slide 80

Slide 80 text

Blade directives: traditional style // In a service provider Blade::directive('public', function () { return "isPublic()): ?>"; }); Blade::directive('endpublic', function () { return ""; }); // Use: @public Only show on the public web site @endpublic customlaravel.com by @stauffermatt

Slide 81

Slide 81 text

Blade directives: using Blade::if in 5.5 // In a service provider Blade::if('env', function ($env) { return app()->environment($env); }); @env('production') @endenv customlaravel.com by @stauffermatt

Slide 82

Slide 82 text

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(); }); customlaravel.com by @stauffermatt

Slide 83

Slide 83 text

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) { // }); customlaravel.com by @stauffermatt

Slide 84

Slide 84 text

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']; } } customlaravel.com by @stauffermatt

Slide 85

Slide 85 text

Form requests (cont) 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; } } customlaravel.com by @stauffermatt

Slide 86

Slide 86 text

GIF BREAK customlaravel.com by @stauffermatt

Slide 87

Slide 87 text

customlaravel.com by @stauffermatt

Slide 88

Slide 88 text

Custom Laravel 301 customlaravel.com by @stauffermatt

Slide 89

Slide 89 text

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) { // }); customlaravel.com by @stauffermatt

Slide 90

Slide 90 text

Arbitrary route bindings (cont) // In a service provider Route::bind('repository', function ($value) { return app('github')->findRepository($value); }); // In your routes file Route::get('repositories/{repository}', function ($repo) { // }); customlaravel.com by @stauffermatt

Slide 91

Slide 91 text

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!) customlaravel.com by @stauffermatt

Slide 92

Slide 92 text

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); customlaravel.com by @stauffermatt

Slide 93

Slide 93 text

Custom response objects: Responsable in 5.5 class Thing implements Responsable { public function toResponse() { return 'HEY THIS IS A USER RESPONSE FOR THING NUMBER ' . $this->id; } } customlaravel.com by @stauffermatt

Slide 94

Slide 94 text

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); } customlaravel.com by @stauffermatt

Slide 95

Slide 95 text

Hijacking index.php if ($response->getStatusCode() != 404) { $response->send(); $kernel->terminate($request, $response); exit; } // CodeIgniter or whatever else customlaravel.com by @stauffermatt

Slide 96

Slide 96 text

GIF BREAK customlaravel.com by @stauffermatt

Slide 97

Slide 97 text

customlaravel.com by @stauffermatt

Slide 98

Slide 98 text

Independent Study (other tools) customlaravel.com by @stauffermatt

Slide 99

Slide 99 text

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 customlaravel.com by @stauffermatt

Slide 100

Slide 100 text

Lambo pro tip ▸ Lambo with config and after scripts ❤" customlaravel.com by @stauffermatt

Slide 101

Slide 101 text

Commonly installed packages https://mattstauffer.co/blog/what-packages-do-you-install-on-every- laravel-application-you-create customlaravel.com by @stauffermatt

Slide 102

Slide 102 text

Recap 1. With great power... customize responsibly! 2. Prioritize, but don't enshrine, discoverability 3. 101: Modify the intended-to-be-modified; make your own, too 4. 201: Use the container! 5. 301: Override/custom all the classes! 6. Custom Homestead, Lambo customlaravel.com by @stauffermatt

Slide 103

Slide 103 text

customlaravel.com (for tips and slides) customlaravel.com by @stauffermatt