<!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <title>Drupal VM, Meet Symfony Console</title> <meta name="description" content="Drupal VM, Meet Symfony Console"> <meta name="author" content="Oliver Davies"> <meta name="apple-mobile-web-app-capable" content="yes"> <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent"> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"> <link rel="stylesheet" href="css/reveal.css"> <link rel="stylesheet" href="css/theme/simple.css" id="theme"> <link rel="stylesheet" href="lib/css/tomorrow-night-bright.css"> <link rel="stylesheet" href="assets/css/custom.css"> <!-- Printing and PDF exports --> <script> var link = document.createElement( 'link' ); link.rel = 'stylesheet'; link.type = 'text/css'; link.href = window.location.search.match( /print-pdf/gi ) ? 'css/print/pdf.css' : 'css/print/paper.css'; document.getElementsByTagName( 'head' )[0].appendChild( link ); </script> <!--[if lt IE 9]> <script src="lib/js/html5shiv.js"></script> <![endif]--> </head> <body> <div class="reveal"> <div class="slides"> <section> <h1 class="big">Drupal VM, Meet Symfony Console</h1> <p> <img class="no-border" src="assets/images/drupalcamp-bristol.png" > </p> </section> <section> <h2>Oliver Davies (opdavies)</h2> <div class="col--6-5"> <ul class="bullets medium"> <li>Senior Drupal Developer for Appnovation</li> <li>Symfony hobbyist</li> <li>Drupal VM user, Drupal VM Generator maintainer</li> <li>Drupal Bristol organiser, PHPSW co-organiser, DrupalCamp committee member</li> </ul> </div> <div class="col--6-1"> <img src="assets/images/me_thumb.jpg" class="no-border"> <img src="assets/images/appno.jpg" class="no-border"> <!-- <img src="assets/images/drupal-bristol.jpg" class="no-border"> --> </div> </section> <section> <h2>Prerequisites</h2> <ul class="bullets"> <li>Object-orientated PHP</li> <li>Composer</li> <li>Autoloading, PSR-4</li> </ul> </section> <section> <h2>About Drupal VM</h2> <ul class="bullets"> <li>Virtual machine for Drupal development</li> <li>Developed and maintained by Jeff Geerling</li> <li>Vagrant, Ansible</li> <li>Configured via YAML files</li> <li>Customisable</li> </ul> </section> <section> <h2>Using Drupal VM (< 3.0)</h2> <ul class="bullets"> <li>Download the project</li> <li>Copy example.config.yml to config.yml</li> <li>Edit values</li> <li>Start the VM</li> </ul> </section> <section> <h2>Using Drupal VM (>= 3.0)</h2> <ul class="bullets"> <li>Download the project</li> <li>default.config.yml contains default values</li> <li>Make config.yml if needed and override values</li> <li>Start the VM</li> </ul> </section> <section> <h2>About the Drupal VM Generator</h2> <ul class="bullets"> <li>Symfony application</li> <li>Twig</li> <li>Generates minimal, use-case specific configuration files</li> <li>http://bit.ly/announcing-drupal-vm-generator</li> </ul> </section> <section> <h2>Using Drupal VM (>= 3.0)</h2> <ul class="bullets"> <li>Download the project</li> <li>default.config.yml contains default values</li> <li><strong>Make config.yml if needed and override values</strong></li> <li>Start the VM</li> </ul> <aside class="notes"> <ul class="bullets"> <li>Time consuming - 297 lines</li> <li>Too much cruft</li> </ul> </aside> </section> <section> <h2>Drupal VM Generator Example 1</h2> <img src="assets/images/drupal-vm-generator-example.gif" alt=""> </section> <section data-background="#000" data-background-transition="none"> <h2 class="white">Drupal VM Generator Example 2</h2> <pre><code class="php text-medium"> drupalvm config:generate \ --machine-name="drupalbristol" \ --hostname="drupalbristol.l" \ --ip-address="192.168.88.88" \ --cpus="1" --memory="512" \ --webserver="nginx" --drupal-version="8" \ --database-name="drupal" --database-user="drupal" \ --database-password="drupal" --build-makefile=false \ ... </code></pre> </section> <section> <h2>CLI Examples</h2> <ul class="bullets"> <li>Drush</li> <li>Symfony/Drupal Console</li> <li>Terminus (Pantheon)/Platform.sh</li> <li>Artisan (Laravel)</li> <li>Composer</li> <li>Sculpin</li> </ul> </section> <section> <h2>CLI Examples</h2> <ul class="bullets"> <li><s>Drush</s></li> <li>Symfony/Drupal Console</li> <li>Terminus (Pantheon)/Platform.sh</li> <li>Artisan (Laravel)</li> <li>Composer</li> <li>Sculpin</li> </ul> <aside class="notes"> <ul class="bullets"> <li>All of these except Drush are based on Symfony Console</li> </ul> </aside> </section> <section data-background="#0076C2" data-background-transition="none"> <h1 class="white big">Symfony Console</h1> </section> <section> <h3 class="title">The Console component eases the creation of beautiful and testable command line interfaces.</h3> <p>The Console component allows you to create command-line commands. Your console commands can be used for any recurring task, such as cronjobs, imports, or other batch jobs.</p> <br> <div class="text-small"> <p> <a href="http://symfony.com/doc/current/components/console/introduction.html">http://symfony.com/doc/current/components/console/introduction.html</a> </p> </div> </section> <section data-background="#000" data-background-transition="none"> <h2 class="white">Installation</h2> <pre><code class="bash text-big" data-trim> $ composer require symfony/console $ composer install </code></pre> </section> <section data-background="#000" data-background-transition="none"> <h2 class="white">Installation (cont)</h2> <pre><code class="json text-big" data-trim> # composer.json "require": { "symfony/console": "^3.1" } </code></pre> </section> <section data-background="#000" data-background-transition="none"> <h2 class="white">Installation (cont)</h2> <pre><code class="php text-big"> # app.php require __DIR__ . '/vendor/autoload.php'; // Do stuff. </code></pre> </section> <section data-background="#0076C2" data-background-transition="none"> <h2 class="white">Building a Console Application</h2> </section> <section> <h2>Steps</h2> <ul class="bullets"> <li>Download the Console component</li> <li>Add an "entry point"</li> <li>Configure and run an <code>Application</code> class</li> <li>Add new Commands</li> </ul> </section> <section data-background="#000" data-background-transition="none"> <h2 class="white">bin/drupalcamp</h2> <pre><code class="php text-big"> #!/usr/bin/env php require __DIR__ . '/drupalcamp.php'; </code></pre> <aside class="notes"> <ul class="bullets"> <li>No .php file extension</li> <li>Formatting issues</li> <li>Convenience</li> </ul> </aside> </section> <section data-background="#000" data-background-transition="none"> <h2 class="white">bin/drupalcamp.php</h2> <pre><code class="php text-medium"> require __DIR__ . '/../vendor/autoload.php'; use Symfony\Component\Console\Application; $application = new Application(); $application->run(); </code></pre> </section> <section data-background="#000" data-background-transition="none"> <img src="assets/images/console-application-1.png" alt="" style="max-width: 100%"> </section> <section data-background="#000" data-background-transition="none"> <h2 class="white">bin/drupalcamp.php (cont)</h2> <pre><code class="php text-medium"> require __DIR__ . '/../vendor/autoload.php'; use Symfony\Component\Console\Application; $application = new Application(); $application->run('DrupalCamps', '1.0'); </code></pre> <aside class="notes"> <ul> <li>No .php extension</li> </ul> </aside> </section> <section data-background="#000" data-background-transition="none"> <img src="assets/images/console-application-2.png" alt=""> </section> <section data-background="#0076C2" data-background-transition="none"> <h2 class="white">Adding Commands</h2> </section> <section> <h2>Adding Commands</h2> <ul class="bullets"> <li>Add command classes in <code>src/</code></li> <li>Autoload via Composer</li> <li>Add to <code>Application</code></li> </ul> </section> <section data-background="#000" data-background-transition="none"> <h2 class="white">Autoloading via Composer</h2> <pre><code class="php text-big" data-trim> # composer.json "autoload": { "psr-4": { "": "src/" } } </code></pre> </section> <section data-background="#000" data-background-transition="none"> <h2 class="white">Autoloading via Composer</h2> <pre><code class="php text-big" data-trim> # composer.json "autoload": { "psr-4": { "DrupalCamps\\": "src/" } } </code></pre> </section> <section data-background="#000" data-background-transition="none"> <h2 class="white">src/GoCommand.php</h2> <pre><code class="php text-medium" data-trim> use Symfony\Component\Console\Command\Command; class GoCommand extends Command { public function configure() { $this->setName('go') ->setDescription('Go to a DrupalCamp.'); } } </code></pre> </section> <section data-background="#000" data-background-transition="none"> <h2 class="white">src/GoCommand.php (cont)</h2> <pre><code class="php text-normal"> use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; ... public function execute(InputInterface $input, OutputInterface $output) { // Execute the command. } </code></pre> </section> <section data-background="#000" data-background-transition="none"> <h2 class="white">bin/drupalcamp.php</h2> <pre><code class="php text-big"> $application = new Application(); $application->add(new GoCommand()); $application->run(); </code></pre> </section> <section data-background="#000" data-background-transition="none"> <h2 class="white">bin/drupalcamp.php</h2> <pre><code class="php text-big"> $application = new Application(); $application->addCommands( [ new GoCommand(), ] ); $application->run(); </code></pre> </section> <section data-background='#000' data-background-transition="none"> <img src="assets/images/go-command-1.png" alt=""> </section> <section data-background="#0076C2" data-background-transition="none"> <h2 class="white">Arguments and Options</h2> </section> <section data-background="#000" data-background-transition="none"> <h2 class="white">src/GoCommand.php</h2> <pre><code class="php text-normal" data-trim> use Symfony\Component\Console\Input\InputArgument; ... public function configure() { $this->setName('go') ->setDescription('Go to a DrupalCamp') ->addArgument('name', InputArgument::OPTIONAL, 'Which DrupalCamp?') ->addOption('past', null, InputOption::VALUE_NONE); } </code></pre> </section> <section data-background="#000" data-background-transition="none"> <h2 class="white">src/GoCommand.php (cont)</h2> <pre><code class="php text-normal" data-trim> public function execute(InputInterface $input, OutputInterface $output) { $text = $input->getArgument('name'); $past = $input->getOption('past'); ... } </code></pre> </section> <section data-background="#000" data-background-transition="none"> <img src="assets/images/command-arguments.png" alt=""> </section> <section data-background="#0076C2" data-background-transition="none"> <h2 class="white">Input and Output</h2> </section> <section data-background="#000" data-background-transition="none"> <h2 class="white">InputInterface</h2> <ul class="bullets"> <pre><code class="php text-big" data-trim> $input->getArgument('foo'); $input->getOption('bar'); </code></pre> <aside class="notes"> Gets values inputted by the user. </aside> </section> <section data-background="#000" data-background-transition="none"> <h2 class="white">OutputInterface</h2> <pre><code class="php text-big" data-trim> $output->write($text); $output->writeln($text); </code></pre> </section> <section data-background="#000" data-background-transition="none"> <h2 class="white">OutputInterface</h2> <pre><code class="php text-big" data-trim> $output->write("<info>$text</info>"); $output->write("<error>$text</error>"); $output->write("<debug>$text</debug>"); </code></pre> </section> <section> <h2>SymfonyStyle</h2> <div class="col--6-3"> <ul class="bullets"> <li>title</li> <li>section</li> <li>text</li> <li>comment</li> <li>note</li> <li>caution</li> <li>table</li> </ul> </div> <div class="col--6-3"> <ul class="bullets"> <li>ask</li> <li>askHidden</li> <li>confirm</li> <li>choice</li> <li>success</li> <li>error</li> <li>warning</li> </ul> </div> </section> <section data-background="#000" data-background-transition="none"> <h2 class="white">SymfonyStyle</h2> <pre><code class="php text-normal"> public function execute(InputInterface $input, OutputInterface $output) { $io = new SymfonyStyle($input, $output); $io->table( ['Title', 'Speaker'], [ ['Drupal VM, Meet Symfony Console', 'Oliver Davies'], ['Drupal 8 and the Symfony EventDispatcher', 'Eric Smith'], ['Building with APIs', 'Nigel Dunn'], ] ); } </code></pre> </section> <section data-background="#000" data-background-transition="none"> <img src="assets/images/talks-command.png" alt=""> </section> <section data-background="#0076C2" data-background-transition="none"> <h2 class="white">Interaction</h2> </section> <section data-background="#000" data-background-transition="none"> <h2 class="white">src/GoCommand.php</h2> <pre><code class="php text-normal"> public function interact(InputInterface $input, OutputInterface $output) { $io = new SymfonyStyle($input, $output); if (!$input->getArgument('name')) { $input->setArgument('name', $io->ask('Which DrupalCamp?')); } } </code></pre> </section> <section data-background="#000" data-background-transition="none"> <h2 class="white">src/GoCommand.php</h2> <pre><code class="php text-normal"> public function execute(InputInterface $input, OutputInterface $output) { $io = new SymfonyStyle($input, $output); $io->success($input->getArgument('name')); } </code></pre> </section> <section data-background="#0076C2" data-background-transition="none"> <h2 class="white">Dependency Injection</h2> </section> <section> <h2>Dependency Injection</h2> <ul class="bullets"> <li>Instantiate dependencies in single place or DI container</li> <li>Add as arguments when adding commands</li> <li>Add as arguments to the constructor and assign to properties</li> </ul> <h3 class="note"> Make sure to call the __construct() method within the parent class. </h3> </section> <section data-background="#000" data-background-transition="none"> <h2 class="white">src/Console/Application.php</h2> <pre><code class="php text-medium" data-trim> use GuzzleHttp\Client; ... $client = new Client(); $application->add(new NewCommand($client)); </code></pre> <aside class="notes"> <ul class="bullets"> <li>Example from the Drupal VM Generator</li> <li>Downloads an archive of Drupal VM from GitHub</li> <li></li> </ul> </aside> </section> <section data-background="#000" data-background-transition="none"> <h2 class="white">src/Command/NewCommand.php</h2> <pre><code class="php text-normal" data-trim> use GuzzleHttp\ClientInterface; use Symfony\Component\Console\Command; final class NewCommand extends Command { private $client; public function __construct(ClientInterface $client) { parent::__construct(); $this->client = $client; } } </code></pre> </section> <section data-background="#0076C2" data-background-transition="none"> <h2 class="white">Distributing Your Application</h2> </section> <section> <h2>.phar files</h2> <ul class="bullets"> <li>PHP archive file</li> <li>Packages your application into one file</li> <li><code>box build</code></li> </ul> </section> <section data-background='#000' data-background-transition="none"> <h2 class="white">Generating phar files</h2> <pre><code class="json text-small"> # composer.json "scripts": { "build": [ "box build", "shasum drupalvm.phar | awk '{print $1}' > drupalvm.phar.version" ] } </code></pre> <pre><code class="bash text-small"> $ composer run-script build </code></pre> </section> <section> <h2>Roadmap</h2> <ul class="bullets medium"> <li>Keep up to date with Drupal VM stable releases</li> <li>New commands - updating existing files, adding vhosts</li> <li>Improve user defaults and settings</li> </ul> </section> <section> <h2>Resources</h2> <ul class="bullets medium"> <li>https://github.com/opdavies/drupal-vm-generator</li> <li>https://www.drupalvmgenerator.com</li> <!-- <li>http://docs.drupalvmgenerator.com</li> --> <li>http://bit.ly/announcing-drupal-vm-generator</li> <li>http://symfony.com/doc/current/components</li> <li>http://symfony.com/doc/current/cookbook/console</li> </ul> </section> <section data-background="#0076C2" data-background-transition="none"> <h2 class="white">Questions?</h2> </section> <section> <h2>Feedback</h2> <ul class="bullets"> <li>@opdavies</li> <li>https://www.oliverdavies.uk</li> </ul> </section> </div> </div> <script src="lib/js/head.min.js"></script> <script src="js/reveal.js"></script> <script> // More info https://github.com/hakimel/reveal.js#configuration Reveal.initialize({ controls: false, progress: true, history: true, center: true, transition: 'none', // none/fade/slide/convex/concave/zoom // More info https://github.com/hakimel/reveal.js#dependencies dependencies: [ { src: 'lib/js/classList.js', condition: function() { return !document.body.classList; } }, { src: 'plugin/markdown/marked.js', condition: function() { return !!document.querySelector( '[data-markdown]' ); } }, { src: 'plugin/markdown/markdown.js', condition: function() { return !!document.querySelector( '[data-markdown]' ); } }, { src: 'plugin/highlight/highlight.js', async: true, callback: function() { hljs.initHighlightingOnLoad(); } }, { src: 'plugin/zoom-js/zoom.js', async: true }, { src: 'plugin/notes/notes.js', async: true } ] }); </script> </body> </html>