Aplicación CLI

Resumen
CLI significa Interfaz de Línea de Comandos (Command Line Interface en inglés). Las aplicaciones CLI se ejecutan desde la línea de comandos o un indicador de shell. Uno de los beneficios de las aplicaciones CLI es que no tienen una capa de vista (sólo potencialmente muestran la salida por pantalla) y se pueden ejecutar más de una vez al mismo tiempo. Some common usages are cron job tasks, manipulation scripts, import data scripts, command utilities and more.
Estructura
You can create a CLI application in Phalcon, using the Phalcon\Cli\Console class. Esta clase extiende desde la clase abstracta de aplicación, y usa un directorio en el que se localizan los scripts de Tareas. Task scripts are classes that extend Phalcon\Cli\Task and contain the code that we need executed.
La estructura del directorio de una aplicación CLI puede parecerse a:
src/tasks/MainTask.php
php cli.php
En el ejemplo anterior, cli.php
es el punto de entrada a nuestra aplicación, mientras que el directorio src/tasks
contiene todas las clases de tareas que manejan cada comando.
NOTE: Each task file and class must be suffixed with Task
. The default task (if no parameters have been passed) is MainTask
and the default method to be executed inside a task is main
Manos a la obra
Como se ha visto anteriormente, el punto de entrada de nuestra aplicación CLI es cli.php
. En ese script, necesitamos arrancar nuestra aplicación con servicios relevantes, directivas, etc. Esto es similar al familiar index.php
que usamos para las aplicaciones MVC.
<?php
declare(strict_types=1);
use Exception;
use Phalcon\Cli\Console;
use Phalcon\Cli\Dispatcher;
use Phalcon\Cli\Console\Exception as PhalconException;
use Phalcon\Di\FactoryDefault\Cli as CliDI;
use Phalcon\Loader\Loader;
use Throwable;
$loader = new Loader();
$loader->setNamespaces(
[
'MyApp' => 'src/',
]
);
$loader->register();
$container = new CliDI();
$dispatcher = new Dispatcher();
$dispatcher->setDefaultNamespace('MyApp\Tasks');
$container->setShared('dispatcher', $dispatcher);
$container->setShared('config', function () {
return include 'app/config/config.php';
});
$console = new Console($container);
$arguments = [];
foreach ($argv as $k => $arg) {
if ($k === 1) {
$arguments['task'] = $arg;
} elseif ($k === 2) {
$arguments['action'] = $arg;
} elseif ($k >= 3) {
$arguments['params'][] = $arg;
}
}
try {
$console->handle($arguments);
} catch (PhalconException $e) {
fwrite(STDERR, $e->getMessage() . PHP_EOL);
exit(1);
} catch (Throwable $throwable) {
fwrite(STDERR, $throwable->getMessage() . PHP_EOL);
exit(1);
} catch (Exception $exception) {
fwrite(STDERR, $exception->getMessage() . PHP_EOL);
exit(1);
}
Veamos el código anterior en más detalle.
Primero necesitamos crear todos los servicios necesarios para nuestra aplicación CLI. Vamos a crear un cargador para autocargar nuestras tareas, la aplicación CLI, un despachador y una aplicación de Consola CLI. Estos son la cantidad mínima de servicios que necesitamos instanciar para crear una aplicación CLI.
Loader
$loader = new Loader();
$loader->setNamespaces(
[
'MyApp' => 'src/',
]
);
$loader->register();
Crea el autocargador de Phalcon y registra el espacio de nombres para apuntar al directorio src/
.
NOTE: If you decided to use the Composer autoloader in your composer.json
, you do not need to register the loader in this application
DI
$container = new CliDI();
Necesitamos un contenedor de Inyección de Dependencias. You can use the Phalcon\Di\FactoryDefault\Cli container, which already has services registered for you. Alternatively, you can always use the Phalcon\Di and register the services you need, one after another.
Dispatcher
$dispatcher = new Dispatcher();
$dispatcher->setDefaultNamespace('MyApp\Tasks');
$container->setShared('dispatcher', $dispatcher);
Las aplicaciones CLI necesitan un despachador específico. Phalcon\Cli\Dispatcher offers the same functionality as the main dispatcher for MVC applications, but it is tailored to CLI applications. Como era de esperar, instanciamos el objeto despachador, establecemos nuestro espacio de nombres por defecto y luego lo registramos en el contenedor DI.
Configuración
$container->setShared('config', function () {
return include 'app/config/config.php';
});
El fragmento anterior es opcional, pero le permitirá acceder a cualquier configuración que haya configurado.
Asegúrese de actualizar la ruta de inclusión para estar relativa a donde está su archivo cli.php
.
Application
$console = new Console($container);
As mentioned above, a CLI application is handled by the Phalcon\Cli\Console class. Aquí la instanciamos y pasamos al contenedor DI.
Argumentos
Our application needs arguments. Estos vienen de la siguiente forma:
php ./cli.php argument1 argument2 argument3 ...
El primer argumento es relativo a la tarea a ejecutar. El segundo es la acción y después le siguen los parámetros que necesitamos pasar.
$arguments = [];
foreach ($argv as $k => $arg) {
if ($k === 1) {
$arguments['task'] = $arg;
} elseif ($k === 2) {
$arguments['action'] = $arg;
} elseif ($k >= 3) {
$arguments['params'][] = $arg;
}
}
Como se puede ver arriba, usamos $argv
para recibir lo que nos han pasado por línea de comandos, y dividimos esos argumentos apropiadamente para entender qué tarea y acción se necesita invocar y con qué parámetros.
Así para el siguiente ejemplo:
php ./cli.php users recalculate 10
Nuestra aplicación invocará UsersTask
, llamará la acción recalculate
y pasará el parámetro 10
.
Ejecución
try {
$console->handle($arguments);
} catch (PhalconException $e) {
fwrite(STDERR, $e->getMessage() . PHP_EOL);
exit(1);
} catch (Throwable $throwable) {
fwrite(STDERR, $throwable->getMessage() . PHP_EOL);
exit(1);
} catch (Exception $exception) {
fwrite(STDERR, $exception->getMessage() . PHP_EOL);
exit(1);
}
En el código anterior, usamos el objeto consola y llamamos handle
con los parámetros calculados. La aplicación CLI hará el enrutado necesario y lanzará la tarea y acción solicitada. Si se lanza una excepción, será capturada por las sentencias catch
y en consecuencia los errores se mostrarán por pantalla.
Excepciones
Any exception thrown in the Phalcon\Cli\Console component will be of type Phalcon\Cli\Console\Exception, which allows you to trap the exception specifically.
Tareas
Tasks are the equivalent of controllers in an MVC application. Cualquier aplicación CLI necesita al menos una tarea llamada MainTask
y un mainAction
. Cualquier tarea definida necesita tener un mainAction
que se llamará si no se define ninguna acción. No hay restricción en el número de acciones que cada tarea puede contener.
Un ejemplo de una clase de tarea (src/Tasks/MainTask.php
) es:
<?php
declare(strict_types=1);
namespace MyApp\Tasks;
use Phalcon\Cli\Task;
class MainTask extends Task
{
public function mainAction()
{
// #01
echo '000000' . PHP_EOL;
}
}
Legend
01: This is the default task and the default action
You can implement your own tasks by either extending the supplied Phalcon\Cli\Task or writing your own class implementing the Phalcon\Cli\TaskInterface.
Acciones
Como se observa arriba, especificamos el segundo parámetro para ser la acción. La tarea puede contener más de una acción.
<?php
declare(strict_types=1);
namespace MyApp\Tasks;
use Phalcon\Cli\Task;
class UsersTask extends Task
{
public function mainAction()
{
// #01
echo '000000' . PHP_EOL;
}
public function regenerateAction(int $count = 0)
{
// #01
echo '111111' . PHP_EOL;
}
}
Legend
01: This is the default task and the default action
02: This is the regenerate action
Podemos llamar a la acción main
(acción predeterminada):
o la acción regenerate
:
./cli.php users regenerate
Parámetros
También puede pasar parámetros a la acción. Un ejemplo de cómo procesar los parámetros se puede encontrar arriba, en el ejemplo del fichero de arranque.
<?php
declare(strict_types=1);
namespace MyApp\Tasks;
use Phalcon\Cli\Task;
class UsersTask extends Task
{
public function mainAction()
{
echo '000000' . PHP_EOL;
}
public function addAction(int $first, int $second)
{
echo $first + $second . PHP_EOL;
}
}
Legend
01: This is the default task and the default action
Entonces podemos ejecutar el siguiente comando:
php cli.php users add 4 5
9
Parameters can also be accessed through the Phalcon\Cli\Dispatcher which is helpful when passing flags in, or an unknown number of parameters.
<?php
declare(strict_types=1);
namespace MyApp\Tasks;
use Phalcon\Cli\Task;
class UsersTask extends Task
{
public function mainAction()
{
print_r( $this->dispatcher->getParams() );
}
}
Ejecutar esto entonces mostrará:
php cli.php users main additional parameters
Array
(
[0] => additional
[1] => parameters
)
Cadena
También puede encadenar tareas. Para ejecutarlas una tras otra, necesitamos hacer un pequeño cambio en nuestro arranque: necesitamos registrar nuestra aplicación en el contenedor DI:
// ...
$console = new Console($container);
$container->setShared('console', $console);
$arguments = [];
// ...
Ahora que la aplicación de consola está en el contenedor DI, podemos acceder a ella desde cualquier tarea.
Assume we want to call the printAction()
from the Users
task, all we have to do is call it, using the container.
<?php
namespace MyApp\Tasks;
use Phalcon\Cli\Console;
use Phalcon\Cli\Task;
/**
* @property Console $console
*/
class UsersTask extends Task
{
public function mainAction()
{
# 01
echo '000000' . PHP_EOL;
# 02
$this->console->handle(
[
'task' => 'main',
'action' => 'print',
]
);
}
public function printAction()
{
# 03
echo '444444' . PHP_EOL;
}
}
Legend
01: This is the default task and the default action
02: Also handle the print
action
03: Print action executed also
Esta técnica le permite ejecutar cualquier tarea y cualquier acción desde cualquier otra tarea. Sin embargo, no es recomendable porque podría provocar pesadillas de mantenimiento. It is better to extend Phalcon\Cli\Task and implement your logic there.
Módulos
Las aplicaciones CLI también puede gestionar diferentes módulos, igual que en aplicaciones MVC. Puede registrar diferentes módulos en su aplicación CLI, para manejar diferentes rutas de su aplicación CLI. Esto permite una mejor organización de su código y la agrupación de tareas.
Puede registrar un módulo frontend
y backend
para su aplicación de consola como sigue:
<?php
declare(strict_types=1);
use Exception;
use MyApp\Modules\Backend\Module as BackendModule;
use MyApp\Modules\Frontend\Module as FrontendModule;
use Phalcon\Cli\Console;
use Phalcon\Cli\Dispatcher;
use Phalcon\Di\FactoryDefault\Cli as CliDI;
use Phalcon\Exception as PhalconException;
use Phalcon\Loader\Loader;
use Throwable;
$loader = new Loader();
$loader->setNamespaces(
[
'MyApp' => 'src/',
]
);
$loader->register();
$container = new CliDI();
$dispatcher = new Dispatcher();
$dispatcher->setDefaultNamespace('MyApp\Tasks');
$container->setShared('dispatcher', $dispatcher);
$console = new Console($container);
$console->registerModules(
[
'frontend' => [
'className' => BackendModule::class,
'path' => './src/frontend/Module.php',
],
'backend' => [
'className' => FrontendModule::class,
'path' => './src/backend/Module.php',
],
]
);
$arguments = [];
foreach ($argv as $k => $arg) {
if ($k === 1) {
$arguments['task'] = $arg;
} elseif ($k === 2) {
$arguments['action'] = $arg;
} elseif ($k >= 3) {
$arguments['params'][] = $arg;
}
}
try {
$console->handle($arguments);
} catch (PhalconException $e) {
fwrite(STDERR, $e->getMessage() . PHP_EOL);
exit(1);
} catch (Throwable $throwable) {
fwrite(STDERR, $throwable->getMessage() . PHP_EOL);
exit(1);
} catch (Exception $exception) {
fwrite(STDERR, $exception->getMessage() . PHP_EOL);
exit(1);
}
El código anterior asume que ha estructurado sus directorios para contener los módulos en los directorios frontend
y backend
.
src/
src/backend/Module.php
src/frontend/Module.php
php cli.php
Métodos
La aplicación CLI ofrece los siguientes métodos:
public function getDefaultModule(): string
Devuelve el nombre de módulo por defecto
public function getModule(string $name): array | object
Obtiene la definición de módulo registrada en la aplicación a través del nombre del módulo
public function getModules(): array
Devuelve los módulos registrados en la aplicación
public function registerModules(array $modules, bool $merge = false): AbstractApplication
Registra un vector de módulos presente en la aplicación
public function setDefaultModule(string $defaultModule): AbstractApplication
Sets the module name to be used if the router does not return a valid module
Rutas
La aplicación CLI tiene su propio router. By default, the Phalcon CLI application uses the Phalcon\Cli\Router object, but you can implement your own by using the Phalcon\Cli\RouterInterface.
Similar to an MVC application, the Phalcon\Cli\Router uses Phalcon\Cli\Router\Route objects to store the route information. You can always implement your own objects by implementing the Phalcon\Cli\Router\RouteInterface.
Las rutas aceptan los parámetros regex esperados como a-zA-Z0-9
etc. También hay comodines adicionales que puede aprovechar:
Marcador |
Descripción |
:module |
El módulo (necesita configurar los módulos primero) |
:task |
El nombre de la tarea |
:namespace |
El nombre del espacio de nombres |
:action |
La acción |
:params |
Cualquier parámetros |
:int |
Si es un parámetro entero de ruta |
The Phalcon\Cli\Router comes with two predefined routes, so that it works right out of the box. Estas son:
/:task/:action
/:task/:action/:params
If you do not wish to use the default routes, all you have to do is pass false
in the Phalcon\Cli\Router object upon construction.
<?php
declare(strict_types=1);
use Phalcon\Cli\Router;
$router = new Router(false);
Para más información sobre rutas y clases de rutas, puede consultar la página Enrutamiento.
Eventos
Las aplicaciones CLI también consideran los eventos. Puede usar los métodos setEventsManager
y getEventsManager
para acceder al gestor de eventos.
Los siguientes eventos están disponibles:
Evento |
Detiene |
Descripción |
afterHandleTask |
Si |
Llamado después de gestionar la tarea |
afterStartModule |
Si |
Llamado después de procesar un módulo (si se usan módulos) |
beforeHandleTask |
No |
Llamado antes de gestionar la tarea |
beforeStartModule |
Si |
Llamado antes de procesar un módulo (si se usan módulos) |
boot |
Si |
Llamado cuando al aplicación arranca |
If you use the Phalcon\Cli\Dispatcher you can also take advantage of the beforeException
event, which can stop operations and is fired from the dispatcher object.