Skip to end of metadata
Go to start of metadata

<h1>Zend Console RFC</h1>

<p>This RFC contains a proposal for implementing goals set previously in <a href="">RFC - CLI</a>. </p>

<p>It is partially implemented and under development in this Git Repository: <a href=""></a></p>

<p>Components and features <span style="color: red;">marked in red</span> are unfinished (broken).</p>

<p>Components and features <span style="color: gray;">marked in gray</span> are not implemented yet.</p>



<p>A static class used for detecting and retrieving Console Adapters. </p>

<p>It will be generally used as a factory for auto-detected console adapter:</p>

<ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
// auto-detect console adapter
$console = Zend\Console\Console::getInstance();
$console->writeLine("Hello world!");

<p>We can tell the factory to instantiate a specific adapter (keep in mind that it might be incompatible with current terminal)</p>

<ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
use Zend\Console\Console, Zend\Console\Color;

// force use the WindowsAnsicon adapter
$console = Console::getInstance("WindowsAnsicon");
$console->writeLine("Hello world!", Color::GREEN);

<p>Charset is auto-detected as well, but we can force one:</p>

<ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
use Zend\Console\Console;

// auto-detect adapter but force basic ascii charset
$console = Console::getInstance(null, "Ascii");
$console->writeBox(1,1,10,10); // draw a box


<p>Adapters provide API for interacting with various console environments. Adapter can be picked (sensed) automatically by <code>Zend\Console</code> static class.</p>

<p>Basic Adapter responsibilities are:</p>
<li>input/output API
<li>get/write line</li>
<li>get/write char</li>
<li><code>writeBox()</code> - basic line drawing</li>
<li><code>writeTextBox()</code> - basic managed text drawing</li>
<li>retrieving terminal size (width, height)</li>
<li>cursor positioning</li>
<li>handling styled (colored) text</li>
<li>clearing terminal screen/line.</li>

<p>Currently there are following adapters implemented:</p>
<li><code>Adapter\Posix</code> - for posix-compliant systems, such as Unix, Linux, BSD, etc.</li>
<li><code>Adapter\Windows</code> - for stock MS Windows (versions XP up)</li>
<li><code>Adapter\WindowsAnsicon</code> - for MS Windows with Ansicon extension</li>
<li><span style="color: red;"><code>Adapter\Virtual</code></span> - a failback providing absolute cursor positioning for limited console environments</li>


<p>Charset components contain character definitions and escape codes required to draw special console characters. <br />
Charset is selected (sensed) automatically by Console Adapter to match console capabilities.</p>

<p>Currently there are following charsets implemented:</p>
<li><code>Charset\Ascii</code> - basic ascii, failback charset</li>
<li><code>Charset\AsciiExtended</code> - Ascii 127+ charset, used primarily by <code>Adapter\Windows</code></li>
<li><code>Charset\DECSG</code> - DEC Special Graphics set (defined by VT100), used primarily by <code>Adapter\Posix</code> in non-utf8 mode</li>
<li><code>Charset\Utf8</code> - charset for utf8 enabled terminals</li>
<li><code>Charset\Utf8Heavy</code> - charset for utf8 enabled terminals, using heavier line art.</li>


<p>Basic Console Request class, a container for command-line parameters and environment info that is used for Console MVC routing.</p>

<li>retrieve and store command line params (in raw form, usually from <code>$argv</code>)</li>
<li>retrieve and store php-provided environment info (usually from <code>$_ENV</code>)</li>

<p>Basic usage:</p>
<ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
$request = new Zend\Console\Request();
echo $request->params->get(2); // echo second command line argument
echo $request->env->get("PWD"); // echo current working directory name


<p>These classes provide interactive, cross-system user prompts. They depend on <code>Console\Adapter</code> and handle most common interactive input found in terminal applications.</p>

<p>Currently the following classes are implemented:</p>
<li><code>Prompt\Char</code> - prompt for a single char (key), optionally filter against a key mask of allowed chars.</li>
<li><code>Prompt\Confirm</code> - ask the user to confirm an operation</li>
<li><code>Prompt\Line</code> - ask for a line of input</li>
<li><code>Prompt\Number</code> - prompt for a number, optionally validated against min, max and floating-point restrictions.</li>
<li><code>Prompt\Select</code> - ask the user to select one option from the provided list.</li>
<li><span style="color: gray;"><code>Prompt\File</code></span> - prompt for a path to existing/readable file.</li>
<li><span style="color: gray;"><code>Prompt\Dir</code></span> - prompt for an existing (writable/readable) dir path.</li>

<p>Basic prompts usage:</p>
<ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
use Zend\Console\Prompt, Zend\Console\Color;


  • Simple line prompt
    $prompt = new Prompt\Line('What is your name? ');
    $name = $prompt->show();
    $console->writeLine("Hello there $name!",Color::GREEN);


  • Confirmation
    $prompt = new Prompt\Confirm('Do you want to continue? (y/n)');
    $continue = $prompt->show();
    $console->writeLine("You've chosen not to continue...",Color::YELLOW);
    $console->writeLine("Good bye!");


  • A list of options (selection)
    $console->writeLine("Quiz time!");
    $prompt = new Prompt\Select(
    'How many children did president Richard Nixon have?',
    'a' => '1 child',
    'b' => '2 children',
    'c' => '3 children',
    'd' => 'He never had children'
    $nixon = $prompt->show();

<p>More examples for all prompt classes can be found in <a href="">demo/prompts.php</a></p>

<h5>Zend\Console\Decorator or Zend\Console\Renderer</h5>

<p>This class will be used to format Console output. Previous CLI RFC and prototypes introduced the concept of Decorators, that take any string (usually an output from a command) and decorate it (i.e. add line wrapping, add color, align, etc.). Because <code>Zend\Console</code> components are being adapted to work with <code>Zend\Mvc</code>, a new problem arised.</p>

<ac:macro ac:name="info"><ac:rich-text-body>
<p>This class does not exist yet, as it requires some more conceptual work.</p></ac:rich-text-body></ac:macro>

<p>If Console applications are being driven by Controllers and their Actions, there is a detachment between the interactive actions being performed and console output. It becomes very similar to HTTP request handling, where zf2-based application produces a piece of HTML content, that is later rendered by user's browser. In CLI, we do not have the luxury of a common "viewer" or "browser" that would handle rendering of complex console output.</p>

<p>To emulate that behavior, I'd like to propose a simple markup language that will be used to pretty-print console output. </p>

<p>Here is an example use-case:</p>

<ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
class InstallerController extends Action
function installAction(){
// perform some installation tasks

Unknown macro: { return "[color}


Unknown macro: { return "[color}


function helpAction()

Unknown macro: { return "[align}


<p>In above example InstallerController serves CLI requests and contains two actions. Each action returns a string with the response peppered with simple, self-explanatory CLI markup. The <code>Zend\Console\Renderer</code> component translated that markup and invokes appropriate <code>Zend\Console\Adapters</code> to pretty-print the result in user's terminal window. If any of the requested features are missing in current environment, they will be ignored by the adapter (but the application and rendering will still work correctly).</p>

<h2>Zend\Mvc Components</h2>

<h5><span style="color: red;">Zend\Mvc\ConsoleApplication</span></h5>

<p>Extension of <code>Zend\Mvc\Application</code>, providing a dispatch loop to handle incoming command-line request and route them to appropriate Mvc action controllers.</p>

<li>Create a <code>Console\Request</code> from passed command-line arguments.</li>
<li>Prepare Console-specific routes</li>
<li>Pass the request to the router</li>
<li>If a route matches, invoke a controller and action to handle the request.
<li>If the Action returns a response, process it with 0 or more <code>Console\Decorators</code> and output to terminal</li>
<li>If the Action returns one or more prompts, display them to the user, collect values, merge with original request and dispatch it again.</li>
<li>If there are no prompts, end application execution.</li>

<h5><span style="color: red;">Zend\Mvc\Route\Console\Simple</span></h5>

<p>A basic, universal route for parsing CLI requests.</p>

<li>Take a route description and parse it</li>
<li>Handle 0 or more constraints (regular expressions for validating param values)</li>
<li>Handle 0 or more filters (for cleaning user-provided values)</li>
<li>Handle 0 or more validators (for validating CLI user input)</li>
<li>Handle 0 or more aliases (that can translate parameter names)</li>
<li>Handle 0 or more default values</li>

<h2>CLI MVC application</h2>

<h5>General CLI application workflow</h5>

<li><code>app.php</code> script is the entry point</li>
<li>Module Manager, configuration and <code>Zend\Mvc\ConsoleApplication</code> are instantiated and configured.</li>
<li><code>ConsoleApplication</code> creates new instance of <code>Zend\Console\Request</code></li>
<li><code>Zend\Console\Request</code> reads command-line arguments</li>
<li>Console routes are read from config.</li>
<li><code>Router</code> runs the <code>$consoleRequest</code> against all declared Console routes until first route matches
<li>if a route matches, but param validation fails, an exception is thrown, caught by <code>ConsoleApplication</code> and transformed to usage information</li>
<li>if no route matches, a default "usage" route could be invoked</li>
<li>if no route matches, a closest matching route will be used as a "did you mean..." suggestion</li>
<li><code>RouteMatch</code> is returned containing all matched mandatory and optional parameters, along with their values</li>
<li>An Action controller is invoked with the request and an Action is called
<li>If the Action returns a string, it will be output to the terminal screen</li>
<li>If the Action returns a ViewModel it will be analyzed
<li>ViewModel may contain 1 or more <code>Console\Prompts</code> they will be displayed to the user</li>
<li>All responses to prompts will be collected and merged with the original <code>$consoleRequest</code> as params</li>
<li>The <code>$consoleRequest</code> is fed back into <code>Router</code></li>
<li>The <code>Router</code> matches the same (or another) route and Action Controller is invoked</li>
<li>... the process repeats itself until there are no more prompts (i.e. the application displays some output and finishes)</li>

<h5>Example Console route definition</h5>
<ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
$route = Zend\Mvc\Router\Console\Simple::factory(array(

// route definition
'route' => 'install ( module | package | application | app ) NAME [--verbose] [--quick] [--user=s]',

// defaults
'defaults' => array(
'controller' => 'installator',
'user' => 'system',
'verbose' => false,

// constraints on parameter values
'constraints' => array(
'name' => '/^[a-zA-Z\_]*$/'
'system' => '/^[a-zA-Z\_]*$/'

<h5>Basic definitions</h5>
<p> <span style="color: green;"><strong>positional literal parameter</strong></span> - a single word that has be typed by the user for the route to match, i.e. literal "install"</p>

<p> <span style="color: green;"><strong>positional value parameter</strong></span> - a positional param that can take any value.</p>

<p> <span style="color: green;"><strong>named flag</strong></span> - a single, boolean (present or not present) named parameter, prefixed with double colons, i.e. "--verbose"</p>

<p> <span style="color: green;"><strong>named short flag</strong></span> - a short version of a flag, prefixed with a single colon, i.e. "-v"</p>

<p> <span style="color: green;"><strong>named value parameter</strong></span> - a double-colon prefixed, named parameter that expects a value, i.e. "--name Foo"</p>

<h5>Recognized route definition tokens</h5>
<li><span style="color: blue;"><code>param</code></span> - a mandatory literal positional parameter</li>
<li><span style="color: blue;"><code>[param]</code></span> - an optional literal positional parameter</li>
<li><span style="color: blue;"><code>( foo | bar | baz )</code></span> - a mandatory positional parameter, one of the provided options</li>
<li><span style="color: blue;"><code>SOMEPARAM</code></span> - a mandatory positional value parameter (any string)</li>
<li><span style="color: blue;"><code>[SOMEPARAM]</code></span> - an optional positional value parameter</li>
<li><span style="color: blue;"><code>--param</code></span> - a mandatory flag</li>
<li><span style="color: blue;"><code>[--param]</code></span> - an optional flag</li>
<li><span style="color: blue;"><code>--param=s</code></span> - a mandatory value parameter (of type *s*tring or *n*umber)</li>
<li><span style="color: blue;"><code>[--param=s]</code></span> - an optional value parameter (of type *s*tring or *n*umber)</li>
<li><span style="color: blue;"><code>-v</code></span> - a mandatory short flag</li>
<li><span style="color: blue;"><code>[-v]</code></span> - an optional short flag</li>
<li><span style="color: blue;"><code>-p=s</code></span> - a mandatory short value parameter (of type *s*tring or *n*umber)</li>
<li><span style="color: blue;"><code>[-p=s]</code></span> - an optional short value parameter (of type *s*tring or *n*umber)</li>

<h5>Example routes</h5>
<ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
clean cache all
<li>three mandatory literal params - "clean", "cache" and "all"</li>
<li>request must contain all three params or the route will not match</li>
<li>params must exist in the exact order as described</li>
<li>RouteMatch will contain the following: <code>[ "clean" => true, "cache" => true, "all" => true ]</code></li>

<ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
clean directory CACHEDIR
<li>two mandatory literal params - "clean" and "cachedir"</li>
<li>one mandatory value param <code>CACHEDIR</code></li>
<li>request must contain 2 params "clean" and "cachedir"</li>
<li>those 2 params must be followed by a cache dir value</li>
<li>if cache dir is not provided, route will not match</li>
<li>RouteMatch will contain the following: <code>[ "clean" => true, "directory" => true, "cachedir" => "...user supplied.." ]</code></li>

<ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
clean cache [ all | modules | system | misc ]
<li>two mandatory literal params - "clean" and "cache"</li>
<li>third optional literal param, one of: all modules system misc</li>
<li>route will match with just two params</li>
<li>route will match with three params only, if the third param is one of the supplied options</li>
<li>Assuming the third parameter is "system", RouteMatch will contain the following: <code>[ "clean" => true, "cache" => true, "system" => true ]</code></li>

<ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
install ( module | application | misc )
<li>two mandatory literal params</li>
<li>first mandatory literal param must equal to "install"</li>
<li>second literal param must be one of: module application misc</li>
<li>route will not match if there is less than 2 params or if the second one does not match any of the options</li>
<li>Assuming the second parameter is "misc", RouteMatch will contain the following: <code>[ "install" => true, "misc" => true ]</code></li>

<ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
<li>one mandatory literal param "start"</li>
<li>one mandatory value (string) param PROCESS</li>
<li>one optional value (string) param LOCATION</li>
<li>route will not match if there is less than 2 params</li>
<li>route will match if LOCATION is not provided</li>
<li>Assuming "location" has been provided, RouteMatch will contain the following: <code>[ "start" => true, "process" => "...user supplied..", "location" => ".. user supplied.." ]</code></li>

<ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
process files [DIR] [--quick] [--verbose]
<li>two mandatory literal params - "process" and "files"</li>
<li>one optional value param DIR</li>
<li>two optional named params (flags) - "-<span style="text-decoration: line-through;">quick" and "</span>-verbose"</li>
<li>route will not match if there is less than 2 positional parameters</li>
<li>route will match if any of the optional parameters is not set</li>
<li>route will match regardless of named parameters order</li>
<li>Assuming "--quick" and dir has been provided, RouteMatch will contain the following: <code>[ "process" => true, "files" => true, "dir" => "...user supplied..", "quick" => true ]</code></li>

<ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
validate --value=s --validator=s [--verbose]
<li>one mandatory literal param "validate"</li>
<li>two mandatory named params "value" and "validator"</li>
<li>one optional "verbose" flag</li>
<li>route will not match if "value" or "validator" parameter is missing</li>
<li>route will match regardless of parameter order</li>
<li>route will match regardless of presence of "verbose" flag</li>
<li>RouteMatch will contain the following: <code>[ "validate" => true, "value" => "..user supplied...", "validator" => "...user supplied.." ]</code></li>

Enter labels to add to this page:
Please wait 
Looking for a label? Just start typing.
  1. Feb 28, 2012

    <p>I'd like to suggest moving the decorating syntax out of the controller's scope. As with html-over-http, this is handled by a dedicated view layer. The same should hold for cli imho, as the view will determine exactly <strong>what</strong> and <strong>how</strong> something is displayed.</p>

    <p>Further implementation details and consequences not what I am bothering of, it's more the principle.</p>