Symfony2 - extending the console component

10,109 views
9,988 views

Published on

The goal of this session is to explain how to take benefit from the Symfony2 command line interface tool. First, I have a closer look at the most interesting commands to generate code and help you reduce your development time. Then, I will show you how to create your own commands to extend the Symfony CLI tool and automate your tedious and redundant tasks. This part of the talk will also explain how to create interactive tasks, interact with the database, generating links or send emails from the command line. Of course, there will be a focus on how to design your commands the best way to make them as much testable as possible.

3 Comments
22 Likes
Statistics
Notes
No Downloads
Views
Total views
10,109
On SlideShare
0
From Embeds
0
Number of Embeds
1,395
Actions
Shares
0
Downloads
102
Comments
3
Likes
22
Embeds 0
No embeds

No notes for slide

Symfony2 - extending the console component

  1. 1. Extending and Leveragingthe Power of the CLI.
  2. 2. Who’s talking? Hugo Hamon
  3. 3. Follow me on Twitter… @hhamon
  4. 4. Introduction to the ConsoleComponent
  5. 5. Redondant andtedious tasks.CRON jobs andbatch processing.
  6. 6. Code generation.Interactive setup tools.Cache clearing / warming.…
  7. 7. Improve yourproductivity and effiency.
  8. 8. Be proud tobe lazy J
  9. 9. Creating new commandline tools in bundles
  10. 10. The Command folder
  11. 11. src/Sensio/Bundle/HangmanBundle/Command/GameHangmanCommand.php
  12. 12. Bootstrapping a new commandnamespace SensioBundleHangmanBundleCommand;use SymfonyComponentConsoleCommandCommand;class GameHangmanCommand extends Command{ protected function configure() { $this ->setName(game:hangman) ->setDescription(Play the famous hangman game from the CLI) ; }}
  13. 13. Adding usage manual
  14. 14. protected function configure(){ $this->setHelp(<<<EOFThe <info>game:hangman</info> command starts a new game of thefamous hangman game:<info>game:hangman 8</info>Try to guess the hidden <comment>word</comment> whose length is<comment>8</comment> before you reach the maximum number of<comment>attempts</comment>.You can also configure the maximum number of attemptswith the <info>--max-attempts</info> option:<info>game:hangman 8 --max-attempts=5</info>EOF);}
  15. 15. Adding arguments & options$this->setDefinition(array( new InputArgument(length,InputArgument::REQUIRED, The length of the word toguess), new InputOption(max-attempts, null,InputOption::VALUE_OPTIONAL, Max number ofattempts, 10),));
  16. 16. $ php app/console help game:hangman
  17. 17. Executing a commandprotected function execute( InputInterface $input, OutputInterface $output){ // the business logic goes here...}
  18. 18. InputInterface
  19. 19. namespace SymfonyComponentConsoleInput;interface InputInterface{ function getFirstArgument(); function hasParameterOption($values); function getParameterOption($values, $default = false); function bind(InputDefinition $definition); function validate(); function isInteractive(); function getArguments(); function getArgument($name); function getOptions(); function getOption($name);}
  20. 20. OutputInterface
  21. 21. interface OutputInterface{ function write($messages, $newline, $type); function writeln($messages, $type = 0); function setVerbosity($level); function getVerbosity(); function setDecorated($decorated); function isDecorated(); function setFormatter($formatter); function getFormatter();}
  22. 22. protected function execute(InputInterface $input, OutputInterface $output){ $dictionary = array( 7 => array(program, speaker, symfony), 8 => array(business, software, hardware), 9 => array(algorithm, framework, developer) ); // Read the input $length = $input->getArgument(length); $attempts = $input->getOption(max-attempts); // Find a word to guess $words = $dictionary[$length]; $word = $words[array_rand($words)]; // Write the output $output->writeln(sprintf(The word to guess is %s., $word)); $output->writeln(sprintf(Max number of attempts is %u., $attempts));}
  23. 23. Validating the inputarguments and options.
  24. 24. Validating input parameters// Read the input$length = $input->getArgument(length);$attempts = $input->getOption(max-attempts);$lengths = array_keys($dictionary);if (!in_array($length, $lengths)) { throw new InvalidArgumentException(sprintf(The length "%s" must bean integer between %u and %u., $length, min($lengths), max($lengths)));}if ($attempts < 1) { throw new InvalidArgumentException(sprintf(The attempts "%s" mustbe a valid integer greater than or equal than 1., $attempts));}
  25. 25. $ php app/console game:hangman foo$ php app/console game:hangman 8 --max-attempts=bar
  26. 26. Formatting the output.
  27. 27. The formatter helperclass FormatterHelper extends Helper{ public function formatSection($section, $message, $style); public function formatBlock($messages, $style, $large);}
  28. 28. $formatter->formatBlock(A green information, info);$formatter->formatBlock(A yellow comment, comment);$formatter->formatBlock(A red error, error);$formatter->formatBlock(A custom style, bg=blue;fg=white);
  29. 29. // Get the formatter helper$formatter = $this->getHelperSet()->get(formatter);// Write the output$output->writeln(array( , $formatter->formatBlock(Welcome in the Hangman Game,bg=blue;fg=white, true), ,));$output->writeln(array( $formatter->formatSection(Info, sprintf(You have %uattempts to guess the hidden word., $attempts), info, true), ,));
  30. 30. Make the commandinteract with the end user.
  31. 31. Dialog Helper
  32. 32. class DialogHelper extends Helper{ public function ask(...); public function askConfirmation(...); public function askAndValidate(...);}
  33. 33. class Command{ // ... protected function interact( InputInterface $input, OutputInterface $output ) { $dialog = $this->getHelperSet()->get(dialog); $answer = $dialog->ask($output, Do you enjoyyour Symfony Day 2011?); }}
  34. 34. $dialog = $this->getHelperSet()->get(dialog);$won = false;$currentAttempt = 1;do { $letter = $dialog->ask( $output, Type a letter or a word... ); $currentAttempt++;} while (!$won && $currentAttempt <= $attempts);
  35. 35. Asking and validating the answerdo { $answer = $dialog->askAndValidate( $output, Type a letter or a word... , array($this, validateLetter) ); $currentAttempt++;} while ($currentAttempt <= $attempts);
  36. 36. Asking and validating the answerpublic function validateLetter($letter){ $ascii = ord(mb_strtolower($letter)); if ($ascii < 97 || $ascii > 122) { throw new InvalidArgumentException(The expectedletter must be a single character between A and Z.); } return $letter;}
  37. 37. Asking and validating the answer
  38. 38. Refactoring your code isgood for your command.
  39. 39. Think your commands ascontrollers.
  40. 40. Request <-> Response Input <-> Output
  41. 41. The Dictionary classnamespace SensioBundleHangmanBundleGame;class Dictionary implements Countable{ private $words; public function addWord($word); public function count(); public function getRandomWord($length);}
  42. 42. The Game classnamespace SensioBundleHangmanBundleGame;class Game{ public function __construct($word, $maxAttempts); public function getWord(); public function getHiddenWord(); public function getAttempts(); public function tryWord($word); public function tryLetter($letter); public function isOver(); public function isWon();}
  43. 43. Command class refactoringprotected function interact(InputInterface $input, OutputInterface $output){ $length = $input->getArgument(length); $attempts = $input->getOption(max-attempts); $this->dictionary = new Dictionary(); $this->dictionary ->addWord(program) ... ; $word = $dictionary->getRandomWord($length); $this->game = new Game($word, $attempts); $this->writeIntro($output, Welcome in the Hangman Game); $this->writeInfo($output, sprintf(%u attempts to guess the word., $attempts)); $this->writeInfo($output, implode( , $this->game->getHiddenWord()));}
  44. 44. Command class refactoringprotected function interact(InputInterface $input, OutputInterface $output){ // ... $dialog = $this->getHelperSet()->get(dialog); do { if ($letter = $dialog->ask($output, Type a letter... )) { $this->game->tryLetter($letter); $this->writeInfo($output, implode( , $this->game->getHiddenWord())); } if (!$letter && $word = $dialog->ask($output, Try a word... )) { $this->game->tryWord($word); } } while (!$this->game->isOver());}
  45. 45. Unit testing consolecommands
  46. 46. Unit testing isabout testingyour modelclasses.
  47. 47. Unit testing the Game classnamespace SensioBundleHangmanBundleTestsGame;use SensioBundleHangmanBundleGameGame;class GameTest extends PHPUnit_Framework_TestCase{ public function testGameIsWon() { $game = new Game(foo, 10); $game->tryLetter(o); $game->tryLetter(f); $this->assertEquals(array(f, o, o), $game->getHiddenWord()); $this->assertTrue($game->isWon()); }}
  48. 48. Functional testing consolecommands
  49. 49. Run the command andcheck the output.
  50. 50. The SayHello commandnamespace SensioBundleDemoBundleCommand;class HelloWorldCommand extends Command{ // ... protected function execute($input, $output) { $name = $input->getOption(name); $output->writeln(Your name is <info>. $name .</info>); }}
  51. 51. StreamOutput
  52. 52. class SayHelloCommandTest extends CommandTester{ public function testSayHello() { $input = new ArrayInput(array(name => Hugo)); $input->setInteractive(false); $output = new StreamOutput(); $command = new SayHelloCommand(); $command->run($input, $output); $this->assertEquals( Your name is <info>Hugo</info>, $output->getStream() ); }}
  53. 53. CommandTester
  54. 54. namespace SymfonyComponentConsoleTester;class CommandTester{ public function __construct(Command $command); public function execute($input, $options); public function getDisplay(); public function getInput(); public function getOutput();}
  55. 55. class SayHelloCommandTest extends CommandTester{ public function testSayHello() { $tester = new CommandTester(new SayHelloCommand()); $tester->execute(array(name => Hugo), array( interactive => false )); $this->assertEquals( Your name is <info>Hugo</info>, $tester->getDisplay() ); }}
  56. 56. Being the God of thecommand line J
  57. 57. Container
  58. 58. ContainerAwareInterface
  59. 59. namespace SymfonyComponentDependencyInjection;interface ContainerAwareInterface{ /** * Sets the Container. * * @param ContainerInterface $container * * @api */ function setContainer(ContainerInterface $container = null);}
  60. 60. namespace SensioBundleHangmanBundleCommand;//...class GameHangmanCommand extends Command implements ContainerAwareInterface{ // ... private $container; public function setContainer(ContainerInterface $container = null) { $this->container = $container; } protected function execute(InputInterface $input, OutputInterface $output) { $service = $this->container->get(my_service); }}
  61. 61. ContainerAwareCommand
  62. 62. namespace SymfonyBundleFrameworkBundleCommand;use SymfonyComponentConsoleCommandCommand;use SymfonyComponentDependencyInjectionContainerInterface;use SymfonyComponentDependencyInjectionContainerAwareInterface;abstract class ContainerAwareCommand extends Command implementsContainerAwareInterface{ private $container; protected function getContainer() { if (null === $this->container) { $this->container = $this->getApplication()->getKernel()->getContainer(); } return $this->container; } public function setContainer(ContainerInterface $container = null) { $this->container = $container; }}
  63. 63. Reading the con guration$container = $this->getContainer();$max = $container->getParameter(hangman.max_attempts);
  64. 64. Accessing the Doctrine registry$container = $this->getContainer();$doctrine = $container->get(doctrine);$em = $doctrine->getEntityManager(default);$score = new Score();$score->setScore(10);$score->setPlayer(hhamon);$em->persist($score);$em->flush();
  65. 65. Rendering Twig templates$container = $this->getContainer();$templating = $container->get(templating):$content = $templating->render( SensioHangmanBundle:Game:finish.txt.twig, array(game => $this->game));
  66. 66. Generating urls$container = $this->getContainer();$router = $container->get(router):$url = $router->generate( game_finish, array(user => hhamon), true);
  67. 67. Translating messages$container = $this->getContainer();$translator = $container->get(translator):$content = $translator->trans( Hello %user%!, array(user => hhamon), null, fr);
  68. 68. Writing logs$container = $this->getContainer();$logger = $container->get(logger);$logger->info(Game finished!);
  69. 69. Dealing with the lesystem$container = $this->getContainer();$fs = $container->get(filesystem);$fs->touch(/path/to/toto.txt);
  70. 70. Conclusion
  71. 71. Questions & Answers Ask a (little) ninja J
  72. 72. •  Calling  a  command  from  a  command  •  Calling  a  command  in  a  command  •  Sending  an  email  

×