Upgrade to PRO for Only $50/Year—Limited-Time Offer! 🔥

ZCEU - Console applications made easy

Daniel Gomes
November 20, 2013

ZCEU - Console applications made easy

In some cases you feel the need to have some specific command-line commands for deployment, testing some code or do any other specific task. The Symfony2 Console component is a tool that gives you a simple and easy way to build a console application with your own command-line commands. This talk is an introduction to the Console component and a walkthrough on how to use it in an effective way.

Source code of the slides: https://github.com/danielcsgomes/ZendCon-console-applications-made-easy

Daniel Gomes

November 20, 2013
Tweet

More Decks by Daniel Gomes

Other Decks in Programming

Transcript

  1. Who am I • Software Engineer @ GuestCentric Systems •

    Co-founder & organizer @ phplx • Father of a beautiful boy • ZCE PHP 5.3, CSM, OCP MySQL 5 Developer
  2. <?php ! // /path/to/app.php require_once __DIR__ . '/vendor/autoload.php'; ! use

    Symfony\Component\Console\Application; ! $app = new Application('My Console App', '0.0.1'); $app->run();
  3. What I needed •Run background jobs •Interactive setup tools •Cache

    clean up / warming •Basically to do a specific task
  4. What I got • Several script files • Written in

    Bash, PHP, Python, etc • No documentation • Not very friendly for other users
  5. <?php ! // /path/to/app.php require_once __DIR__ . '/vendor/autoload.php'; ! use

    Symfony\Component\Console\Application; ! $app = new Application('My Console App', '0.0.1'); $app->run();
  6. class MyCommand extends Command { protected function configure(){…} ! protected

    function execute($input, $output){…} ! protected function interact($input, $output){…} }
  7. namespace DCSG\Command; ! use Symfony\Component\Console\Command\Command; ! class HelloWorldCommand extends Command

    { protected function configure() { $this->setName('hello:world') ->setDescription('Hello World <name>'); } }
  8. protected function configure() { // ... $this->setHelp(<<<EOF This is a

    simple command that outputs “<info>Hello world</info> 'Your Name’.” to the console. EOF ); }
  9. class HelloWorldCommand extends Command { // ... ! protected function

    execute( InputInterface $input, OutputInterface $output) { // Business logic goes here… $name = $input->getArgument(‘name'); ! if ($input->getOption('uppercase')) { $name = strtoupper($name); } ! $output->writeln("Hello World <info>$name</info>."); } }
  10. protected function execute( InputInterface $input, OutputInterface $output) { $name =

    $input->getArgument(‘name'); ! if (preg_match("/[0-9]+/", $name)) { throw new \InvalidArgumentException('Invalid name.'); } // … }
  11. // Symfony/Component/Console/Helper/DialogHelper.php ! public function select(...){...} ! public function ask(...){...}

    ! public function askConfirmation(...){...} ! public function askHiddenResponse(...){...} ! public function askAndValidate(...){...} ! public function askHiddenResponseAndValidate (...){...}
  12. protected function execute($input, $output) { $colors = array('Red', 'Yellow', 'Green',

    'Blue', 'Black'); $dialog = $this->getHelperSet()->get('dialog'); $index = $dialog->select( $output, 'Please select your favorite color:', $colors ); $output->writeln("Your favorite color is {$colors[$index]}"); }
  13. $name = $this->getHelper('dialog')->askAndValidate( $output, 'Insert your name: ', function ($name)

    { if (empty($name)) { throw new \InvalidArgumentException( 'The name cannot be empty.’ ); } ! return $name; } ); $output->writeln("Your name is <info>{$name}</info>");
  14. ! $fmt = $this->getHelperSet()->get('formatter'); ! $formattedLine = $fmt->formatSection( ‘My Section',

    'Here is some message related to that section' ); $output->writeln($formattedLine); ! $msg = array('Something went wrong'); $fmtBlock = $fmt->formatBlock($msg, 'error'); $output->writeln($fmtBlock); ! $msg = array('Custom Colors'); $fmtBlock = $fmt->formatBlock($msg, ‘bg=blue;fg=white'); $output->writeln($fmtBlock);
  15. $progress = $this->getHelperSet()->get('progress'); ! $progress->start($output, 50); $i = 0; while

    ($i++ < 50) { sleep(1); $progress->advance(); } ! $progress->finish();
  16. $table = $this->getHelperSet()->get('table'); $table ->setHeaders(array('Color', 'HEX')) ->setRows( array( array('Red', '#ff0000'),

    array('Blue', '#0000ff'), array('Green', '#008000'), array('Yellow', '#ffff00') ) ); $table->render($output);
  17. protected function interact($input, $output) { $isEmpty = function($value) { if

    (empty($value)) { throw new \InvalidArgumentException('Value cannot be empty.'); } return $value; }; $dialog = $this->getHelper('dialog'); $this->host = $dialog->askAndValidate($output, 'host: ', $isEmpty); $this->user = $dialog->askAndValidate($output, 'username: ', $isEmpty); $this->password = $dialog->askHiddenResponseAndValidate( $output, 'password: ', $isEmpty ); $this->dbnames = $dialog->askAndValidate( $output, 'databases separate by space: ', $isEmpty ); }
  18. protected function execute($input, $output) { $mysqldump = $this->composeExecCommand( $this->getOptions($input) );

    $exitCode = 0; $execOutput = array(); exec($mysqldump, $execOutput, $exitCode); if (0 === $exitCode) { $message = "<info>Databases dumped with success.</info>"; } else { $message = "<error>Error with exit code: {$exitCode}"; } $output->writeln($message); }
  19. Events •command - before run •exception - on exceptions •terminate

    - before return exit code •extend to add custom event note: requires Symfony EventDispatcher Component
  20. namespace Symfony\Component\Console\Tester; ! class CommandTester { private $command; private $input;

    private $output; ! public function __construct(Command $command){…} ! public function execute(array $input, array $options=array()){…} ! public function getDisplay($normalize = false){…} ! public function getInput(){…} ! public function getOutput(){…} }
  21. public function testOutputNameInUppercase() { $command = new HelloWorldCommand(); $commandTester =

    new CommandTester($command); $commandTester->execute( array( 'command' => $command->getName(), 'name' => 'Daniel', '--uppercase' => true, ) ); ! $this->assertRegExp( '/DANIEL/', $commandTester->getDisplay() ); }
  22. namespace Symfony\Component\DependencyInjection; ! interface ContainerAwareInterface { /** * Sets the

    Container. * * @param ContainerInterface|null $container */ public function setContainer(ContainerInterface $container = null); }
  23. use Symfony\Component\Console\Command\Command; use Symfony\Component\DependencyInjection\ContainerAwareInterface; use Symfony\Component\DependencyInjection\ContainerInterface; ! class HelloWorldCommand extends

    Command implements ContainerAwareInterface { private $container; ! public function setContainer(ContainerInterface $container = null) { $this->container = $container; } ! protected function execute($input, $output) { $this->container->get(‘my_service'); // ... } ! // ... }
  24. protected function execute($input, $output) { $logger = $this->container->get('logger'); $mysqldump =

    $this->composeExecCommand($this->getOptions($input)); $exitCode = 0; $execOutput = array(); exec($mysqldump, $execOutput, $exitCode); if (0 === $exitCode) { $message = "<info>Success</info>"; $logger->addInfo('Databases dumped with success.’); } else { $message = "<error>Error with exit code: {$exitCode}"; $logger->addCritical('Error dumping databases.'); touch($filename); } return $filename; }
  25. Catching Signals • SIGSTOP & SIGKILL cannot be catch •

    Only SIGINT can be triggered by shortcut (ctrl+c) • Find the best Tick value that fits your needs • Define inside your Commands • Create a base command • Read “Signaling PHP” book by Cal Evans http://www.signalingphp.com
  26. protected function execute($input, $output) { declare(ticks = 10); pcntl_signal(SIGINT, [$this,

    'signalHandler']); ! do { // do something interesting here. $this->write('.'); } while ($this->continueFlag); } ! public function signalHandler($signal) { ! echo "Caught a signal" . $signal . PHP_EOL; $this->continueFlag = false; }
  27. Resources http://goo.gl/h0Xcfe http://symfony.com/doc/current/components/process.html Long running processes http://symfony.com/doc/current/components/console/index.html http://symfony.com/doc/current/cookbook/console/index.html http://symfony.com/doc/current/cookbook/service_container/index.html http://symfony.com/doc/current/components/dependency_injection/index.html

    Symfony2 Docs Cron jobs http://www.cyberciti.biz/faq/how-do-i-add-jobs-to-cron-under-linux-or-unix-oses/ Source Code https://github.com/danielcsgomes/ZendCon-console-applications-made-easy https://github.com/symfony/Console Catching Signals http://signalingphp.com https://github.com/Cilex/Cilex Cilex
  28. @danielcsgomes | [email protected] | http://danielcsgomes.com Photo by Jian Awe ©

    http://www.flickr.com/photos/qqjawe/6511141237 Please give feedback: https://joind.in/9269 Questions?