86
LARAVEL 5 Darren Craig @minusdarren

What's New In Laravel 5

Embed Size (px)

Citation preview

LARAVEL 5

Darren Craig@minusdarren

@minusdarren

Introduction

Darren CraigCTO - @minus40co

★ Built my first website in 1998

★ Hosted on FortuneCity ★ Developing in PHP for over 10 years ★ Superstar DJ

… in Microsoft Word

(ish)

Caveats

★ Code is an example only - not best practice… ★ …or consistent! ★ Mix of basic and advanced ideas ★ Please ask questions! ★ More than one way to skin a cat

Overview

What is Laravel?

★ PHP Framework ★ V1 was released in 2011 ★ Taylor Otwell (@taylorotwell) ★ V5 released in February 2015 ★ Laracon - Annual conferences in EU & US

Why are you talking about it?

★ Using Laravel since v3 ★ Excellent Framework ★ Striving for better & more robust applications ★ Laravel helping lead the charge

★ PSR-4 ★ Contracts/Interfaces ★ Command Bus ★ SOLID principles ★ Community - DDD, Testing, Event Sourcing

Under the hood

★ Uses Composer (http://getcomposer.org) ★ >= PHP 5.4 ★ Leverages lots of Symfony components ★ Artisan CLI ★ Eloquent ORM

Included Tools

PHPSpec

★ Testing library ★ Uses “Design by Specification”

class EmailAddressSpec { public function it_validates_the_email()

{ $this->shouldThrow('\Exception')->during('__construct', ['InvalidEmail.com']) }

public function it_normalizes_the_address() {

$this->beConstructedWith('[email protected]'); $this->getEmail()->shouldReturn('[email protected]'); } }

vendor/bin/phpspec describe EmailAddress

vendor/bin/phpspec run

PHPSpec

class EmailAddress { private $email; public function __construct($email) { if( !filter_var($email, FILTER_VALIDATE_EMAIL) ) throw new \Exception; $this->email = strtolower($email); } public function getEmail() { return $this->email; } }

Elixir

★ Wrapper for GulpJS

var elixir = require('laravel-elixir');

elixir(function(mix) { mix.less('app.less');

mix.styles([ "normalize.css", "main.css" ], 'public/build/css/everything.css');

mix.version(["css/all.css", "js/app.js"]); });

href=“/css/all-16d570a7.css“> <link rel="stylesheet" href="{{ elixir("css/all.css") }}">

Directory Structure

Directory Structure

★ Config, DB, Views moved above the app directory

★ New directories within app/

★ No “models” directory ★ Leverages PSR-4

autoloading(http://www.php-fig.org/psr/psr-4/)

★ App/ namespace = app/ folder

php artisan app:name Acme

Directory Structure

app Commands Console Events Handlers Commands Events Http Controllers Middleware Requests Providers Services

bootstrap config database migrations seeds public package resources lang views storage cache logs meta sessions views work tests

Service Providers

★ Bootstrap/configure classes in your application ★ app/Providers

★ AppServiceProvider.php ★ BusServiceProvider.php ★ ConfigServiceProvider.php ★ EventServiceProvider.php ★ RouteServiceProvider.php

★ Referenced in config/app.php

Routes ’n Things

Routing

★ Routes moved to app/Http/routes.php

★ RESTful routes

★ No namespace necessary!

Route::get(‘/', 'WelcomeController@index');

$router->get(‘/','WelcomeController@index');

Route::get(…); Route::post(…); Route::put(…); Route::patch(…); Route::delete(…);

Routing

★ Route Variables

Route::get('video/{video}', ‘VideoController@show');

Route::get('/video/{video}/comments/{comment}', 'VideoCommentsController@show');

// VideoController public function show($videoId) {}

// VideoCommentsController public function show($videoId, $commendId) {}

★ Resource Routes

Route::resource('video', 'VideoController'); @index @show @create

@store @edit @update

@delete

Routing

★ Implicit Controllers

Route::controller('videos', ‘VideoController');

class VideoController extends Controller

{

public function getIndex() {} // GET videos

public function postProfile() {} // POST videos/profile

public function anyAction() {} // ANY videos/action

}

RouteServiceProvider

class RouteServiceProvider extends ServiceProvider {

protected $namespace = ‘App\Http\Controllers';

public function map(Router $router) {

$router->group(['namespace' => $this->namespace], function($router) {

require app_path('Http/routes.php'); }); } }

Route Cache

★ Resolves and caches your routes file ★ Drastically speeds up applications with lots of

routes

php artisan route:cache

Eloquent

Eloquent

★ Laravel’s own Active Record implementation ★ Beautifully expressive syntax ★ Allows you to easily query and update your database ★ Has drivers for MySQL, SQLite, Postgres, SQL Server

and Redis out of the box. ★ Lots of packages available for DB support

Eloquent

class User extends Model { public function comments() { return $this->hasMany('Comment'); } }

$user = User::all();

$user = User::find(1);

$user = User::where('name', =, $name)->first();

$user = User::where('age', <, 18)->get();

$user = User::with('comments')->get();

@foreach($user->comments as $comment)

<li>{{ $comment->body }}</li>

@endforeach

Eloquent N+1 Problem

// 1 query per comment

$user = User::first();

@foreach($user->comments as $comment)

<li>{{ $comment->body }}</li>

@endforeach

// 1 query total

$user = User::with('comments')->first();

@foreach($user->comments as $comment)

<li>{{ $comment->body }}</li>

@endforeach

IoC Container

IoC Container

class UserController { public function getProfile() { $facebook = new Facebook(['appId' => 123, 'secret' => 'cohaaagan']);

$user = $facebook->getUser(); }

public function getFriends() { $facebook = new Facebook(['appId' => 123, 'secret' => 'cohaaagan']);

$friends = $facebook->getFriends(); } }

IoC Container

class FooController { private $facebook;

public function __construct() { $this->facebook = new Facebook(['appId' => 123, 'secret' => 'cohaaagan']); } }

class BarController { private $facebook;

public function __construct() { $this->facebook = new Facebook(['appId' => 123, 'secret' => 'cohaaagan']); } }

IoC Container

class AppServiceProvider extends ServiceProvider {

public function register() {

$this->app->bind('Facebook', function() { return new Facebook(['appId' => 123, 'secret' => 'cohaaagan']); });

} }

★ You can ‘tell’ Laravel to provide a fully instantiated class instead of the requested class

IoC Container

class FooController {

private $facebook;

public function __construct() {

$this->facebook = App::make('Facebook');

}

}

class FooController {

private $facebook;

public function __construct(Facebook $facebook) {

$this->facebook = $facebook;

}

}

Contracts

Contracts

★ Also known as “Interfaces” ★ Define the ‘Public API’ of your classes ★ Packaged as part of L5 ★ One for each of the the core Laravel services ★ Help you ‘decouple’ your code

Contracts

class UserController

{

public function __construct(\Some\Vendor\MailGun $mail) {

$this->mail = $mail;

}

public function registerUser() {

$this->mail->send(...);

}

}

★ “Tightly coupled” ★ What would happen if we changed from MailGun

to Mandrill, or Gmail?

Contracts

namespace Illuminate\Contracts\Mail;

interface Mailer {

public function raw($text, $callback);

public function send($view, array $data, $callback);

public function failures();

}

class UserController { public function __construct(\Illuminate\Contracts\Mail\Mailer $mail) { $this->mail = $mail; }

public function registerUser() { $this->mail->send(...); } }

Contracts

use Illuminate\Contracts\Mail\Mailer;

class FacebookMailer implements Mailer { public function raw($text, $callback) {};

public function send($view, array $data, $callback) {};

public function failures() {}; }

class AppServiceProvider extends ServiceProvider {

public function register() {

$this->app->bind('Illuminate\Contracts\Mail\Mailer', function() {

return new FacebookMailer();

});

}

}

Contracts

class UserController { public function __construct(\Illuminate\Contracts\Mail\Mailer $mail) { $this->mail = $mail; }

public function registerUser() { $this->mail->send(...); } }

class AppServiceProvider extends ServiceProvider {

public function register() {

$this->app->bind('Illuminate\Contracts\Mail\Mailer', function() {

return new TwitterMailer();

});

}

}

File Drivers

File Drivers

★ Improved version of the previous Filesystem class ★ Now powered by Flysystem

(flysystem.thephpleague.com) ★ Allows for local & remote filesystems at the same

time ★ Compatible with S3 & Rackspace ★ Support available for lots of others

composer require league/flysystem-aws-s3-v2 ~1.0 composer require league/flysystem-rackspace ~1.0

File Drivers - Configuration

// config/filesystems.php

return [

'default' => 'local', 'cloud' => 's3', 'disks' => [

'local' => [ 'driver' => 'local', 'root' => storage_path().'/app', ],

's3' => [ 'driver' => 's3', 'key' => 'your-key', 'secret' => 'your-secret', 'region' => 'your-region', 'bucket' => 'your-bucket', ],

'rackspace' => [ 'driver' => 'rackspace', 'username' => 'your-username', 'key' => 'your-key', 'container' => 'your-container', 'endpoint' => 'https://identity.api.rackspacecloud.com/v2.0/', 'region' => 'IAD', ], ], ];

File Drivers - Usage

$disk = Storage::disk('cloud');

$disk = Storage::disk('local');

$disk = Storage::disk('s3');

$exists = Storage::disk('s3')->exists('file.jpg');

$exists = $disk->exists('file.jpg');

$file = Storage::get('file.jpg');

Storage::put('file.jpg', $contents);

Storage::prepend('file.log', 'Prepended Text');

Storage::append('file.log', 'Appended Text');

Storage::delete('file.jpg');

Storage::delete(['file1.jpg', 'file2.jpg']);

Storage::copy('old/file1.jpg', 'new/file1.jpg');

Method Injection

Method Injection

★ Uses the IoC container to resolve dependencies injected directly into a method.

★ Helps cut down on cluttered constructor methods

class UserController {

public function registerUser(Mailer $mail) { // do some registration stuff $mail->send(); }

// Route::post('user/{id}/mail', 'UserController@mailUser'); public function mailUser($userId, Mailer $mail) {

// do some registration stuff $mail->send(); } }

Form Requests

Form Requests

★ Aim to standardise and simplify form validation ★ Validation in V3/4 was verbose:

class UserController { public function register() { $rules = [ 'name' => 'required', 'email' => 'email|required' ];

$validator = Validator::make(Input::all(), $rules); if($validator->fails()) return redirect()->back()->withErrors(); // validation passed, do something... } }

Form Requests

★ Form requests make things very clean…

class UserController { public function register(RegisterUserRequest $request) { // validation passed, do something... } }

★ Automatically validates ★ Automatically redirects back, with errors

Form Requests

★ How to generate a request

php artisan make:request RegisterUserRequest

★ Stored in app/Http/Requests

class RegisterUserRequest extends Request {

public function authorize() { return false; }

public function rules() { return [ 'name' => 'required', 'email' => 'email|required' ]; } }

Forms & HTML Helpers

★ Uses an expressive syntax to generate form fields & HTML entities

★ Removed from L5 as default ★ Available as a composer package

composer require illuminate/html ~5.0

Middleware

Middleware

★ Route Decorators ★ Similar to filters (still available) ★ Executed before the logic of your application

Middleware

★ Stored in app/Http/Middleware: ★ Authenticate ★ RedirectIfAuthenticated ★ VerifyCSRFToken

★ Easy to add your own

php artisan make:middleware WhitelistedIP

Middleware

class WhitelistedIP {

$ipWhitelist = [ '123.123.123.123' ];

public function handle($request, Closure $next) { if(!in_array($request->getClientIp(), $this->ipWhitelist)) { return redirect('/'); }

return $next($request); } }

“After” Middleware

class DoSomethingAfter {

public function handle($request, Closure $next) { $response = $next($request);

// Perform action

return $response; }

}

Middleware

<?php namespace App\Http;

use Illuminate\Foundation\Http\Kernel as HttpKernel;

class Kernel extends HttpKernel {

protected $middleware = [ 'Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode', 'Illuminate\Cookie\Middleware\EncryptCookies', 'Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse', 'Illuminate\Session\Middleware\StartSession', 'Illuminate\View\Middleware\ShareErrorsFromSession', 'App\Http\Middleware\VerifyCsrfToken', 'App\Http\Middleware\WhitelistedIP' ];

protected $routeMiddleware = [ 'auth' => 'App\Http\Middleware\Authenticate', 'auth.basic' => 'Illuminate\Auth\Middleware\AuthenticateWithBasicAuth', 'guest' => 'App\Http\Middleware\RedirectIfAuthenticated', 'whitelisted' => 'App\Http\Middleware\WhitelistedIP', ]; }

Applying Route Middleware

Route::group('admin/*', ['middleware' => 'whitelisted', function() { // protected }]);

class UserController extends Controller {

public function __construct() { $this->middleware('auth');

$this->middleware('log', ['only' => ['fooAction', 'barAction']]);

$this->middleware('subscribed', ['except' => ['fooAction', 'barAction']]); } }

Cron & Scheduling

Cron & Scheduling

15 6 2 1 * /home/melissa/backup.sh 17 6 2 1 * /home/carl/hourly-archive.sh 19 6 2 1 * /sites/my-site/artisan command:run 30 6 2 1 * /sites/my-site/artisan arnie:gettothechopper

Laravel’s Scheduler

// app/Console/Kernel.php class Kernel extends ConsoleKernel {

protected function schedule(Schedule $schedule) { $schedule->command('inspire')->hourly(); $schedule->command('foo')->everyFiveMinutes(); $schedule->command('foo')->everyThirtyMinutes(); $schedule->exec('composer self-update')->daily(); $schedule->command('foo')->weekdays(); $schedule->command('foo')->mondays(); $schedule->command(‘foo')->sendOutputTo($filePath)

->emailOutputTo('[email protected]'); } }

* * * * * php /path/to/artisan schedule:run 1>> /dev/null 2>&1

Command Bus

Command Bus

★ Separates the logic of you application into small, manageable actions

★ Creates reusable classes that can be used from different access points to your application

★ Helps keep your code DRY ★ Helps make your code testable

Command Bus

[ request ]

[ command ]

[ bus ]

[ handler ]

Command Bus

[ request ]

[ command ]

[ bus ]

[ handler ]

A data transfer object, or message, which contains only the information required for carrying out a specific action

Matches a command to the corresponding handler and executes it

Responsible for carrying out the ‘task’ in response to the command.

Command Bus

[ request ]

[ command ]

[ bus ]

[ handler ]

POST /user/register

RegisterUser

RegisterUserHandler

POST /user/updatePassword

UpdateUserPassword

UpdateUserPasswordHandler

Creating a command

class RegisterUser extends Command { public $name; public $email; public $password;

public function __construct($name, $email, $password) { $this->name = $name; $this->email = $email; $this->password = $password; } }

php artisan make:command RegisterUser

app/Commands/RegisterUser.php

Dispatching the command

class UserController extends Controller {

use DispatchesCommands;

public function postRegister(RegisterUserRequest $request)

{

$this->dispatch(new RegisterUser(

$request->get('name'),

$request->get('email'),

$request->get('password')

));

// OR

$this->dispatchFrom(RegisterUser::class, $request);

}

}

The Command Handler

class RegisterUserHandler

{

public function handle(RegisterUser $command)

{

$user = User::create([

'name' => $command->name,

'email' => $command->email,

'password' => \Hash::make('password')

]);

}

}

php artisan handler:command RegisterUserHandler

Creating a command

class UpdateUserPassword extends Command {

public $userId; public $password;

public function __construct($userId, $password) {

$this->userId = $userId; $this->password = $password; } }

php artisan make:command UpdateUserPassword

app/Commands/UpdateUserPassword.php

The Command Handler

class UpdateUserPasswordHandler

{

public function handle(RegisterUser $command)

{

$user = User::find($command->userId);

$user->updatePassword(

\Hash::make($command->password)

);

}

}

php artisan handler:command UpdateUserPasswordHandler

Queued Commands

php artisan make:command ExampleQueuedCommand --queued

use Illuminate\Queue\SerializesModels; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Contracts\Queue\ShouldBeQueued;

class ExampleQueuedCommand extends Command implements ShouldBeQueued { use InteractsWithQueue, SerializesModels;

public function __construct() { // } }

Events

Events

★ Also known as the Publish-Subscribe pattern ★ Used to model concerns of the application you’re

building ★ Work in a similar way to Commands

Generating An Event

php artisan make:event UserWasRegistered

use App\Events\Event;

use Illuminate\Queue\SerializesModels;

class UserWasRegistered extends Event {

use SerializesModels;

public $user;

public function __construct(User $user)

{

$this->user = $user;

}

}

app/EventsUserWasRegistered.php

The Event Handler

class SendRegistrationConfirmation {

public function __construct()

{

//

}

public function handle(UserWasRegistered $event)

{

// send mail

}

}

php artisan handler:event SendRegistrationConfirmation --event=UserWasRegistered

Registering Events

class EventServiceProvider extends ServiceProvider {

protected $listen = [

UserWasRegistered::class => [

SendRegistrationConfirmation::class,

GenerateHolidayEntitlement::class,

SetServerPermissions::class,

NotifyCurrentEmployees::class

]

];

}

app/Providers/EventServiceProvider.php

Firing Events

class RegisterUserHandler

{

public function handle(RegisterUser $command)

{

// register the user $user

\Event::fire(new UserWasRegistered($user));

}

}

app/Handlers/Commands/RegisterUserHandler.php

Optional Packages

Optional Packages

★ Socialite ★ Provides a consistent interface for

authentication using social networks ★ Returns a consistent user object & API to get

name, email, avatar, etc

composer require "laravel/socialite": "~2.0"

Optional Packages

★ Cashier ★ Provides a fluent interface for Stripe ★ Handles creating/modifying subscriptions,

recurring charges, free trials, failed payments and invoices

composer require "laravel/cashier": "~3.0"

Learning More

Learning Laravel

★ Laravel Docs (http://laravel.com/docs) ★ Laracasts (http://laracasts.com) ★ Twitter @laravelphp ★ IRC

★ #Laravel ★ #Dev-Discussions

Phew! Questions?

Thanks! Pints?@minusdarren