[!!!][FEATURE] Integrate Symfony/Console into CommandController 43/30743/11
authorWouter Wolters <typo3@wouterwolters.nl>
Mon, 16 Jun 2014 12:32:12 +0000 (14:32 +0200)
committerAnja Leichsenring <aleichsenring@ab-softlab.de>
Thu, 30 Apr 2015 18:03:22 +0000 (20:03 +0200)
This is a backport from the new introduced feature in Flow
https://review.typo3.org/#/c/30653/ with upstream patches.

This extends the base ``CommandController`` by some convenience
helpers from the ``symfony/console`` package:
easy output coloring through "<error>Warning!</error>"
TableHelper to render values to a grid
ProgressHelper to render and advance and progress bars
DialogHelper with numerous types of questions like: select,
ask, confirm, askHidden, etc
Additionally this change improves the
``mapRequestArgumentsToControllerArguments()`` method to ask for
missing required arguments instead of quitting with an exception.
You can make use of the new features by calling the introduced
ConsoleOutput object with its respective methods:
outputTable()
select()
ask()
askConfirmation()
askHiddenResponse()
askAndValidate()
askHiddenResponseAndValidate()
progressStart()
progressSet()
progressAdvance()
progressFinish()

This change does not alter the public API so it is not breaking
in the strict sense. But it introduces a new behavior:
Previously all outputs where collected in the ``Cli\Response``
and only rendered to the console at the end of a CLI request.
Now all methods producing output (inluding ``output()`` and
``outputLine()``) render the result directly to the console.
If you use ``$this->response`` directly or let the command method
return a string, the rendering is still deferred until the end of
the CLI request.

Resolves: #59606
Releases: master
Change-Id: I33e051f698f5cc1e204f609734280bbed69610c9
Reviewed-on: http://review.typo3.org/30743
Tested-by: Wouter Wolters <typo3@wouterwolters.nl>
Reviewed-by: Helmut Hummel <helmut.hummel@typo3.org>
Tested-by: Helmut Hummel <helmut.hummel@typo3.org>
Reviewed-by: Anja Leichsenring <aleichsenring@ab-softlab.de>
Tested-by: Anja Leichsenring <aleichsenring@ab-softlab.de>
composer.json
typo3/sysext/core/Documentation/Changelog/master/Feature-59606-IntegrateSymfonyConsoleIntoCommandController.rst [new file with mode: 0644]
typo3/sysext/extbase/Classes/Command/HelpCommandController.php
typo3/sysext/extbase/Classes/Mvc/Cli/ConsoleOutput.php [new file with mode: 0644]
typo3/sysext/extbase/Classes/Mvc/Controller/CommandController.php
typo3/sysext/extbase/Tests/Unit/Mvc/Controller/CommandControllerTest.php
typo3/sysext/lang/Classes/Command/LanguageCommandController.php

index dd5a125..ed3ead1 100644 (file)
@@ -37,6 +37,7 @@
                "pear/http_request2": "2.2.1",
                "phpwhois/idna-convert": "0.8.2",
                "swiftmailer/swiftmailer": "5.2.1",
+               "symfony/console": "2.5.*",
                "helhum/class-alias-loader": "~1.1",
                "typo3/cms-composer-installers": "1.1.*@dev"
        },
diff --git a/typo3/sysext/core/Documentation/Changelog/master/Feature-59606-IntegrateSymfonyConsoleIntoCommandController.rst b/typo3/sysext/core/Documentation/Changelog/master/Feature-59606-IntegrateSymfonyConsoleIntoCommandController.rst
new file mode 100644 (file)
index 0000000..03e1e70
--- /dev/null
@@ -0,0 +1,93 @@
+==================================================================
+Feature: #59606 - Integrate Symfony/Console into CommandController
+==================================================================
+
+Description
+===========
+
+The CommandController now makes use of Symfony/Console internally and
+provides various methods directly from the CommandController's ``output`` member:
+
+* TableHelper
+
+       * outputTable($rows, $headers = NULL)
+
+* DialogHelper
+
+       * select($question, $choices, $default = NULL, $multiSelect = false, $attempts = FALSE)
+       * ask($question, $default = NULL, array $autocomplete = array())
+       * askConfirmation($question, $default = TRUE)
+       * askHiddenResponse($question, $fallback = TRUE)
+       * askAndValidate($question, $validator, $attempts = FALSE, $default = NULL, array $autocomplete = NULL)
+       * askHiddenResponseAndValidate($question, $validator, $attempts = FALSE, $fallback = TRUE)
+
+* ProgressHelper
+
+       * progressStart($max = NULL)
+       * progressSet($current)
+       * progressAdvance($step = 1)
+       * progressFinish()
+
+Here's an example showing of some of those functions:
+
+.. code-block:: php
+
+       namespace Acme\Demo\Command;
+
+       use TYPO3\CMS\Extbase\Mvc\Controller\CommandController;
+
+       /**
+        * My command
+        */
+       class MyCommandController extends CommandController {
+
+               /**
+                * @return string
+                */
+               public function myCommand() {
+                       // render a table
+                       $this->output->outputTable(array(
+                               array('Bob', 34, 'm'),
+                               array('Sally', 21, 'f'),
+                               array('Blake', 56, 'm')
+                       ),
+                       array('Name', 'Age', 'Gender'));
+
+                       // select
+                       $colors = array('red', 'blue', 'yellow');
+                       $selectedColorIndex = $this->output->select('Please select one color', $colors, 'red');
+                       $this->outputLine('You choose the color %s.', array($colors[$selectedColorIndex]));
+
+                       // ask
+                       $name = $this->output->ask('What is your name?' . PHP_EOL, 'Bob', array('Bob', 'Sally', 'Blake'));
+                       $this->outputLine('Hello %s.', array($name));
+
+                       // prompt
+                       $likesDogs = $this->output->askConfirmation('Do you like dogs?');
+                       if ($likesDogs) {
+                               $this->outputLine('You do like dogs!');
+                       }
+
+                       // progress
+                       $this->output->progressStart(600);
+                       for ($i = 0; $i < 300; $i ++) {
+                               $this->output->progressAdvance();
+                               usleep(5000);
+                       }
+                       $this->output->progressFinish();
+
+               }
+       }
+
+
+Impact
+======
+
+This change does not alter the public API so it is not breaking
+in the strict sense. But it introduces a new behavior:
+Previously all outputs where collected in the ``Cli\Response``
+and only rendered to the console at the end of a CLI request.
+Now all methods producing output (inluding ``output()`` and
+``outputLine()``) render the result directly to the console.If you use ``$this->response`` directly or let the command method
+return a string, the rendering is still deferred until the end of
+the CLI request.
\ No newline at end of file
index 4947805..66eaf17 100644 (file)
@@ -84,10 +84,10 @@ class HelpCommandController extends \TYPO3\CMS\Extbase\Mvc\Controller\CommandCon
                foreach ($this->commandsByExtensionsAndControllers as $extensionKey => $commandControllers) {
                        $this->outputLine('');
                        $this->outputLine('EXTENSION "%s":', array(strtoupper($extensionKey)));
-                       $this->outputLine(str_repeat('-', self::MAXIMUM_LINE_LENGTH));
+                       $this->outputLine(str_repeat('-', $this->output->getMaximumLineLength()));
                        foreach ($commandControllers as $commands) {
                                foreach ($commands as $command) {
-                                       $description = wordwrap($command->getShortDescription(), self::MAXIMUM_LINE_LENGTH - 43, PHP_EOL . str_repeat(' ', 43), TRUE);
+                                       $description = wordwrap($command->getShortDescription(), $this->output->getMaximumLineLength() - 43, PHP_EOL . str_repeat(' ', 43), TRUE);
                                        $shortCommandIdentifier = $this->commandManager->getShortestIdentifierForCommand($command);
                                        $this->outputLine('%-2s%-40s %s', array(' ', $shortCommandIdentifier, $description));
                                }
@@ -129,7 +129,7 @@ class HelpCommandController extends \TYPO3\CMS\Extbase\Mvc\Controller\CommandCon
                if ($command->hasArguments()) {
                        foreach ($commandArgumentDefinitions as $commandArgumentDefinition) {
                                $argumentDescription = $commandArgumentDefinition->getDescription();
-                               $argumentDescription = wordwrap($argumentDescription, self::MAXIMUM_LINE_LENGTH - 23, PHP_EOL . str_repeat(' ', 23), TRUE);
+                               $argumentDescription = wordwrap($argumentDescription, $this->output->getMaximumLineLength() - 23, PHP_EOL . str_repeat(' ', 23), TRUE);
                                if ($commandArgumentDefinition->isRequired()) {
                                        $argumentDescriptions[] = vsprintf('  %-20s %s', array($commandArgumentDefinition->getDashedName(), $argumentDescription));
                                } else {
diff --git a/typo3/sysext/extbase/Classes/Mvc/Cli/ConsoleOutput.php b/typo3/sysext/extbase/Classes/Mvc/Cli/ConsoleOutput.php
new file mode 100644 (file)
index 0000000..d02f123
--- /dev/null
@@ -0,0 +1,307 @@
+<?php
+namespace TYPO3\CMS\Extbase\Mvc\Cli;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use Symfony\Component\Console\Formatter\OutputFormatterStyle;
+use Symfony\Component\Console\Helper\DialogHelper;
+use Symfony\Component\Console\Helper\FormatterHelper;
+use Symfony\Component\Console\Helper\HelperSet;
+use Symfony\Component\Console\Helper\ProgressHelper;
+use Symfony\Component\Console\Helper\TableHelper;
+use Symfony\Component\Console\Output\ConsoleOutput as SymfonyConsoleOutput;
+
+/**
+ * A wrapper for Symfony ConsoleOutput and related helpers
+ */
+class ConsoleOutput {
+
+       /**
+        * @var SymfonyConsoleOutput
+        */
+       protected $output;
+
+       /**
+        * @var DialogHelper
+        */
+       protected $dialogHelper;
+
+       /**
+        * @var ProgressHelper
+        */
+       protected $progressHelper;
+
+       /**
+        * @var TableHelper
+        */
+       protected $tableHelper;
+
+       /**
+        * Creates and initializes the SymfonyConsoleOutput instance
+        *
+        * @return void
+        */
+       public function __construct() {
+               $this->output = new SymfonyConsoleOutput();
+               $this->output->getFormatter()->setStyle('b', new OutputFormatterStyle(NULL, NULL, array('bold')));
+               $this->output->getFormatter()->setStyle('i', new OutputFormatterStyle('black', 'white'));
+               $this->output->getFormatter()->setStyle('u', new OutputFormatterStyle(NULL, NULL, array('underscore')));
+               $this->output->getFormatter()->setStyle('em', new OutputFormatterStyle(NULL, NULL, array('reverse')));
+               $this->output->getFormatter()->setStyle('strike', new OutputFormatterStyle(NULL, NULL, array('conceal')));
+       }
+
+       /**
+        * Returns the desired maximum line length for console output.
+        *
+        * @return int
+        */
+       public function getMaximumLineLength() {
+               return 79;
+       }
+
+       /**
+        * Outputs specified text to the console window
+        * You can specify arguments that will be passed to the text via sprintf
+        * @see http://www.php.net/sprintf
+        *
+        * @param string $text Text to output
+        * @param array $arguments Optional arguments to use for sprintf
+        * @return void
+        */
+       public function output($text, array $arguments = array()) {
+               if ($arguments !== array()) {
+                       $text = vsprintf($text, $arguments);
+               }
+               $this->output->write($text);
+       }
+
+       /**
+        * Outputs specified text to the console window and appends a line break
+        *
+        * @param string $text Text to output
+        * @param array $arguments Optional arguments to use for sprintf
+        * @return void
+        * @see output()
+        * @see outputLines()
+        */
+       public function outputLine($text = '', array $arguments = array()) {
+               $this->output($text . PHP_EOL, $arguments);
+       }
+
+       /**
+        * Formats the given text to fit into the maximum line length and outputs it to the
+        * console window
+        *
+        * @param string $text Text to output
+        * @param array $arguments Optional arguments to use for sprintf
+        * @param int $leftPadding The number of spaces to use for indentation
+        * @return void
+        * @see outputLine()
+        */
+       public function outputFormatted($text = '', array $arguments = array(), $leftPadding = 0) {
+               $lines = explode(PHP_EOL, $text);
+               foreach ($lines as $line) {
+                       $formattedText = str_repeat(' ', $leftPadding) . wordwrap($line, $this->getMaximumLineLength() - $leftPadding, PHP_EOL . str_repeat(' ', $leftPadding), TRUE);
+                       $this->outputLine($formattedText, $arguments);
+               }
+       }
+
+       /**
+        * Renders a table like output of the given $rows
+        *
+        * @param array $rows
+        * @param array $headers
+        */
+       public function outputTable($rows, $headers = NULL) {
+               $tableHelper = $this->getTableHelper();
+               if ($headers !== NULL) {
+                       $tableHelper->setHeaders($headers);
+               }
+               $tableHelper->setRows($rows);
+               $tableHelper->render($this->output);
+       }
+
+       /**
+        * Asks the user to select a value
+        *
+        * @param string|array $question The question to ask. If an array each array item is turned into one line of a multi-line question
+        * @param array $choices List of choices to pick from
+        * @param bool $default The default answer if the user enters nothing
+        * @param bool $multiSelect If TRUE the result will be an array with the selected options. Multiple options can be given separated by commas
+        * @param bool|int $attempts Max number of times to ask before giving up (false by default, which means infinite)
+        * @return int|string|array The selected value or values (the key of the choices array)
+        * @throws \InvalidArgumentException
+        */
+       public function select($question, $choices, $default = NULL, $multiSelect = FALSE, $attempts = FALSE) {
+               return $this->getDialogHelper()->select($this->output, $question, $choices, $default, $attempts, 'Value "%s" is invalid', $multiSelect);
+       }
+
+       /**
+        * Asks a question to the user
+        *
+        * @param string|array $question The question to ask. If an array each array item is turned into one line of a multi-line question
+        * @param string $default The default answer if none is given by the user
+        * @param array $autocomplete List of values to autocomplete. This only works if "stty" is installed
+        * @return string The user answer
+        * @throws \RuntimeException If there is no data to read in the input stream
+        */
+       public function ask($question, $default = NULL, array $autocomplete = NULL) {
+               return $this->getDialogHelper()->ask($this->output, $question, $default, $autocomplete);
+       }
+
+       /**
+        * Asks a confirmation to the user.
+        *
+        * The question will be asked until the user answers by nothing, yes, or no.
+        *
+        * @param string|array $question The question to ask. If an array each array item is turned into one line of a multi-line question
+        * @param bool $default The default answer if the user enters nothing
+        * @return bool true if the user has confirmed, false otherwise
+        */
+       public function askConfirmation($question, $default = TRUE) {
+               return $this->getDialogHelper()->askConfirmation($this->output, $question, $default);
+       }
+
+       /**
+        * Asks a question to the user, the response is hidden
+        *
+        * @param string|array $question The question. If an array each array item is turned into one line of a multi-line question
+        * @param bool $fallback In case the response can not be hidden, whether to fallback on non-hidden question or not
+        * @return string The answer
+        * @throws \RuntimeException In case the fallback is deactivated and the response can not be hidden
+        */
+       public function askHiddenResponse($question, $fallback = TRUE) {
+               return $this->getDialogHelper()->askHiddenResponse($this->output, $question, $fallback);
+       }
+
+       /**
+        * Asks for a value and validates the response
+        *
+        * The validator receives the data to validate. It must return the
+        * validated data when the data is valid and throw an exception
+        * otherwise.
+        *
+        * @param string|array $question The question to ask. If an array each array item is turned into one line of a multi-line question
+        * @param callable $validator A PHP callback that gets a value and is expected to return the (transformed) value or throw an exception if it wasn't valid
+        * @param int|bool $attempts Max number of times to ask before giving up (false by default, which means infinite)
+        * @param string $default The default answer if none is given by the user
+        * @param array $autocomplete List of values to autocomplete. This only works if "stty" is installed
+        * @return mixed
+        * @throws \Exception When any of the validators return an error
+        */
+       public function askAndValidate($question, $validator, $attempts = FALSE, $default = NULL, array $autocomplete = NULL) {
+               return $this->getDialogHelper()->askAndValidate($this->output, $question, $validator, $attempts, $default, $autocomplete);
+       }
+
+       /**
+        * Asks for a value, hide and validates the response
+        *
+        * The validator receives the data to validate. It must return the
+        * validated data when the data is valid and throw an exception
+        * otherwise.
+        *
+        * @param string|array $question The question to ask. If an array each array item is turned into one line of a multi-line question
+        * @param callable $validator A PHP callback that gets a value and is expected to return the (transformed) value or throw an exception if it wasn't valid
+        * @param int|bool $attempts Max number of times to ask before giving up (false by default, which means infinite)
+        * @param bool $fallback In case the response can not be hidden, whether to fallback on non-hidden question or not
+        * @return string The response
+        * @throws \Exception When any of the validators return an error
+        * @throws \RuntimeException In case the fallback is deactivated and the response can not be hidden
+        */
+       public function askHiddenResponseAndValidate($question, $validator, $attempts = FALSE, $fallback = TRUE) {
+               return $this->getDialogHelper()->askHiddenResponseAndValidate($this->output, $question, $validator, $attempts, $fallback);
+       }
+
+       /**
+        * Starts the progress output
+        *
+        * @param int $max Maximum steps. If NULL an indeterminate progress bar is rendered
+        * @return void
+        */
+       public function progressStart($max = NULL) {
+               $this->getProgressHelper()->start($this->output, $max);
+       }
+
+       /**
+        * Advances the progress output X steps
+        *
+        * @param int $step Number of steps to advance
+        * @param bool $redraw Whether to redraw or not
+        * @return void
+        * @throws \LogicException
+        */
+       public function progressAdvance($step = 1, $redraw = false) {
+               $this->getProgressHelper()->advance($step, $redraw);
+       }
+
+       /**
+        * Sets the current progress
+        *
+        * @param int $current The current progress
+        * @param bool $redraw Whether to redraw or not
+        * @return void
+        * @throws \LogicException
+        */
+       public function progressSet($current, $redraw = false) {
+               $this->getProgressHelper()->setCurrent($current, $redraw);
+       }
+
+       /**
+        * Finishes the progress output
+        *
+        * @return void
+        */
+       public function progressFinish() {
+               $this->getProgressHelper()->finish();
+       }
+
+       /**
+        * Returns or initializes the symfony/console DialogHelper
+        *
+        * @return DialogHelper
+        */
+       protected function getDialogHelper() {
+               if ($this->dialogHelper === NULL) {
+                       $this->dialogHelper = new DialogHelper();
+                       $helperSet = new HelperSet(array(new FormatterHelper()));
+                       $this->dialogHelper->setHelperSet($helperSet);
+               }
+               return $this->dialogHelper;
+       }
+
+       /**
+        * Returns or initializes the symfony/console ProgressHelper
+        *
+        * @return ProgressHelper
+        */
+       protected function getProgressHelper() {
+               if ($this->progressHelper === NULL) {
+                       $this->progressHelper = new ProgressHelper();
+               }
+               return $this->progressHelper;
+       }
+
+       /**
+        * Returns or initializes the symfony/console TableHelper
+        *
+        * @return TableHelper
+        */
+       protected function getTableHelper() {
+               if ($this->tableHelper === NULL) {
+                       $this->tableHelper = new TableHelper();
+               }
+               return $this->tableHelper;
+       }
+
+}
\ No newline at end of file
index 1c58248..0fb18d9 100644 (file)
@@ -14,6 +14,21 @@ namespace TYPO3\CMS\Extbase\Mvc\Controller;
  * The TYPO3 project - inspiring people to share!
  */
 
+use TYPO3\CMS\Extbase\Mvc\Cli\ConsoleOutput;
+use TYPO3\CMS\Extbase\Mvc\Cli\Request;
+use TYPO3\CMS\Extbase\Mvc\Cli\Response;
+use Symfony\Component\Console\Output\ConsoleOutput as SymfonyConsoleOutput;
+use Symfony\Component\Console\Helper\DialogHelper;
+use Symfony\Component\Console\Helper\FormatterHelper;
+use Symfony\Component\Console\Helper\HelperSet;
+use Symfony\Component\Console\Helper\ProgressHelper;
+use Symfony\Component\Console\Helper\TableHelper;
+use TYPO3\CMS\Extbase\Mvc\Exception\InvalidArgumentTypeException;
+use TYPO3\CMS\Extbase\Mvc\Exception\NoSuchCommandException;
+use TYPO3\CMS\Extbase\Mvc\Exception\StopActionException;
+use TYPO3\CMS\Extbase\Mvc\Exception\UnsupportedRequestTypeException;
+use TYPO3\CMS\Extbase\Mvc\RequestInterface;
+use TYPO3\CMS\Extbase\Mvc\ResponseInterface;
 use TYPO3\CMS\Core\Authentication\AbstractUserAuthentication;
 use TYPO3\CMS\Extbase\Mvc\Cli\CommandArgumentDefinition;
 
@@ -24,20 +39,18 @@ use TYPO3\CMS\Extbase\Mvc\Cli\CommandArgumentDefinition;
  */
 class CommandController implements CommandControllerInterface {
 
-       const MAXIMUM_LINE_LENGTH = 79;
-
        /**
-        * @var \TYPO3\CMS\Extbase\Mvc\Cli\Request
+        * @var Request
         */
        protected $request;
 
        /**
-        * @var \TYPO3\CMS\Extbase\Mvc\Cli\Response
+        * @var Response
         */
        protected $response;
 
        /**
-        * @var \TYPO3\CMS\Extbase\Mvc\Controller\Arguments
+        * @var Arguments
         */
        protected $arguments;
 
@@ -73,6 +86,11 @@ class CommandController implements CommandControllerInterface {
        protected $objectManager;
 
        /**
+        * @var \TYPO3\CMS\Extbase\Mvc\Cli\ConsoleOutput
+        */
+       protected $output;
+
+       /**
         * @param \TYPO3\CMS\Extbase\Object\ObjectManagerInterface $objectManager
         * @return void
         */
@@ -80,6 +98,7 @@ class CommandController implements CommandControllerInterface {
                $this->objectManager = $objectManager;
                $this->arguments = $this->objectManager->get(\TYPO3\CMS\Extbase\Mvc\Controller\Arguments::class);
                $this->userAuthentication = isset($GLOBALS['BE_USER']) ? $GLOBALS['BE_USER'] : NULL;
+               $this->output = $this->objectManager->get(ConsoleOutput::class);
        }
 
        /**
@@ -89,25 +108,27 @@ class CommandController implements CommandControllerInterface {
         * @return bool TRUE if this request type is supported, otherwise FALSE
         */
        public function canProcessRequest(\TYPO3\CMS\Extbase\Mvc\RequestInterface $request) {
-               return $request instanceof \TYPO3\CMS\Extbase\Mvc\Cli\Request;
+               return $request instanceof Request;
        }
 
        /**
         * Processes a command line request.
         *
-        * @param \TYPO3\CMS\Extbase\Mvc\RequestInterface $request The request object
-        * @param \TYPO3\CMS\Extbase\Mvc\ResponseInterface $response The response, modified by this controller
-        * @throws \TYPO3\CMS\Extbase\Mvc\Exception\UnsupportedRequestTypeException
+        * @param RequestInterface $request The request object
+        * @param ResponseInterface $response The response, modified by this handler
         * @return void
+        * @throws UnsupportedRequestTypeException if the controller doesn't support the current request type
         * @api
         */
-       public function processRequest(\TYPO3\CMS\Extbase\Mvc\RequestInterface $request, \TYPO3\CMS\Extbase\Mvc\ResponseInterface $response) {
+       public function processRequest(RequestInterface $request, ResponseInterface $response) {
                if (!$this->canProcessRequest($request)) {
-                       throw new \TYPO3\CMS\Extbase\Mvc\Exception\UnsupportedRequestTypeException(get_class($this) . ' does not support requests of type "' . get_class($request) . '".', 1300787096);
+                       throw new UnsupportedRequestTypeException(sprintf('%s only supports command line requests – requests of type "%s" given.', get_class($this), get_class($request)), 1300787096);
                }
+
                $this->request = $request;
                $this->request->setDispatched(TRUE);
                $this->response = $response;
+
                $this->commandMethodName = $this->resolveCommandMethodName();
                $this->initializeCommandMethodArguments();
                $this->mapRequestArgumentsToControllerArguments();
@@ -122,11 +143,12 @@ class CommandController implements CommandControllerInterface {
         *
         * @throws \TYPO3\CMS\Extbase\Mvc\Exception\NoSuchCommandException
         * @return string Method name of the current command
+        * @throws NoSuchCommandException
         */
        protected function resolveCommandMethodName() {
                $commandMethodName = $this->request->getControllerCommandName() . 'Command';
                if (!is_callable(array($this, $commandMethodName))) {
-                       throw new \TYPO3\CMS\Extbase\Mvc\Exception\NoSuchCommandException('A command method "' . $commandMethodName . '()" does not exist in controller "' . get_class($this) . '".', 1300902143);
+                       throw new NoSuchCommandException(sprintf('A command method "%s()" does not exist in controller "%s".', $commandMethodName, get_class($this)), 1300902143);
                }
                return $commandMethodName;
        }
@@ -137,9 +159,12 @@ class CommandController implements CommandControllerInterface {
         *
         * @throws \TYPO3\CMS\Extbase\Mvc\Exception\InvalidArgumentTypeException
         * @return void
+        * @throws InvalidArgumentTypeException
         */
        protected function initializeCommandMethodArguments() {
+               $this->arguments->removeAll();
                $methodParameters = $this->reflectionService->getMethodParameters(get_class($this), $this->commandMethodName);
+
                foreach ($methodParameters as $parameterName => $parameterInfo) {
                        $dataType = NULL;
                        if (isset($parameterInfo['type'])) {
@@ -148,10 +173,10 @@ class CommandController implements CommandControllerInterface {
                                $dataType = 'array';
                        }
                        if ($dataType === NULL) {
-                               throw new \TYPO3\CMS\Extbase\Mvc\Exception\InvalidArgumentTypeException('The argument type for parameter $' . $parameterName . ' of method ' . get_class($this) . '->' . $this->commandMethodName . '() could not be detected.', 1306755296);
+                               throw new InvalidArgumentTypeException(sprintf('The argument type for parameter $%s of method %s->%s() could not be detected.', $parameterName, get_class($this), $this->commandMethodName), 1306755296);
                        }
-                       $defaultValue = isset($parameterInfo['defaultValue']) ? $parameterInfo['defaultValue'] : NULL;
-                       $this->arguments->addNewArgument($parameterName, $dataType, $parameterInfo['optional'] === FALSE, $defaultValue);
+                       $defaultValue = (isset($parameterInfo['defaultValue']) ? $parameterInfo['defaultValue'] : NULL);
+                       $this->arguments->addNewArgument($parameterName, $dataType, ($parameterInfo['optional'] === FALSE), $defaultValue);
                }
        }
 
@@ -166,11 +191,17 @@ class CommandController implements CommandControllerInterface {
                        $argumentName = $argument->getName();
                        if ($this->request->hasArgument($argumentName)) {
                                $argument->setValue($this->request->getArgument($argumentName));
-                       } elseif ($argument->isRequired()) {
-                               $commandArgumentDefinition = $this->objectManager->get(CommandArgumentDefinition::class, $argumentName, TRUE, NULL);
-                               $exception = new \TYPO3\CMS\Extbase\Mvc\Exception\CommandException('Required argument "' . $commandArgumentDefinition->getDashedName() . '" is not set.', 1306755520);
-                               $this->forward('error', \TYPO3\CMS\Extbase\Command\HelpCommandController::class, array('exception' => $exception));
+                               continue;
+                       }
+                       if (!$argument->isRequired()) {
+                               continue;
+                       }
+                       $argumentValue = NULL;
+                       $commandArgumentDefinition = $this->objectManager->get(CommandArgumentDefinition::class, $argumentName, TRUE, NULL);
+                       while ($argumentValue === NULL) {
+                               $argumentValue = $this->output->ask(sprintf('<comment>Please specify the required argument "%s":</comment> ', $commandArgumentDefinition->getDashedName()));
                        }
+                       $argument->setValue($argumentValue);
                }
        }
 
@@ -183,8 +214,8 @@ class CommandController implements CommandControllerInterface {
         * @param string $commandName
         * @param string $controllerObjectName
         * @param array $arguments
-        * @throws \TYPO3\CMS\Extbase\Mvc\Exception\StopActionException
         * @return void
+        * @throws StopActionException
         */
        protected function forward($commandName, $controllerObjectName = NULL, array $arguments = array()) {
                $this->request->setDispatched(FALSE);
@@ -193,8 +224,9 @@ class CommandController implements CommandControllerInterface {
                        $this->request->setControllerObjectName($controllerObjectName);
                }
                $this->request->setArguments($arguments);
+
                $this->arguments->removeAll();
-               throw new \TYPO3\CMS\Extbase\Mvc\Exception\StopActionException();
+               throw new StopActionException();
        }
 
        /**
@@ -258,10 +290,7 @@ class CommandController implements CommandControllerInterface {
         * @return void
         */
        protected function output($text, array $arguments = array()) {
-               if ($arguments !== array()) {
-                       $text = vsprintf($text, $arguments);
-               }
-               $this->response->appendContent($text);
+               $this->output->output($text, $arguments);
        }
 
        /**
@@ -273,7 +302,21 @@ class CommandController implements CommandControllerInterface {
         * @see output()
         */
        protected function outputLine($text = '', array $arguments = array()) {
-               $this->output($text . PHP_EOL, $arguments);
+               $this->output->outputLine($text, $arguments);
+       }
+
+       /**
+        * Formats the given text to fit into MAXIMUM_LINE_LENGTH and outputs it to the
+        * console window
+        *
+        * @param string $text Text to output
+        * @param array $arguments Optional arguments to use for sprintf
+        * @param int $leftPadding The number of spaces to use for indentation
+        * @return void
+        * @see outputLine()
+        */
+       protected function outputFormatted($text = '', array $arguments = array(), $leftPadding = 0) {
+               $this->output->outputFormatted($text, $arguments, $leftPadding);
        }
 
        /**
@@ -281,12 +324,12 @@ class CommandController implements CommandControllerInterface {
         * An exit status code can be specified @see http://www.php.net/exit
         *
         * @param int $exitCode Exit code to return on exit
-        * @throws \TYPO3\CMS\Extbase\Mvc\Exception\StopActionException
+        * @throws StopActionException
         * @return void
         */
        protected function quit($exitCode = 0) {
                $this->response->setExitCode($exitCode);
-               throw new \TYPO3\CMS\Extbase\Mvc\Exception\StopActionException();
+               throw new StopActionException;
        }
 
        /**
@@ -298,7 +341,7 @@ class CommandController implements CommandControllerInterface {
         */
        protected function sendAndExit($exitCode = 0) {
                $this->response->send();
-               die($exitCode);
+               exit($exitCode);
        }
 
 }
index b53499e..00479ca 100644 (file)
@@ -24,17 +24,22 @@ class CommandControllerTest extends \TYPO3\CMS\Core\Tests\UnitTestCase {
         */
        protected $commandController;
 
+       /**
+        * \Symfony\Component\Console\Output\ConsoleOutput|\PHPUnit_Framework_MockObject_MockObject
+        */
+       protected $mockConsoleOutput;
+
        protected function setUp() {
                $this->commandController = $this->getAccessibleMock(\TYPO3\CMS\Extbase\Mvc\Controller\CommandController::class, array('dummyCommand'));
+               $this->mockConsoleOutput = $this->getMockBuilder(\TYPO3\CMS\Extbase\Mvc\Cli\ConsoleOutput::class)->disableOriginalConstructor()->getMock();
+               $this->commandController->_set('output', $this->mockConsoleOutput);
        }
 
        /**
         * @test
         */
        public function outputAppendsGivenStringToTheResponseContent() {
-               $mockResponse = $this->getMock(\TYPO3\CMS\Extbase\Mvc\Cli\Response::class);
-               $mockResponse->expects($this->once())->method('appendContent')->with('some text');
-               $this->commandController->_set('response', $mockResponse);
+               $this->mockConsoleOutput->expects($this->once())->method('output')->with('some text');
                $this->commandController->_call('output', 'some text');
        }
 
@@ -42,24 +47,12 @@ class CommandControllerTest extends \TYPO3\CMS\Core\Tests\UnitTestCase {
         * @test
         */
        public function outputReplacesArgumentsInGivenString() {
-               $mockResponse = $this->getMock(\TYPO3\CMS\Extbase\Mvc\Cli\Response::class);
-               $mockResponse->expects($this->once())->method('appendContent')->with('some text');
-               $this->commandController->_set('response', $mockResponse);
+               $this->mockConsoleOutput->expects($this->once())->method('output')->with('%2$s %1$s', array('text', 'some'));
                $this->commandController->_call('output', '%2$s %1$s', array('text', 'some'));
        }
 
        /**
         * @test
-        */
-       public function outputLineAppendsGivenStringAndNewlineToTheResponseContent() {
-               $mockResponse = $this->getMock(\TYPO3\CMS\Extbase\Mvc\Cli\Response::class);
-               $mockResponse->expects($this->once())->method('appendContent')->with('some text' . PHP_EOL);
-               $this->commandController->_set('response', $mockResponse);
-               $this->commandController->_call('outputLine', 'some text');
-       }
-
-       /**
-        * @test
         * @expectedException \TYPO3\CMS\Extbase\Mvc\Exception\StopActionException
         */
        public function quitThrowsStopActionException() {
index d2c9e98..d42f32b 100644 (file)
@@ -59,6 +59,8 @@ class LanguageCommandController extends \TYPO3\CMS\Extbase\Mvc\Controller\Comman
                $packageManager = $this->objectManager->get(\TYPO3\CMS\Core\Package\PackageManager::class);
                $this->emitPackagesMayHaveChangedSignal();
                $packages = $packageManager->getAvailablePackages();
+               $this->outputLine((sprintf('Updating language packs of all activated extensions for locales "%s"', implode(', ', $locales))));
+               $this->output->progressStart(count($locales)*count($packages));
                foreach ($locales as $locale) {
                        /** @var PackageInterface $package */
                        foreach ($packages as $package) {
@@ -67,8 +69,10 @@ class LanguageCommandController extends \TYPO3\CMS\Extbase\Mvc\Controller\Comman
                                if (empty($result[$extensionKey][$locale]['error'])) {
                                        $this->registryService->set($locale, $GLOBALS['EXEC_TIME']);
                                }
+                               $this->output->progressAdvance();
                        }
                }
+               $this->output->progressFinish();
        }
 
        /**