Phalcon搭建多模块框架二十九:创建多模块命令行应用

前二十八篇文章已经创建了一个完整的web多模块应用,但项目中往往会用到一些需要在后台执行的脚本,这就用到了命令行应用(CLI应用)。这样就可以很方便的在脚本中使用很多服务。
phalcon的命令行应用与web应用相似,分为单模块和多模块。这次创建的是多模块命令行应用。
这里写图片描述
1、为了代码复用性,我们需要对public/index.php进行修改,将定义常量单独提取出来,放入define.php文件中。

<?php
/**
 * @desc 入口文件
 * @author zhaoyang
 * @date 2018年5月3日 下午5:16:27
 */
use \Phalcon\Mvc\Application;

// 检查版本,搭建用到php7一些新特性
version_compare(PHP_VERSION, '7.0.0', '>') || exit('Require PHP > 7.0.0 !');
extension_loaded('phalcon') || exit('Please open the Phalcon extension !');

// 引入自定义常量文件
require '../config/define.php';

version_compare(PHP_VERSION, '3.0.0', '>') || exit('Require Phalcon > 3.0.0 !');

// 设置时区
date_default_timezone_set('Asia/Shanghai');

// error_reporting(E_ALL & ~E_NOTICE);

try {
    // 引入配置文件
    $config = require BASE_PATH . 'config/config_' . NOW_ENV . '.php';

    // 引入自动加载配置
    require BASE_PATH . 'config/loader.php';

    // 引入路由规则
    $routerRules = require BASE_PATH . 'config/routers.php';

    // 引入注册服务
    require BASE_PATH . 'config/services.php';

    // 处理请求
    $application = new Application($di);

    // 组装应用程序模块
    $modules = [ ];
    foreach (MODULE_ALLOW_LIST as $v) {
        $modules[$v] = [ 
            'className' => APP_NAMESPACE . '\\' . ucfirst($v) . '\Module',
            'path' => APP_PATH . $v . '/Module.php'
        ];
    }
    // 加入模块分组配置
    $application->registerModules($modules);

    // 输出请求内容
    echo $application->handle()->getContent();
} catch (\Throwable $e) {
    $previous = $e->getPrevious();
    $applicationConfig = $application->config->application;
    if ($applicationConfig->debug->state ?? false) {
        if (empty($applicationConfig->debug->path)) {
            echo 'Exception: <br/>', '所在文件:', $e->getFile(), '<br/>所在行:', $e->getLine(), '<br/>错误码:', $e->getCode(), '<br/>错误消息:', $e->getMessage();
            if (!is_null($previous)) {
                echo '<br/>前一个Exception: <br/>', '所在文件:', $previous->getFile(), '<br/>所在行:', $previous->getLine(), '<br/>错误码:', $previous->getCode(), '<br/>错误消息:', $previous->getMessage();
            }
            exit();
        }
        $errorFile = $applicationConfig->debug->path;
        $errorType = 'debug';
    } else {
        $errorFile = $applicationConfig->error->path;
        $errorType = 'error';
    }
    $errorMessage = 'Exception: [所在文件:' . $e->getFile() . '] [所在行:' . $e->getLine() . '] [错误码:' . $e->getCode() . '] [错误消息:' . $e->getMessage() . '] '/*  . PHP_EOL . '[异常追踪信息:' . $e->getTraceAsString() . ']' */;
    if (!is_null($previous)) {
        $errorMessage .= '  前一个Exception: [所在文件:' . $previous->getFile() . '] [所在行:' . $previous->getLine() . '] [错误码:' . $previous->getCode() . '] [错误消息:' . $previous->getMessage() . '] '/*  . PHP_EOL . '[异常追踪信息:' . $previous->getTraceAsString() . ']' */;
    }
    $application->di->get('logger', [$errorFile])->$errorType($errorMessage);
}

2、在config下新建define.php文件

<?php
// phalcon版本
define('PHALCON_VERSION', Phalcon\Version::get());

//重新命名文件分隔符,建议路径后面加上分隔符
define('DS', DIRECTORY_SEPARATOR);

// 应用程序名称(应用程序所在目录名)
define('APP_NAME', 'app');

// 顶级命名空间
define('APP_NAMESPACE', 'App');

// 项目根目录
define('BASE_PATH', dirname(__DIR__) . DS);

// 应用程序所在目录
define('APP_PATH', BASE_PATH . APP_NAME . DS);

// 模块列表
// @formatter:off
define('MODULE_ALLOW_LIST', ['home', 'admin']);
// @formatter:on

// 默认模块
define('DEFAULT_MODULE', 'home');

// 默认模块命名空间
define('DEFAULT_MODULE_NAMESPACE', APP_NAMESPACE . '\Home');

// 默认使用的配置文件名
define('NOW_ENV', 'dev');

3、创建cli目录,并创建cli/cli.php命令行入口文件

<?php
/**
 * @desc 命令行入口文件
 * @author: ZhaoYang
 * @date: 2018年6月17日 下午5:42:16
 */
use Phalcon\Cli\Console;

// 检查版本,搭建用到php7一些新特性
version_compare(PHP_VERSION, '7.0.0', '>') || exit('Require PHP > 7.0.0 !');
extension_loaded('phalcon') || exit('Please open the Phalcon extension !');

// 引入自定义常量文件
require '../config/define.php';

version_compare(PHP_VERSION, '3.0.0', '>') || exit('Require Phalcon > 3.0.0 !');

// 设置时区
date_default_timezone_set('Asia/Shanghai');

// error_reporting(E_ALL & ~E_NOTICE);

try {
    // 引入配置文件
    $config = require BASE_PATH . 'config/config_' . NOW_ENV . '.php';

    // 引入自动加载配置
    require BASE_PATH . 'config/loader.php';

    // 引入路由规则
    $routerRules = require BASE_PATH . 'cli/config/routers.php';

    // 引入注册服务
    require BASE_PATH . 'cli/config/services.php';

    $arguments = $argv;

    // 处理请求
    $console = new Console($di);

    // 组装应用程序模块
    $modules = [ ];
    foreach (MODULE_ALLOW_LIST as $v) {
        $modules[$v] = [ 
            'className' => APP_NAMESPACE . '\\' . ucfirst($v) . '\CliModule',
            'path' => APP_PATH . $v . '/CliModule.php'
        ];
    }
    // 加入模块分组配置
    $console->registerModules($modules);

    // 设置选项
    $console->setArgument($arguments);

    unset($arguments[0]);

    $arguments = array_map('urlencode', $arguments);

    $arguments = implode(' ', $arguments);

    // 解析路由
    $console->router->handle($arguments);
    if (!$console->router->wasMatched()) {
        echo '未匹配到合适的路由,请校验参数',PHP_EOL;
        exit();
    }
    $arguments = [
        'module' => $console->router->getModuleName(),
        'task' => $console->router->getTaskName(),
        'action' => $console->router->getActionName(),
        'params' => array_map('urldecode', $console->router->getParams())
    ];

    // 处理请求
    $console->handle($arguments);
} catch (\Throwable $e) {
    $previous = $e->getPrevious();
    $consoleConfig = $console->config->application;
    if ($consoleConfig->debug->state ?? false) {
        if (empty($consoleConfig->debug->path)) {
            echo 'Exception: ', PHP_EOL, '所在文件:', $e->getFile(), PHP_EOL, '所在行:', $e->getLine(), PHP_EOL, '错误码:', $e->getCode(), PHP_EOL, '错误消息:', $e->getMessage(), PHP_EOL, PHP_EOL;
            if (!is_null($previous)) {
                echo '前一个Exception: ', PHP_EOL, '所在文件:', $previous->getFile(), PHP_EOL, '所在行:', $previous->getLine(), PHP_EOL, '错误码:', $previous->getCode(), PHP_EOL, '错误消息:', $previous->getMessage(), PHP_EOL, PHP_EOL;
            }
            exit();
        }
        $errorFile = $consoleConfig->debug->path;
        $errorType = 'debug';
    } else {
        $errorFile = $consoleConfig->error->path;
        $errorType = 'error';
    }
    $errorMessage = 'Exception: [所在文件:' . $e->getFile() . '] [所在行:' . $e->getLine() . '] [错误码:' . $e->getCode() . '] [错误消息:' . $e->getMessage() . '] '/* . PHP_EOL . '[异常追踪信息:' . $e->getTraceAsString() . ']' */;
    if (!is_null($previous)) {
        $errorMessage .= ' 前一个Exception: [所在文件:' . $previous->getFile() . '] [所在行:' . $previous->getLine() . '] [错误码:' . $previous->getCode() . '] [错误消息:' . $previous->getMessage() . '] '/* . PHP_EOL . '[异常追踪信息:' . $previous->getTraceAsString() . ']' */;
    }
    $console->di->get('logger', [ 
        $errorFile
    ])->$errorType($errorMessage);
}

4、在cli下创建config目录,并在config目录下创建router.php路由规则文件

<?php
/**
 * @desc cli路由规则
 * @author: ZhaoYang
 * @date: 2018年6月17日 下午5:45:54
 */
// @formatter:off
defined('MODULE_ALLOW_LIST') || define('MODULE_ALLOW_LIST', ['home']);
//@formatter:on
defined('DEFAULT_MODULE') || define('DEFAULT_MODULE', MODULE_ALLOW_LIST[0]);
defined('DEFAULT_MODULE_NAMESPACE') || define('DEFAULT_MODULE_NAMESPACE', APP_NAMESPACE . '\\' . ucfirst(MODULE_ALLOW_LIST['0']));
$defaultRouters = [
    '#^([a-zA-Z0-9\\_\\-]+)$#' => [
        'module' => DEFAULT_MODULE,
        'task' => 1,
        'action' => 'main'
    ],
    '#^([a-zA-Z0-9\\_\\-]+):delimiter([a-zA-Z0-9\\_\\-]+)$#' => [
        'module' => DEFAULT_MODULE,
        'task' => 1,
        'action' => 2
    ],
    '#^([a-zA-Z0-9\\_\\-]+):delimiter([a-zA-Z0-9\\_\\-]+)(:delimiter.+)$#' => [
        'module' => DEFAULT_MODULE,
        'task' => 1,
        'action' => 2,
        'params' => 3
    ]
];

$routers = [ ];
foreach (MODULE_ALLOW_LIST as $v) {
    $vUcfirst = ucfirst($v);
    $routers['#^' . $v . '$#'] = [
        'module' => $v,
        'task' => 'Main',
        'action' => 'main'
    ];
    $routers[$v . ':delimiter:task'] = [
        'module' => $v,
        'task' => 1,
        'action' => 'main'
    ];
    $routers[$v . ':delimiter:task:delimiter:action'] = [
        'module' => $v,
        'task' => 1,
        'action' => 2
    ];
    $routers[$v . ':delimiter:task:delimiter:action:delimiter:params'] = [
        'module' => $v,
        'task' => 1,
        'action' => 2,
        'params' => 3
    ];
}
return array_merge($defaultRouters, $routers);

5、在cli/config下创建services.php文件注册一些cli服务
主要是调度和路由服务需要重写,web有很多服务都用不上,所以剔除掉。

<?php
/**
 * @desc 注册cli服务
 * @author: ZhaoYang
 * @date: 2018年6月17日 下午6:51:16
 */
use Common\Common;
use Phalcon\Cache\Backend\Factory as CacheBackendFactory;
use Phalcon\Cache\Frontend\Factory as CacheFrontendFactory;
use Phalcon\Cli\Dispatcher;
use Phalcon\Cli\Router;
use Phalcon\Config;
use Phalcon\Crypt;
use Phalcon\Db\Adapter\Pdo\Mysql;
use Phalcon\Db\Profiler;
use Phalcon\Di\FactoryDefault\Cli;
use Phalcon\Events\Manager as EventsManager;
use Phalcon\Logger\Adapter\File as LoggerAdapterFile;
use Phalcon\Logger\Formatter\Line as LoggerFormatterLine;
use Library\Plugins\DbProfilerPlugin;
use Phalcon\Text;

$di = new Cli();

/**
 * @desc 注册配置服务
 * @author: ZhaoYang
 * @date: 2018年6月17日 下午6:55:44
 */
$di->setShared('config', function () use ($config) {
    return new Config($config);
});

/**
 * @desc 注册路由服务
 * @author: ZhaoYang
 * @date: 2018年6月17日 下午6:56:10
 */
$di->setShared('router', function () use ($routerRules) {
    $router = new Router(false);
    foreach ($routerRules as $k => $v) {
        $router->add($k, $v);
    }
    return $router;
});

/**
 * @desc 注册调度器服务
 * @author: ZhaoYang
 * @date: 2018年6月18日 下午4:38:04
 */
$di->setShared('dispatcher', function () {
    $dispatcher = new Dispatcher();
    return $dispatcher;
});

/**
 * @desc 注册性能分析组件
 * @author zhaoyang
 * @date 2018年5月20日 下午9:34:33
 */
$di->setShared('profiler', function () {
    $profiler = new Profiler();
    return $profiler;
});

/**
 * @desc 注册数据库(连接)服务
 * @author zhaoyang
 * @date 2018年5月14日 下午9:01:36
 */
$di->setShared('db', function () {
    $dbConfig = $this->getConfig()->services->db->toArray();
    $mysql = new Mysql($dbConfig);
    if ($dbConfig['logged'] ?? false) {
        $eventsManager = new EventsManager();
        $eventsManager->attach('db', new DbProfilerPlugin());
        $mysql->setEventsManager($eventsManager);
    }
    return $mysql;
});

/**
 * @desc 注册日志服务
 * @author zhaoyang
 * @date 2018年5月19日 下午6:20:36
 */
$di->set('logger', function (string $file = null, array $line = null) {
    $config = $this->getConfig()->services->logger;
    $linConfig = clone $config->line;
    !is_null($line) && $linConfig = $linConfig->merge(new Config($line));
    $loggerFormatterLine = new LoggerFormatterLine($linConfig->format, $linConfig->date_format);
    $fileConfig = $config->file;
    if (empty($file)) {
        $file = $fileConfig->info;
    } else if (array_key_exists($file, $fileConfig->toArray())) {
        $file = $fileConfig->$file;
    }
    $file = Common::dirFormat($file);
    $dir = dirname($file);
    $mkdirRes = Common::mkdir($dir);
    if (!$mkdirRes) {
        throw new \Exception('创建目录 ' . $dir . ' 失败');
    }
    $loggerAdapterFile = new LoggerAdapterFile($file);
    $loggerAdapterFile->setFormatter($loggerFormatterLine);
    return $loggerAdapterFile;
});

/**
 * @desc 注册加密服务
 * @author zhaoyang
 * @date 2018年5月28日 下午8:17:46
 */
$di->set('crypt', function (string $key = null, int $padding = null, string $cipher = null) {
    $cryptConfig = $this->getConfig()->services->crypt;
    $crypt = new Crypt();
    if (!empty($cryptConfig->key) || !empty($padding)) {
        $crypt->setKey($key ?? $cryptConfig->key);
    }
    if (!empty($cryptConfig->padding) || !empty($key)) {
        $crypt->setPadding($padding ?? $cryptConfig->padding);
    }
    if (!empty($cryptConfig->cipher) || !empty($cipher)) {
        $crypt->setCipher($cipher ?? $cryptConfig->cipher);
    }
    return $crypt;
});

/**
 * @desc 注册缓存
 * @author zhaoyang
 * @date 2018年5月30日 下午10:30:29
 */
$di->set('cache', function (array $options = []) {
    $cacheConfig = $this->getConfig()->services->cache;
    $frontendConfig = $cacheConfig->frontend;
    if (isset($options['frontend']['adapter'])) {
        $frontendOption = new Config($options['frontend']);
        if (array_key_exists($options['frontend']['adapter'], $frontendConfig->toArray())) {
            $frontendOptionClone = clone $frontendConfig->{$options['frontend']['adapter']};
            $frontendOptionClone->merge($frontendOption);
            $frontendOption = $frontendOptionClone;
        }
    } else {
        $frontendOption = clone $frontendConfig->data;
        $frontendOption->adapter = 'data';
    }
    $frontendOption = Common::convertArrKeyUnderline($frontendOption->toArray());
    if (version_compare(PHALCON_VERSION, '3.2.0', '>')) {
        $frontendCache = CacheFrontendFactory::load($frontendOption);
    } else {
        $frontendClassName = 'Phalcon\\Cache\\Frontend\\' . Text::camelize($frontendOption['adapter']);
        $frontendCache = new $frontendClassName($frontendOption);
    }
    $backendConfig = $cacheConfig->backend;
    if (isset($options['backend']['adapter'])) {
        $backendOption = new Config($options['backend']);
        if (array_key_exists($options['backend']['adapter'], $backendConfig->toArray())) {
            $backendOptionClone = clone $backendConfig->{$options['backend']['adapter']};
            $backendOptionClone->merge($backendOption);
            $backendOption = $backendOptionClone;
        }
    } else {
        $backendOption = clone $backendConfig->file;
        $backendOption->adapter = 'file';
    }
    if ($backendOption->adapter == 'file') {
        if (empty($dir = $backendOption->cache_dir)) {
            throw new \Exception('缓存目录不能为空');
        }
        $dir = Common::dirFormat($dir);
        $mkdirRes = Common::mkdir($dir);
        if (!$mkdirRes) {
            throw new \Exception('创建目录 ' . $dir . ' 失败');
        }
    }
    $backendOption = Common::convertArrKeyUnderline($backendOption->toArray());
    if (version_compare(PHALCON_VERSION, '3.2.0', '>')) {
        $backendOption['frontend'] = $frontendCache;
        $backendCache = CacheBackendFactory::load($backendOption);
    } else {
        $backendClassName = 'Phalcon\\Cache\\Backend\\' . Text::camelize($backendOption['adapter']);
        $backendCache = new $backendClassName($frontendCache, $backendOption);
    }
    return $backendCache;
});

/**
 * @desc 注册 modelsMetadata服务
 * @author zhaoyang
 * @date 2018年6月2日 下午10:39:43
 */
$di->setShared('modelsMetadata', function () {
    $modelsMetadataConfig = $this->getConfig()->services->models_metadata;
    $backendConfig = $this->getConfig()->services->cache->backend;
    $optionsArr = $modelsMetadataConfig->options->toArray();
    if (!isset($optionsArr['adapter'])) {
        throw new \Exception('modelsMetadata必须设置adapter');
    }
    if (array_key_exists($optionsArr['adapter'], $backendConfig->toArray())) {
        $backendOption = clone $backendConfig->{$optionsArr['adapter']};
        $optionsArr = $backendOption->merge(new Config($optionsArr))->toArray();
    }
    if ($optionsArr['adapter'] == 'files') {
        if (empty($optionsArr['meta_data_dir'])) {
            throw new \Exception('缓存目录不能为空');
        }
        $dir = Common::dirFormat($optionsArr['meta_data_dir']);
        $mkdirRes = Common::mkdir($dir);
        if (!$mkdirRes) {
            throw new \Exception('创建目录 ' . $dir . ' 失败');
        }
    }
    $optionsArr = Common::convertArrKeyUnderline($optionsArr);
    $modelsMetadataClassName = 'Phalcon\\Mvc\\Model\\MetaData\\' . Text::camelize($optionsArr['adapter']);
    $modelsMetadata = new $modelsMetadataClassName($optionsArr);
    return $modelsMetadata;
});

/**
 * @desc 注册modelsCache服务
 * @author zhaoyang
 * @date 2018年6月3日 下午6:22:31
 */
$di->set('modelsCache', function (array $options = []) {
    $modelsCacheConfig = clone $this->getConfig()->services->models_cache;
    !empty($options) && $modelsCacheConfig->merge(new Config($options));
    $options = $modelsCacheConfig->toArray();
    $modelsCache = $this->get('cache', [
        $options
    ]);
    return $modelsCache;
});

6、在app/home模块下创建CliModule.php文件
由于phalcon(本项目用的是3.1.2)的命令行应用无法注册命名空间去访问,只能注册目录,并且registerAutoloaders()方法没有传参,所以与原Module.php无法共用,只能单独重写。同时为了能在命令行应用中使用model,需要将models命名空间注册。

<?php
/**
 * @desc 模块配置
 * @author zhaoyang
 * @date 2018年5月3日 下午8:49:49
 */
namespace App\Home;

use Phalcon\DiInterface;
use Phalcon\Loader;
use Phalcon\Config\Adapter\Php as ConfigAdapterPhp;

class CliModule {

    // 模块配置文件目录
    private static $_configPath = __DIR__ . '/config/config_' . NOW_ENV . '.php';

    public function registerAutoloaders() {

    }

    public function registerServices(DiInterface $di) {
        // 注册配置文件服务,合并主配置和模块配置
        $this->registerConfigService($di);
        $config = $di->getConfig();
        // 注册命名空间
        $loader = new Loader();
        $directories = [ 
            __DIR__ . '/tasks/'
        ];
        $loader->registerNamespaces($config->module_namespaces->toArray())->registerDirs($directories)->register();
    }

    /**
     * @desc 注册配置服务
     * @author zhaoyang
     * @date 2018年5月3日 下午8:50:51
     */
    private function registerConfigService(DiInterface $di) {
        $config = $di->getConfig();
        $di->setShared('config', function () use ($config) {
            $moduleConfigPath = self::$_configPath;
            if (is_file($moduleConfigPath)) {
                $override = new ConfigAdapterPhp($moduleConfigPath);
                $config->merge($override);
            }
            return $config;
        });
    }
}

7、在home模块下创建tasks目录,并在该目录下创建MainTask.php做测试

<?php
use Phalcon\Cli\Task;
use App\Home\Models\Robots;

class MainTask extends Task
{
    public function mainAction()
    {
        echo "This is the home main task and the main action" . PHP_EOL;
        var_dump($this->dispatcher->getParams());
    }

    public function testAction()
    {
        echo "This is the home main task and the test action" . PHP_EOL;
        var_dump($this->dispatcher->getParams());
    }

    public function mysqlAction(){
        $robotsList = Robots::find();
        foreach ($robotsList as $robot){
            echo $robot->id,' ',$robot->name,' ',$robot->type,' ',$robot->weight,PHP_EOL;
        }
    }

    public function loggerAction(){
        $this->logger->info('This is an info message2');

        $this->di->getLogger()->info('This is an info message3');

        $this->getDi()->getLogger()->info('This is an info message4');

        $this->di['logger']->info('This is an info message5');

        $this->di->get('logger')->info('This is an info message6');
    }
}

8、在app/home/tasks下创建TestTask.php做测试

<?php
use Phalcon\Cli\Task;

class TestTask extends Task
{
    public function mainAction()
    {
        echo "This is the home test task and the main action" . PHP_EOL;
        var_dump($this->dispatcher->getParams());
    }

    public function testAction()
    {
        echo "This is the home test task and the test action" . PHP_EOL;
        var_dump($this->dispatcher->getParams());
    }
}

9、至此多模块命令行应用已经完成,下面开始测试
首先进入cli目录下

php cli.php

这里写图片描述

php cli.php main

这里写图片描述

php cli.php main test

这里写图片描述

 php cli.php main test aaa

这里写图片描述

php cli.php home

这里写图片描述

php cli.php home test

这里写图片描述

php cli.php home test test

这里写图片描述

php cli.php home test test aaa bbb

这里写图片描述

 php cli.php main mysql

这里写图片描述
测试完成

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值