zendframework/zf2 — pull request #1945 by Thinkscape (2012-07-21 13:12:57+00:00)
Title: [WIP] Console support

This PR contains all necessary changes and enhancements to support zf2, console-based MVC applications. **Work In Progress - please do not attempt to merge** This PR description will also be updated to include clarifications and more info as requested by the community.

## Theory of operation ##

* All console related components are grouped in the `Zend\Console` namespace.
* `Zend\Mvc` and `Zend\View` have been enhanced to support console requests.
* When `Zend\Application` is run from a console environment (i.e. in a shell or command prompt window) it will switch from http logic to console logic.
* In this environment, the equivalent of a *Http request* (get/post/put/env etc.) is a *Console request* (containing script name, command-line parameters and environment information).
* The request is passed to a console-aware *Router*.
* *Console Router* operates and analyzes command-line parameters, parsing them and matching against a *console route definition*
* *Console Router* config is loaded from `$config['console']['router']`.
* Routes should be defined in `$config['console']['router']['routes']`.
* An example of a console route could be: `module install [--verbose|-v] [--fast] [--dry]`
* This route expects 2 constant arguments `module install`.
* After that, a single variable argument ``.
* An optional `--verbose` argument (or its shorthand `-v`)
* An optional `--fast` argument.
* An optional `--dry` argument
* When a console route matches it will behave analogical to an http route - trying to dispatch the request with an action controller, obtained from *Service Manager*.
* Action controller can handle a request in several ways:
1. It can directly retrieve a `Console\Adapter` from Service Manager - it allows reading from and writing to console window, bypassing the MVC layer and enabling interactivity with the user.
2. It can return a `View\Model\ConsoleModel` instance that will be shown in console window
3. It can return a `string` that will be show in console window.

## Standard console request flow ##
1. We are assuming zf2 application is run in a console window.
1. The entry point could be any php script. A standard [public/index.php](https://github.com/zendframework/ZendSkeletonApplication/blob/master/public/index.php) will work just fine.
1. **Zend\Application::init()** - **Zend\Application::boostrap()**
1. `ResponseFactory` creates and returns `Console\Response`
1. `RequestFactory` creates and returns `Console\Request`
1. `Console\Request` by default loads parameters from `global $argv` and environment information from `$_ENV`.
1. `RouterFactory` creates and returns `Mvc\Router\Console\SimpleRouteStack` with configuration loaded from `$config['console']['router']`
1. `ViewManagerFactory` creates and returns `Mvc\View\Console\ViewManager`
1. **Zend\Application::run()**
1. **$events->trigger(MvcEvent::EVENT_ROUTE ... ) - routing phase**
1. `Console\Request` is routed
1. User-supplied command-line arguments are matched against defined routes.
1. The first route that matches, wins.
1. Matching parameters are stored in `RouteMatch` objects.
1. **$events->trigger(MvcEvent::EVENT_DISPATCH ... ) - dispatching phase**
1. In case any route matches, an action controller is loaded and invoked.
1. If the action returns a `string`: `Mvc\View\Console\CreateViewModelListener` will transform the string into a `View\Model\ConsoleModel` object.
1. If the action returns `Console\Response` - the response will immediately be shown in console window.
1. If the action returns `View\Model\ConsoleModel` it will not be transformed.
1. **$events->trigger(MvcEvent::EVENT_RENDER, $event) - rendering phase**
1. NOTE: Current version does not perform any form of rendering/transformation on the result coming from action controller. There is no support for templating or markup. This is planned and will be discussed soon.
1. If the resulting `View\Model\ConsoleModel` contains children (i.e. several actions provided some text), results will be concatenated.
1. `Mvc\View\Console\DefaultRenderingStrategy` transforms `View\Model\ConsoleModel` into `Console\Response`.
1. **$events->trigger(MvcEvent::EVENT_FINISH, $event); - output phase**
1. `Mvc\View\SendResponseListener` invokes `Console\Response::send()`
1. Result is echoed to console window
1. `exit($statusCode)`.

## Route not found flow ##
In case no arguments have been provided and/or no console route matched user-provided arguments, the following will happen:

1. *[no changes up to here]*
1. **Zend\Application::run()**
1. **$events->trigger(MvcEvent::EVENT_ROUTE ... ) - routing phase**
1. `Console\Request` is routed
1. User-supplied command-line arguments are matched against defined routes.
1. **No route can be matched**
1. **$events->trigger(MvcEvent::EVENT_ROUTE ... ) - dispatch error occured**
1. `Mvc\View\Console\RouteNotFoundStrategy::handleRouteNotFoundError()`
1. `View\Model\ConsoleModel` is created with `errorLevel = 1` (which on most OS means application error)
1. Create a *console banner*:
1. Go through all `$moduleManager->getLoadedModules()`
1. For all modules that implement `ModuleManager\Feature\ConsoleBannerProviderInterface` call method `::getConsoleBanner()` that is expected to return a string.
1. All banners are joined together with a newline `\n`.
1. In case there are no banners `return "Zend Framework ".Version::VERSION." application.";`
1. Create *console usage information*:
1. Go through all `$moduleManager->getLoadedModules()`
1. For all modules that implement `ModuleManager\Feature\ConsoleUsageProviderInterface` call method `::getConsoleUsage()` that is expected to return a string or array
1. If an array is returned, its elements are analyzed. Subarrays are treated as commands/arguments with descriptions, strings are used as they are.
1. Lists of parameters are automatically formatted, aligned in columns that fit inside current console window.
1. Banner and usage info is concatenated and injected into console view model.
1. *[no changes from here]*

## Possible BC breaks ##

### Introduction of Mvc\View\SendResponseListener ####
This deprecates the following invocation method used in ZendSkeleton:
`````php
$application->init()->run()->send(); // <- deprecated
````
In favor of
`````php
$application->init()->run();
````

The entry point, init script (i.e. `index.php`) should not decide if the response should be sent or not. The ability to martial response sending or not-sending should be in the hands of developers, module authors. It must be delegated to mvc listeners. Furthermore, `send()` method is not a standard response interface method and might not be implemented by all response subclasses. More detailed explanation in the following commit message: https://github.com/Thinkscape/zf2/commit/37747bd608d9b75dc340c4ff8b991ff3706dd4de

`Mvc\View\SendResponseListener` has been introduced to fix this. It is attached by `ViewManager` to `MvcEvent::EVENT_FINISH` and will invoke `->send()` method on response object, if possible.

### Refactor of Mvc\View namespace ###
All Http-related classes have been moved to `Mvc\View\Http` namespace. This might break some modules or custom listeners that were subclassing them.
This change *does NOT break* SM aliases. The following SM aliases are still valid:

* `404Strategy`
* `DefaultRenderingStrategy`
* `ExceptionStrategy`
* `RouteNotFoundStrategy`
* `Zend\Mvc\View\ExceptionStrategy`
* `Zend\Mvc\View\RouteNotFoundStrategy`
* `Zend\Mvc\View\DefaultRenderingStrategy`

NOTE: Those aliases will point to classes relevant to current environment. For example, in http requests `DefaultRenderingStrategy` will point to an instance of `Mvc\View\Http\DefaultRenderingStrategy`, while in console requests it will be `Mvc\View\Console\DefaultRenderingStrategy`

The following new aliases have been created for clarity (available in http requests):

* `Zend\Mvc\View\Http\DefaultRenderingStrategy`
* `Zend\Mvc\View\Http\ExceptionStrategy`
* `Zend\Mvc\View\Http\RouteNotFoundStrategy`
* `Zend\Mvc\View\Console\DefaultRenderingStrategy`
* `Zend\Mvc\View\Console\ExceptionStrategy`
* `Zend\Mvc\View\Console\RouteNotFoundStrategy`