跳转到内容

命令行应用程序


概览

CLI 是 Command Line Interface(命令行界面)的缩写。CLI 应用程序从命令行或 shell 提示符中执行。CLI 应用程序的一个好处是它们没有视图层(仅可能在屏幕上回显输出),并且可以同时运行多次。一些常见的用途包括定时任务、操作脚本、导入数据脚本、命令工具等。

结构

您可以使用Phalcon\Cli\Console类在 Phalcon 中创建一个 CLI 应用程序。这个类扩展自主要的抽象应用程序类,并使用一个包含Task脚本所在的目录。Task脚本是扩展Phalcon\Cli\Task的类,并包含需要执行的代码。

CLI 应用程序的目录结构可能如下所示:

src/tasks/MainTask.php
php cli.php
在上面的例子中,将cli.php是我们应用程序的入口点,而src/tasks目录包含处理每个命令的所有任务类。

注意

每个任务文件和类必须Task结尾。如果未传递参数,则默认任务是MainTask,而在任务内要执行的默认方法是main

引导程序

正如上面所见,我们 CLI 应用程序的入口点是cli.php。在该脚本中,我们需要引导我们的应用程序并添加相关的服务、指令等。这类似于我们在 MVC 应用程序中使用的熟悉的index.php

<?php

declare(strict_types=1);

use Exception;
use Phalcon\Autoload\Loader;
use Phalcon\Cli\Console;
use Phalcon\Cli\Dispatcher;
use Phalcon\Cli\Console\Exception as PhalconException;
use Phalcon\Di\FactoryDefault\Cli as CliDI;
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);
}
让我们更详细地看看上面的代码。

首先,我们需要为我们的 CLI 应用程序创建所有必要的服务。我们将创建一个加载器来自动加载我们的任务、CLI 应用程序、调度器以及 CLI 控制台应用程序。这些是我们创建 CLI 应用程序所需实例化的最少服务。

加载器

$loader = new Loader();
$loader->setNamespaces(
    [
        'MyApp' => 'src/',
    ]
);
$loader->register();
创建 Phalcon 自动加载器并将命名空间注册到 src/ 目录。

注意

如果您决定在您的composer.json中使用 Composer 自动加载器,则无需在此应用程序中注册加载器。

依赖注入

$container  = new CliDI();
我们需要一个依赖注入容器。您可以使用Phalcon\Di\FactoryDefault\Cli容器,它已经为您注册了服务。或者,您始终可以使用Phalcon\Di并逐一注册您需要的服务。

分派器

$dispatcher = new Dispatcher();

$dispatcher->setDefaultNamespace('MyApp\Tasks');
$container->setShared('dispatcher', $dispatcher);
CLI 应用程序需要特定的调度器。Phalcon\Cli\Dispatcher提供与 MVC 应用程序的主要调度器相同的功能,但它是为 CLI 应用程序量身定制的。如预期的那样,我们实例化调度器对象,设置我们的默认命名空间,然后将其注册到 DI 容器中。

配置

$container->setShared(
    'config', 
    function () {
        return include 'app/config/config.php';
    }
);
上面的代码片段是可选的,但它将允许您访问已设置的任何配置设置。

确保将 include 路径更新为相对于您的cli.php文件的位置。

应用程序

$console = new Console($container);
如上所述,CLI 应用程序由Phalcon\Cli\Console类处理。在这里我们实例化它并将其传递给 DI 容器。

参数

我们的应用程序需要参数。它们以以下形式出现:

php ./cli.php argument1 argument2 argument3 ...
第一个参数与要执行的任务相关。第二个参数是动作,之后跟随我们需要传递的参数。

$arguments = [];
foreach ($argv as $k => $arg) {
    if ($k === 1) {
        $arguments['task'] = $arg;
    } elseif ($k === 2) {
        $arguments['action'] = $arg;
    } elseif ($k >= 3) {
        $arguments['params'][] = $arg;
    }
}
如您在上面看到的,我们使用$argv接收通过命令行传递的内容,并相应地拆分这些参数以理解需要调用哪个任务和动作以及使用哪些参数。

因此对于以下示例:

php ./cli.php users recalculate 10
我们的应用程序将调用UsersTask,调用recalculate动作并传递参数10.

执行

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);
}
在上面的代码中,我们使用我们的控制台对象并调用handle使用计算出的参数。CLI 应用程序将进行必要的路由并分发请求的任务和动作。如果抛出异常,则会被catch语句捕获,并根据相应的错误显示在屏幕上。

异常

Phalcon\Cli\Console组件中抛出的任何异常类型为Phalcon\Cli\Console\Exception中抛出的任何异常都可以让您专门捕获该异常。

任务

任务相当于 MVC 应用程序中的控制器。任何 CLI 应用程序都需要至少一个名为MainTaskmainAction的任务。任何定义的任务都需要有一个mainAction,如果没有定义动作则会调用它。您不受每个任务中动作数量的限制。

一个任务类的示例 (src/Tasks/MainTask.php) 是:

<?php

declare(strict_types=1);

namespace MyApp\Tasks;

use Phalcon\Cli\Task;

class MainTask extends Task
{
    public function mainAction()
    {
        // This is the default task and the default action
        echo '000000' . PHP_EOL;
    }
}
您可以通过扩展提供的Phalcon\Cli\Task或编写自己的类来实现任务,实现Phalcon\Cli\TaskInterface.

动作

如上所述,我们指定了第二个参数为动作。任务可以包含多个动作。

<?php

declare(strict_types=1);

namespace MyApp\Tasks;

use Phalcon\Cli\Task;

class UsersTask extends Task
{
    public function mainAction()
    {
        // This is the default task and the default action
        echo '000000' . PHP_EOL;
    }

    public function regenerateAction(int $count = 0)
    {
        // This is the regenerate action
        echo '111111' . PHP_EOL;
    }
}
然后我们可以调用main动作(默认动作):

./cli.php users
或 regernate 动作:

./cli.php users regenerate

参数

您还可以向动作传递参数。如何处理参数的示例可以在上面的示例引导文件中找到。

<?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;
    }
}
然后我们可以运行以下命令:

php cli.php users add 4 5

9
参数也可以通过Phalcon\Cli\Dispatcher访问,这对传递标志或未知数量的参数时很有帮助。

<?php

declare(strict_types=1);

namespace MyApp\Tasks;

use Phalcon\Cli\Task;

class UsersTask extends Task
{
    public function mainAction()
    {
        print_r( $this->dispatcher->getParams() );
    }

}
运行这行代码将输出:

php cli.php users main additional parameters

Array
(
    [0] => additional
    [1] => parameters
)

链式调用

您还可以链式调用任务。为了按顺序运行它们,我们需要对引导部分进行一个小改动:我们需要将应用程序注册到 DI 容器中:

// ...
$console = new Console($container);
$container->setShared('console', $console);

$arguments = [];
// ...
现在控制台应用程序已在 DI 容器内,我们可以在任何任务中访问它。

假设我们要调用printAction()元素Users任务,我们只需要使用容器调用它。

<?php

namespace MyApp\Tasks;

use Phalcon\Cli\Console;
use Phalcon\Cli\Task;

/**
 * @property Console $console
 */
class UsersTask extends Task
{
    public function mainAction()
    {
        # This is the default task and the default action
        echo '000000' . PHP_EOL;

        # Also handle the `print` action
        $this->console->handle(
            [
                'task'   => 'main',
                'action' => 'print',
            ]
        );
    }

    public function printAction()
    {
        # Print action executed also
        echo '444444' . PHP_EOL;
    }
}
这种技术允许您从任何其他任务中运行任何任务和任何动作。但是不推荐这样做,因为它可能导致维护上的噩梦。最好扩展Phalcon\Cli\Task并在那里实现你的逻辑。

模块

命令行应用程序也可以处理不同的模块,与MVC应用程序相同。你可以在CLI应用程序中注册不同的模块来处理CLI应用程序的不同路径。这允许更好地组织代码并分组任务。

你可以注册一个frontendbackend控制台应用程序的模块如下:

<?php

declare(strict_types=1);

use Exception;
use MyApp\Modules\Backend\Module as BackendModule;
use MyApp\Modules\Frontend\Module as FrontendModule;
use Phalcon\Autoload\Loader;
use Phalcon\Cli\Console;
use Phalcon\Cli\Dispatcher;
use Phalcon\Di\FactoryDefault\Cli as CliDI;
use Phalcon\Exception as PhalconException;
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);
}
上述代码假设你已经将目录结构设置为包含模块在frontendbackend目录中。

src/
src/backend/Module.php
src/frontend/Module.php
php cli.php

方法

CLI应用程序提供了以下方法:

public function getDefaultModule(): string
返回默认的模块名称

public function getModule(string $name): array | object
通过模块名称获取在应用程序中注册的模块定义

public function getModules(): array
返回在应用程序中注册的模块

public function registerModules(array $modules, bool $merge = false): AbstractApplication
注册应用程序中存在的模块数组

public function setDefaultModule(string $defaultModule): AbstractApplication
如果路由器未返回有效的模块,则设置要使用的模块名称

路由

CLI应用程序有自己的路由器。默认情况下,Phalcon CLI应用程序使用Phalcon\Cli\Router object, but you can implement your own by using the Phalcon\Cli\RouterInterface.

Default Routes

Similar to an MVC application, the Phalcon\Cli\Router使用Phalcon\Cli\Router\Route对象来存储路由信息。你始终可以通过实现Phalcon\Cli\Router\RouteInterface.

这些路由支持正则表达式参数,例如a-zA-Z0-9,还提供额外的占位符:

占位符 描述
:module 模块(需要先设置模块)
:task 任务名称
:namespace 命名空间名称
:action 动作
:params 任何参数
:int 是否此为整数路由参数

默认路由是:

  • /:task/:action
  • /:task/:action/:params

如果你不希望使用默认路由,可以通过传递false在构造时禁用它们Phalcon\Cli\Router对象:

<?php

declare(strict_types=1);

use Phalcon\Cli\Router;

$router = new Router(false);

关于路由和路由类的更多详细信息,可以参考路由页面上找到。

事件

Phalcon中的CLI应用程序是支持事件的,允许你使用setEventsManagergetEventsManager方法访问事件管理器。以下是可用的事件:

事件 停止 描述
afterHandleTask 在任务处理后调用。它允许你在任务执行后执行操作。
afterStartModule 在处理模块后调用(如果使用了模块)。适用于模块执行后的后处理任务。
beforeHandleTask 在任务处理前调用。它提供了在任务执行前执行操作的机会。
beforeStartModule 在处理模块前调用(如果使用了模块)。适用于模块执行前的预处理任务。
boot 在应用程序启动时调用。适用于在应用程序引导过程中执行操作。

如果你使用的是Phalcon\Cli\Dispatcher,你还可以利用beforeException事件,该事件可以从调度器对象触发并停止操作。

这些事件为CLI应用程序生命周期的不同阶段提供了钩子,使你能够在应用程序流程的特定点执行自定义逻辑。

无噪 Logo
无噪文档
25 年 6 月翻译
文档源↗