We have moved our forum to GitHub Discussions. For questions about Phalcon v3/v4/v5 you can visit here and for Phalcon v6 here.

CLI routing example?

I haven't been able to find any examples of what I'm looking to do... I'm fairly familiar with the MVC routing, but this has me baffled.

cli.php

<?php
$di = new Phalcon\Di\FactoryDefault\Cli();
$loader = new Phalcon\Loader();
$loader->registerDirs(array(
    __DIR__ . "/tasks",
));
$loader->register();

$console = new Phalcon\Cli\Console();
$di->setShared('router', function() {
    $router = new \Phalcon\Cli\Router();
    $router->add("/:task/:action/params", array(
        'task' => 1,
        'action' => 2,
        'params' => 3
    ));
    return $router;
});
$console->setDI($di);
try {
    $console->handle($_SERVER['argv']);
} catch (Exception $e) {
    $exceptionLogger = new Phalcon\Logger\Adapter\Stream('php://stdout');
    $exceptionLogger->critical($e->getMessage() . $e->getTraceAsString());
}

tasks/MainTask.php

<?php

class MainTask extends AbstractTask {
    public function testAction() {
        echo "Yay!\n"
    }
}
[email protected]:~/code/cli-proj/app$ php cli.php main test
[Mon, 10 Apr 17 17:38:34 -0400][CRITICAL] Action 'main' was not found on handler 'main'#0 [internal function]: Phalcon\Cli\Dispatcher->_throwDispatchException('Action 'main' w...', 5)
#1 [internal function]: Phalcon\Dispatcher->_dispatch()
#2 [internal function]: Phalcon\Dispatcher->dispatch()
#3 /home/eellis/code/php/bones/cli.php(20): Phalcon\Cli\Console->handle(Array)
#4 {main}
[email protected]:~/code/cli-proj/app$


8.7k

My CLI doesn't use the router at all. This works for me:

// Create a console application
$console = new Phalcon\CLI\Console($di);

// Process the console arguments
$arguments = [];
foreach ($argv as $k => $arg) {

    if($k === 1)  $arguments['task'] = $arg;
    else if($k === 2)  $arguments['action'] = $arg;
    else if($k >= 3)  $arguments['params'][] = $arg;
}

// Define global constants for the current task and action
define('CURRENT_TASK',   (isset($argv[1]) ? $argv[1] : null));
define('CURRENT_ACTION', (isset($argv[2]) ? $argv[2] : null));

// Handle incoming arguments
$console->handle($arguments);


3.4k
edited Apr '17

I find it easier to use the "arguments based routing" instead of setting up routes like you've done as it can get pretty bloated when you don't need the custom routes for a command line.

  • Read this StackOverflow answer (self promote) on how to set up your CLI with tasks and how to call them.

Directory set-up

You will need to create a tasks/ directory in app/, then you can make appropriate file names; for example;

app/
├── tasks/
│   ├── SomeTask.php
│   ├── SomeOtherTask.php
│
├── cli.php

Now, you'd need to create a cli.php file, which your command line will call. In here, you'd need to look at the call arguments and pass it in your $console->handle() method. For example;

cli.php

<?php

use Phalcon\Di\FactoryDefault\Cli as CliDI;
use Phalcon\Cli\Console as ConsoleApp;
use Phalcon\Loader;

// Using the CLI factory default services container
$di = new CliDI();

$arrBasePath = preg_split("/\//", __DIR__);
$limit = sizeof($arrBasePath);

$strBasePath = "";
for ($count = 0; $count < $limit - 1; $count++) {
    $strBasePath .= $arrBasePath[$count] . '/';
}

define('BASE_PATH', $strBasePath);

/**
 * Register the autoloader and tell it to register the tasks directory
 */
$loader = new Loader();

$loader->registerDirs(
    [
        __DIR__ . "/tasks",
        __DIR__ . "/models",
        __DIR__ . "/controllers"
    ]
);

$loader->register();

// Create a console application
$console = new ConsoleApp();
$console->setDI($di);

//Process the console arguments
$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 {
    // Handle incoming arguments
    $console->handle($arguments);
} catch (\Phalcon\Exception $e) {
    echo $e->getMessage();
    exit(255);
}

Now, say in app/tasks/SomeTask.php you have the following;

app/tasks/SomeTask.php

<?php

use Phalcon\Cli\Task;

class SomeTask extends Task
{

    public function MainAction()
    {
        echo "This is the main action\r\n";
    }

    public function OtherAction()
    {
        echo "This is other action\r\n";
    }

}

You can just call your tasks by passing them in the arguments, for example;

$ php cli.php some
This is the main action

Or, call the other method by passing it;

$ php cli.php some other
This is other action

Notes

  • Your class name needs to end in Task. When calling this class, you omit it. See how I called the someTask class.


7.5k
edited Apr '17

@409H and @tkroll when you pass arguments to your tasks, you list or array_shift them off as needed?

--edit

Part of the reason I'm looking at this routing route is because it makes for less congative load for my team. We're already accustomed to heavily managing routes in our MVC app, so mimicing that code over here becomes advantageous.



3.4k

What do you mean?

We don't alter the argument input at all, we build a ["task" => "", "action" => "", "params" => [] ] array from the arguments ($argv) and pass it to $console->handle() to do its magic.

@409H and @tkroll when you pass arguments to your tasks, you list or array_shift them off as needed?



7.5k

What do you mean?

We don't alter the argument input at all, we build a ["task" => "", "action" => "", "params" => [] ] array from the arguments ($argv) and pass it to $console->handle() to do its magic.

@409H and @tkroll when you pass arguments to your tasks, you list or array_shift them off as needed?

In many places in our MVC application, we have actions that look like this:

...
public function fooAction($bar, $baz = null) {
    // logic around $baz not being null.
}

I'd like to have that flexibility in my CLI work as well.

I'm completely open to criticism on this approach, as long as there's sound logic and a reasonable alternative offered. :)



3.4k
edited Apr '17

Oh, I understand.

The method I've done above allows for parameters to be passed to methods, but as a single parameter - see docs: https://docs.phalcon.io/en/3.0.1/reference/cli.html#processing-action-parameters

For example, our SomeTask.php file holds this;

<?php

use Phalcon\Cli\Task;

class SomeTask extends Task
{

    public function MainAction()
    {
        echo "This is the main action\r\n";
    }

    public function OtherAction($bar)
    {
        echo "\r\nThis is other action\r\n";
        echo print_r($bar, true);
        echo "\r\n";
    }

}

And below is the output from various calls;

$ php cli.php some other foo

This is other action
Array
(
    [0] => foo
)

---------------------------------------------------------

$ php cli.php some other foo bar

This is other action
Array
(
    [0] => foo
    [1] => bar
)

---------------------------------------------------------

$ php cli.php some other foo bar baz

This is other action
Array
(
    [0] => foo
    [1] => bar
    [2] => baz
)

So you could do something like;

    //...
    public function OtherAction(array $params)
    {
        $bar = $params[0];
        $baz = isset($params[1]) ? $params[1] : null;

        //Logic.
    }
    //...

What do you mean?

We don't alter the argument input at all, we build a ["task" => "", "action" => "", "params" => [] ] array from the arguments ($argv) and pass it to $console->handle() to do its magic.

@409H and @tkroll when you pass arguments to your tasks, you list or array_shift them off as needed?

In many places in our MVC application, we have actions that look like this:

...
public function fooAction($bar, $baz = null) {
  // logic around $baz not being null.
}

I'd like to have that flexibility in my CLI work as well.

I'm completely open to criticism on this approach, as long as there's sound logic and a reasonable alternative offered. :)