Upload
darren-craig
View
738
Download
1
Embed Size (px)
Citation preview
@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
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
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
★ 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
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
★ 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
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
★ 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
★ 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
★ 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
★ 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
★ 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
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
★ 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 ]
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
★ 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
★ 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 Laravel
★ Laravel Docs (http://laravel.com/docs) ★ Laracasts (http://laracasts.com) ★ Twitter @laravelphp ★ IRC
★ #Laravel ★ #Dev-Discussions