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

這裏寫圖片描述
測試完成

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章