Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Sharing Laravel - ZendCon 2015 Edition

Matt Stauffer
October 21, 2015

Sharing Laravel - ZendCon 2015 Edition

Matt Stauffer

October 21, 2015


  1. Sharing Laravel @stauffermatt Most Laravel tutorials assume a blank slate.

    (or, that you’re developing in Laravel at all)
  2. Sharing Laravel @stauffermatt The goal: Learn how to bring the

    best assets of the Laravel world to non-Laravel projects.
  3. Sharing Laravel @stauffermatt What is Laravel doing right? Modern coding

    practices Collection of great community packages: Symfony, League, PHPDotEnv, etc. Rapid application development Collection of great unique (Illuminate) packages Easy interfaces—contracts and simple, interchangeable mail/queue/cache/session/file/etc. drivers Thriving and positive community Adoption of advanced patterns & practices
  4. Sharing Laravel @stauffermatt My side (or main) project is in

    CodeIgniter/ZF1/etc… what do I do? Frequent question
  5. Sharing Laravel @stauffermatt Nuking your old project is a Bad

    Idea. 1. It’s going to take longer than you think. 2. It’s going to take longer than you think. 3. There’s more domain knowledge in your old project than you can imagine. 4. Development on the production site will halt during development. 5. It’s going to take longer than you think.
  6. Sharing Laravel @stauffermatt UNDERCOVER LARAVEL • If you’re working in

    a legacy codebase… Sneak it in.
 Test here. DI there. Illuminate component there. • Prove the value before you’re asking anyone to pay for it. • On every task or project, look for one way to make your codebase a little more Laravel-y.
 • If you’re working on a non-legacy, but non-Laravel, codebase…
 use the components.
  7. Sharing Laravel @stauffermatt The 6 steps of bringing the “best

    of Laravel” to your non-Laravel projects
  8. Sharing Laravel @stauffermatt WRITE GOOD CODE OOP best practices SOLID

    Law of Demeter Loose coupling Not just wrapping procedural code in classes STEP 1
  9. Sharing Laravel @stauffermatt WRITE GOOD CODE Design patterns Identifying best-practice,

    commonly-used patterns; giving each a name; and suggesting situations in which each will (and won’t) prove useful STEP 1
  10. Sharing Laravel @stauffermatt WRITE GOOD CODE Design patterns Examples: •

    Adapter Pattern • Singleton Pattern • Observer Pattern • Many more… STEP 1
  11. Sharing Laravel @stauffermatt WRITE GOOD CODE Keep it Simple, Stupid

    Sandi Metz’ Rules for Developers https://robots.thoughtbot.com/sandi-metz-rules-for-developers 1. Classes can be no longer than one hundred lines of code. 2. Methods can be no longer than five lines of code. 3. Pass no more than four parameters into a method. Hash options are parameters. 4. Controllers can instantiate only one object. Therefore, views can only know about one instance variable and views should only send messages to that object (@object.collaborator.value is not allowed). STEP 1 “You should break these rules only if you have a good reason or your pair lets you.”
  12. Sharing Laravel @stauffermatt Read the books From Apprentice to Artisan

    Taylor Otwell Implementing Laravel
 Chris Fidao Laravel Up & Running (2016)
 Matt Stauffer
 laravelupandrunning.com LEARN FROM LARAVEL STEP 2
  13. Sharing Laravel @stauffermatt IRC like it’s 1999 (or Slack like

    it’s 2015) Freenode #laravel / larachat.co You could hang out in #laravel and never have written a line of Laravel code in your life. LEARN FROM LARAVEL STEP 2
  14. Sharing Laravel @stauffermatt MODERNIZE YOUR FOUNDATION Code standards PSR-2 in

    Laravel 5! PHP CodeSniffer (PHPStorm, Phil Sturgeon blog post on Sublime Text) PHP-CS-Fixer (Automatically fixes your code) Nitpick.io (comments on your pull requests notifying about PSR-2 violations—tweet @adamwathan for beta access) STEP 3
  15. Sharing Laravel @stauffermatt Stay Classy Procedural code/functions -> methods on

    classes -> retroactive designed OOP Namespaces & autoloading STEP 3 MODERNIZE YOUR FOUNDATION
  16. Sharing Laravel @stauffermatt Autoloading & PSR-4 Autoload your classes PSR-4

    autoloading Drop as many includes/requires as possible STEP 3 MODERNIZE YOUR FOUNDATION
  17. Sharing Laravel @stauffermatt Bug-driven testing For every bug reported… Write

    a (failing) test. Then fix the bug by writing code that makes the test pass. Repeat. STEP 3 MODERNIZE YOUR FOUNDATION
  18. Sharing Laravel @stauffermatt COMPOSE ALL THE THINGS What is

    Dependency manager for PHP (Like Ruby Gems, Node Package Manager, etc.) STEP 4
  19. Sharing Laravel @stauffermatt LARAVEL-AS-A-COMPONENT Just a little bit of Laravel

    Laravel-as-a-frontend: Build a modern Laravel app that consumes data from your existing site Laravel-as-a-backend: Build a modern Laravel app that replaces an existing data store STEP 5
  20. Sharing Laravel @stauffermatt CraftCMS CraftCMS is based on Yii REST

    API (via Plugin) Eloquent database models for reading LARAVEL-AS-A-COMPONENT STEP 5
  21. Sharing Laravel @stauffermatt “The Right Toolbox” Anthony Colangelo at HappyCog

    Eloquent objects to describe your Craft database structure https://speakerdeck.com/ acolangelo/the-right-toolbox LARAVEL-AS-A-COMPONENT STEP 5
  22. Sharing Laravel @stauffermatt DEEP/OpenAPI/Export It Same thing, but for ExpressionEngine

    https://github.com/rsanchez/Deep https://github.com/putyourlightson/open-api https://www.mithra62.com/ (Export It) LARAVEL-AS-A-COMPONENT STEP 5
  23. Sharing Laravel @stauffermatt ILLUMINATE YOUR PROJECT Illuminate packages Mail Auth

    Queue Config Cache Session Workbench Validation Translation Support Routing Remote Pagination Log Http Html Hashing Events Database Cookie STEP 6 Commonly exported: • Database (Query Builder, Migrations & Seeding, Eloquent) • Queues • Container (IOC/DI container) • Cache • Session • Mail • Support (Collections, array helpers, etc.) • Many more…
  24. Sharing Laravel @stauffermatt /* Composer.json: { "require": { "illuminate/support": "~5.1.0"

    } } */ require_once('vendor/autoload.php'); // Collection $people = new Illuminate\Support\Collection([ 'Declan', 'Abner', 'Mitzi' ]); $people->each(function ($person) { echo "Well, howdy $person!<br>"; }); Simple Use Example
  25. Sharing Laravel @stauffermatt include_once('vendor/autoload.php'); $app = new Illuminate\Container\Container; $app->bind('app', $app);

    $app->bind('ClassName', function () { return new ClassName; }); $app->singleton('ClassName', function () { return new ClassName; }); $app->share(function () { return new ClassName }); $app->extend('ClassName', function ($app, $class) { $class->setThing('a', 'b'); }); $app->instance('ClassName', $instanceOfClassName); $app->alias('OriginalBinding', 'alias'); $app->make('ClassName'); $app->when('OneThing')->needs('StuffInterface')->give('WhiteStuff'); $app->when('OtherThing')->needs('StuffInterface')->give('BlackStuff'); Using the Container
  26. Sharing Laravel @stauffermatt $app = new Illuminate\Container\Container; $app->bind('app', $app); //

    Asks for an instance of ClassName; "auto-wiring" // means it resolves this class, even if it's never // been explicitly bound $object = $app->make('ClassName'); Container: make
  27. Sharing Laravel @stauffermatt $app = new Illuminate\Container\Container; $app->bind('app', $app); //

    Bind the ClassName string to run the Closure provided; // every time it's injected, will run Closure again $app->bind('ClassName', function () { return new ClassName; }); $object1 = $app->make('ClassName'); $object2 = $app->make('ClassName'); $object1 !== $object2 Container: bind
  28. Sharing Laravel @stauffermatt $app = new Illuminate\Container\Container; $app->bind('app', $app); //

    Bind the ClassName string to run the Closure provided; // caches the result and returns it every time in the future $app->singleton('ClassName', function () { return new ClassName; }); $object1 = $app->make('ClassName'); $object2 = $app->make('ClassName'); $object1 === $object2 Container: singleton
  29. Sharing Laravel @stauffermatt $app = new Illuminate\Container\Container; $app->bind('app', $app); $app['ClassName']

    = $app->share(function () { return new ClassName; }); $app->bindShared('ClassName', function () { return new ClassName; }); $app->singleton('ClassName', function () { return new ClassName; }); $classNameInstance = new ClassName; $app->instance('ClassName', $classNameInstance); Container: Singleton alternatives
  30. Sharing Laravel @stauffermatt $app = new Illuminate\Container\Container; $app->bind('app', $app); //

    Teaches the Container that, every time it resolves // this class (after this binding), it should also // perform the following behavior $app->extend('ClassName', function ($app, $class) { $class->setThing('a', 'b'); }); Container: extend
  31. Sharing Laravel @stauffermatt $object = $app->make('ClassName'); // Magic: class ClassName

    { public function __construct(OtherClass $other, ThirdClass $third) { $this->other = $other; $this->third = $third; } } // But what about... class OtherClass { public function __construct(ThingInterface $thing) { $this->thing = $thing; } } Container: Automatic dependency injection
  32. Sharing Laravel @stauffermatt $app = new Illuminate\Container\Container; $app->bind('app', $app); //

    Adds an alias that returns the same output // as an existing Binding $app->alias('OriginalBinding', 'alias'); $app->alias( 'MyConcreteClass', 'MyInterface', ); // Real-life examples: $app->alias( 'files', 'Illuminate\Filesystem\Filesystem', ); Container: alias
  33. Sharing Laravel @stauffermatt $app = new Illuminate\Container\Container; $app->bind('app', $app); //

    Conditionally binds injected dependencies depending on the // requesting class/interface $app->when('OneThing') ->needs('StuffInterface') ->give('ThisStuff'); $app->when('OtherThing') ->needs('StuffInterface') ->give('ThatStuff'); Container: when/ needs/give
  34. Sharing Laravel @stauffermatt $messageBag = new Illuminate\Support\MessageBag; // Do stuff

    $messageBag->add('error', 'Something happened!'); // Do other stuff if ($messageBag->has('error')): ?> <h2>Errors</h2> <ul> <?php foreach ($messageBag->get('error', 'ERROR! :message') as $message): ?> <li><?= $message; ?></li> <?php endforeach; ?> </ul> <?php endif; ?> Support: Message
  35. Sharing Laravel @stauffermatt // Collections $names = new Collection(['Declan', 'Abner',

    'Mitzi']); $names->filter(function ($name) { // Filter out any names which start with "D" return substr($name, 0, 1) !== 'D'; })->map(function ($name) { // Decorate each name return "<i>{$name}</i>"; })->each(function ($name) { // Output each name echo "Collection name: $name\n"; }); // More at http://laravel.com/docs/5.1/collections Support: Collections
  36. Sharing Laravel @stauffermatt $personRecord = [ 'first_name' => 'Mohammad', 'last_name'

    => 'Gufran' ]; $record = new Fluent($personRecord); $record->address('hometown, street, house'); echo $record->first_name . "\n"; echo $record->address . "\n"; // Fluent is like the associative array sibling of Collections // class Fluent implements ArrayAccess, Arrayable, Jsonable, JsonSerializable echo json_encode($record); echo $record->toArray(); Support: Fluent
  37. Sharing Laravel @stauffermatt // Pluralizer use Illuminate\Support\Pluralizer; $item = 'goose';

    echo "One {$item}, two " . Pluralizer::plural($item); $item = 'moose'; echo "One {$item}, two " . Pluralizer::plural($item); // Str use Illuminate\Support\Str; if (Str::contains('My fourteenth visit', 'first')) { echo 'Howdy!'; } else { echo 'Nice to see you again.'; } Support: Str & Pluralizer
  38. Sharing Laravel @stauffermatt $capsule = new Illuminate\Database\Capsule\Manager; $capsule->addConnection([ 'driver' =>

    'mysql', 'host' => 'localhost', 'database' => 'database', 'username' => 'root', 'password' => 'supersecret!', 'charset' => 'utf8', 'collation' => 'utf8_unicode_ci', 'prefix' => '' ]); $capsule->bootEloquent(); // Eloquent model definition class User extends Illuminate\Database\Eloquent\Model {} // Eloquent model usage $user = User::find(1); Database & Eloquent
  39. Sharing Laravel @stauffermatt $queue = new Queue($container); // ... add

    Config, Request, and Encryption dependencies... // Uses 'sync' driver if no connection set $queue->addConnection([ 'driver' => 'iron', 'project' => 'your-project-id', 'token' => 'your-token', 'queue' => 'illuminate-test', 'encrypt' => true, ]); Queue::push('doThing', ['string' => 'sync-' . date('r')]); class doThing { public function fire($job, $data) { // Do stuff... $job->delete(); } } Queue
  40. Sharing Laravel @stauffermatt // Prep $queue like in previous example...

    $worker = new Illuminate\Queue\Worker($queue->getQueueManager()); // Params list for 'pop': // * Name of the connection to use // * Name of the queue to use // * Number of seconds to delay a job if it fails // * Maximum amount of memory to use // * Time (in seconds) to sleep when no job is returned // * Maximum number of times to retry the given job // before discarding it while (true) { try { $worker->pop('default', 'worker-test', 3, 64, 30, 3); } catch (Exception $e) { // Handle job exception } } Queue: Worker
  41. Sharing Laravel @stauffermatt // Prepare dependencies $container = new Container;

    $container['files'] = new Filesystem; $container['config'] = [ 'cache.default' => 'file', 'cache.stores.file' => [ 'driver' => 'file', 'path' => __DIR__ . '/cache' ] ]; // Create cache manager $cacheManager = new CacheManager($container); // Get specific cache instance (no parameter means "default") $cache = $cacheManager->store(); $cache->put('test', 'This is loaded from cache.', 500); echo $cache->get('test'); Cache
  42. Sharing Laravel @stauffermatt $container = new Container; $container->bind('app', $container); $container['config']

    = new Config; $container['files'] = new Filesystem; $container['config'] = [ 'session' => [ 'lottery' => [2, 100], 'cookie' => 'laravel_session', 'path' => '/', 'domain' => null, 'driver' => 'file', 'files' = __DIR__ . '/sessions' ] ]; $cookieName = $container['session']->getName(); if (isset($_COOKIE[$cookieName])) { if ($sessionId = $_COOKIE[$cookieName]) { $container['session']->setId($sessionId); } } // Boot the session $container['session']->start(); Session: Prepare
  43. Sharing Laravel @stauffermatt $container['session']->put('test', 'abcdef'); // Save the session $container['session']->save();

    // Store the session ID in our cookie $cookie = new Symfony\Component\HttpFoundation\Cookie( $container['session']->getName(), $container['session']->getId(), time() + ($container['config']['session.lifetime'] * 60), '/', null, false ); setcookie( $cookie->getName(), $cookie->getValue(), $cookie->getExpiresTime(), $cookie->getPath(), $cookie->getDomain() ); Session: Set
  44. Sharing Laravel @stauffermatt if ($container['session']->has('test')) { echo 'Set<br> Value: '

    . $container['session']->get('test'); } else { echo 'Not set'; } Session: Get
  45. Sharing Laravel @stauffermatt // Above: prepare writer, logger // Choose

    a transport (SMTP, PHP Mail, Sendmail, Mailgun, Mandrill, Log) $transport = Swift_SmtpTransport::newInstance(getenv('SMTP_HOST'), getenv('SMTP_PORT')); // $transport = Swift_MailTransport::newInstance(); // $transport = Swift_SendmailTransport::newInstance('/usr/sbin/sendmail -bs'); // $transport = new MailgunTransport(getenv('MAILGUN_SECRET'), getenv('MAILGUN_DOMAIN')); // $transport = new MandrillTransport(getenv('MANDRILL_SECRET')); // $transport = new SesTransport(new SesClient($sesConfig)); // $transport = new LogTransport($logger->getMonolog()); // SMTP specific configuration $transport->setUsername(getenv('SMTP_USERNAME')); $transport->setPassword(getenv('SMTP_PASSWORD')); $transport->setEncryption(true); $swift = new Swift_Mailer($transport); $finder = new FileViewFinder(new Filesystem, ['views']); $resolver = new EngineResolver; // Determine which template engine to use $resolver->register('php', function () { return new PhpEngine; }); $view = new Factory($resolver, $finder, new Dispatcher); $mailer = new Mailer($view, $swift); // Optionally set logger, queue manager, and container Mail: prepare
  46. Sharing Laravel @stauffermatt $data = [ 'greeting' => 'Nice work.',

    ]; $mailer->send('email.welcome', $data, function ($message) { $message->from(getenv('MAIL_FROM_ADDRESS'), 'Code Guy'); $message->to(getenv('MAIL_TO_ADDRESS'), 'Barack Obama'); $message->bcc('[email protected]'); $message->attach('secret_documents.zip'); $message->subject('Yo!'); }); Mail: send
  47. Sharing Laravel @stauffermatt $app = new Illuminate\Container\Container; $app->bind('app', $app); $providers

    = [ 'Illuminate\Queues\QueueServiceProvider', 'Illuminate\Events\EventServiceProvider' ]; foreach ($providers as $provider) { with (new $provider($app))->register(); } $aliases = [ 'Queue' => 'Illuminate\Support\Facades\Queue', 'Config' => 'Illuminate\Support\Facades\Config', ]; foreach ($aliases as $alias => $class) { class_alias($class, $alias); } Illuminate\Support\Facades\Facade::setFacadeApplication($app); foreach ($providers as $provider) { with (new $provider($app))->boot(); } Booting: Service Providers, Aliases, and Façades
  48. Sharing Laravel @stauffermatt ILLUMINATE YOUR CODEBASE Illuminate Contracts Interfaces that

    define how all of the Illuminate components interface—and allow for easy third-party replacements STEP 6
  49. Sharing Laravel @stauffermatt <?php namespace Illuminate\Contracts\Cache; interface Store { public

    function get($key); public function put($key, $value, $minutes); public function increment($key, $value = 1); public function decrement($key, $value = 1); public function forever($key, $value); public function forget($key); public function flush(); public function getPrefix(); } Example Contract: Cache
  50. Sharing Laravel @stauffermatt <?php namespace Illuminate\Cache; ... use Illuminate\Contracts\Cache\Store; ...

    class CacheManager implements FactoryContract { ... public function repository(Store $store) { $repository = new Repository($store); if ($this->app->bound('Illuminate\Contracts\Events\Dispatcher')) { $repository->setEventDispatcher( $this->app['Illuminate\Contracts\Events\Dispatcher'] ); } return $repository; Example Contract Usage: Cache Manager
  51. Sharing Laravel @stauffermatt Know why you’re here What is it

    about Laravel that brought you to this talk?
  52. Sharing Laravel @stauffermatt Bring that, incrementally, to every app •

    OOP & Design/Architecture Patterns • Modernize codebase • Composer & Community Packages • Undercover Laravel • Laravel as a Component • Illuminate Components
  53. Sharing Laravel @stauffermatt Additional resources for legacy codebases Modernizing Legacy

    Applications in PHP 
 Paul Jones Working Effectively with Legacy Code
 Michael Feathers Refactoring 
 Martin Fowler, Kent Beck, et al. Mastering Object Oriented PHP
 Brandon Savage