Share
Embed
Sharing Laravel - ZendCon 2015 Edition
Matt Stauffer
October 21, 2015
Transcript
77 slides
-
Sharing Laravel @stauffermatt Most Laravel tutorials assume a blank slate.
(or, that you’re developing in Laravel at all) -
Sharing Laravel @stauffermatt The goal: Learn how to bring the
best assets of the Laravel world to non-Laravel projects. -
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 -
Sharing Laravel @stauffermatt My side (or main) project is in
CodeIgniter/ZF1/etc… what do I do? Frequent question -
Sharing Laravel @stauffermatt WHY DON’T WE JUST REWRITE THE WHOLE
APP IN LARAVEL? TERRIBLE IDEA -
Sharing Laravel @stauffermatt response to legacy code: trash it. Sharing
Laravel @stauffermatt -
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. -
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. -
Sharing Laravel @stauffermatt The 6 steps of bringing the “best
of Laravel” to your non-Laravel projects -
Sharing Laravel @stauffermatt WRITE GOOD CODE OOP best practices SOLID
Law of Demeter Loose coupling Not just wrapping procedural code in classes STEP 1 -
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 -
Sharing Laravel @stauffermatt WRITE GOOD CODE Design patterns Examples: •
Adapter Pattern • Singleton Pattern • Observer Pattern • Many more… STEP 1 -
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.” -
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 -
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 -
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 -
Sharing Laravel @stauffermatt Stay Classy Procedural code/functions -> methods on
classes -> retroactive designed OOP Namespaces & autoloading STEP 3 MODERNIZE YOUR FOUNDATION -
Sharing Laravel @stauffermatt Autoloading & PSR-4 Autoload your classes PSR-4
autoloading Drop as many includes/requires as possible STEP 3 MODERNIZE YOUR FOUNDATION -
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 -
Sharing Laravel @stauffermatt COMPOSE ALL THE THINGS What is Composer?
Dependency manager for PHP (Like Ruby Gems, Node Package Manager, etc.) STEP 4 -
Sharing Laravel @stauffermatt Community packages Packagist Symfony PHP League DIY
COMPOSE ALL THE THINGS STEP 4 -
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 -
Sharing Laravel @stauffermatt CraftCMS CraftCMS is based on Yii REST
API (via Plugin) Eloquent database models for reading LARAVEL-AS-A-COMPONENT STEP 5 -
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 -
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 -
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… -
Sharing Laravel @stauffermatt TITLE FOR STEP 1 github.com/ mattstauffer/ Torch
ILLUMINATE YOUR PROJECT STEP 6 -
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 -
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 -
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 -
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 -
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 -
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 -
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 -
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 -
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 -
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 -
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 Bag -
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 -
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 -
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 -
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 -
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 -
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 -
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 -
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 -
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 -
Sharing Laravel @stauffermatt if ($container['session']->has('test')) { echo 'Set<br> Value: '
. $container['session']->get('test'); } else { echo 'Not set'; } Session: Get -
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 -
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 -
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 -
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 -
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 -
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 -
Sharing Laravel @stauffermatt Know why you’re here What is it
about Laravel that brought you to this talk? -
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 -
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