Secciones

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):

./cli.php users 

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.