php项目增加命令行程序
control/interface.php
function formSubArray($data): array|string
{
if (is_array($data)) {
$result = [];
foreach ($data as $key => $value) {
if (is_array($value)) {
$result[$key] = formSubArray($value);
} else {
$result[$key] = formSub($value);
}
}
} else {
$result = formSub($data);
}
return $result;
}
/*表单提交数据整理和防sql注入*/
function formSub($data): string
{
$data = trim($data); //消除两边的空格
$data = htmlentities($data, ENT_QUOTES, "utf-8"); //字符转换为 HTML 实体。
//对单引号(')双引号(")反斜杠(\)NULL进行转义
return addslashes($data);
}/cmd.php
#! /usr/bin/env php
<?php
$_SERVER['HTTP_HOST'] = '';
$_SERVER['REQUEST_URI'] = '';
require_once __DIR__ . '/shell/baseCommand.php';
class CommandRunner
{
/** @var string 控制器名称 */
protected $controllerName;
/** @var string 动作名称 */
protected $actionName;
/** @var array 命令行参数 */
protected $params = [];
/** @var array 控制器映射 */
protected $controllerMap = [];
/** @var array 颜色定义 */
protected $colors = [
'reset' => "\033[0m",
'red' => "\033[31m",
'green' => "\033[32m",
'yellow' => "\033[33m",
'blue' => "\033[34m",
'purple' => "\033[35m",
'cyan' => "\033[36m",
];
/**
* 构造函数
*/
public function __construct()
{
$this->initControllerMap();
}
/**
* 初始化控制器映射
* 子类应重写此方法来定义控制器映射
*/
protected function initControllerMap()
{
// 示例控制器映射
$this->controllerMap = [];
//遍历shell目录下的所有Command结尾的类文件,加入控制器映射中
$dir = __DIR__ . '/shell';
$files = scandir($dir);
foreach ($files as $file) {
if (is_file($dir . '/' . $file) && str_ends_with($file, 'Command.php')) {
$controllerName = substr($file, 0, -11);
$this->controllerMap[$controllerName] = "shell\\{$controllerName}Command";
}
}
}
/**
* 运行命令行脚本
*/
public function run()
{
try {
$this->parseArguments();
$this->validateCommand();
$this->executeAction();
} catch (Exception $e) {
$this->error($e->getMessage());
exit(1);
}
}
/**
* 解析命令行参数
*/
protected function parseArguments()
{
global $argv;
// 至少需要 CONTROL 和 ACTION 两个参数
if (count($argv) < 3) {
throw new InvalidArgumentException("缺少 CONTROL 和 ACTION 参数");
}
// 获取 CONTROL 和 ACTION
$this->controllerName = strtolower(formSubArray($argv[1]));
$this->actionName = strtolower(formSubArray($argv[2]));
// 解析其他参数
for ($i = 3; $i < count($argv); $i++) {
$arg = $argv[$i];
if (str_starts_with($arg, '--')) {
$param = substr($arg, 2);
if (str_contains($param, '=')) {
list($key, $value) = explode('=', $param, 2);
//参数安全处理
$this->params[$key] = formSubArray($value);
} else {
// 无值参数视为布尔值 true
$this->params[$param] = true;
}
} else {
// 位置参数
$this->params[] = $arg;
}
}
}
protected function getControllerName($controllerName=null)
{
$controllerClass = $this->controllerMap[$controllerName?:$this->controllerName];
$controllerClassFile = __DIR__ . "/{$controllerClass}.php";
$controllerClassFile = str_replace(['\\','/'], DIRECTORY_SEPARATOR, $controllerClassFile);
// 检查控制器类是否存在
if (!class_exists($controllerClass)) {
if (file_exists($controllerClassFile)) require_once $controllerClassFile;
else {
throw new InvalidArgumentException("控制器文件不存在: {$controllerClassFile}");
}
if (!class_exists($controllerClass)) {
throw new InvalidArgumentException("控制器类不存在: {$controllerClass}");
}
}
return $controllerClass;
}
protected function getActionName()
{
return 'action' . ucfirst($this->actionName);
}
/**
* 验证命令是否有效
*/
protected function validateCommand()
{
// 检查控制器是否存在
if (!isset($this->controllerMap[$this->controllerName])) {
throw new InvalidArgumentException("未知的控制器: {$this->controllerName}");
}
$controllerName = $this->getControllerName();
// 检查动作方法是否存在
$controller = new $controllerName();
$actionMethod = $this->getActionName();
if (!method_exists($controller, $actionMethod)) {
throw new InvalidArgumentException("控制器 {$this->controllerName} 中不存在动作: {$this->actionName}");
}
// 检查方法是否为 public
$reflection = new ReflectionMethod($controllerName, $actionMethod);
if (!$reflection->isPublic()) {
throw new InvalidArgumentException("动作方法 {$actionMethod} 不是 public 的");
}
}
/**
* 执行控制器动作
*/
protected function executeAction()
{
$controllerClass = $this->getControllerName();
$controller = new $controllerClass();
$actionMethod = $this->getActionName();
// 获取方法参数信息
$reflection = new ReflectionMethod($controllerClass, $actionMethod);
$methodParams = $reflection->getParameters();
// 准备传递给方法的参数
$callParams = [];
foreach ($methodParams as $param) {
$paramName = $param->getName();
// 检查参数是否在命令行参数中提供
if (array_key_exists($paramName, $this->params)) {
$callParams[] = $this->params[$paramName];
} // 检查是否有默认值
elseif ($param->isOptional()) {
$callParams[] = $param->getDefaultValue();
} // 必需参数缺失
else {
throw new InvalidArgumentException("缺少必需参数: {$paramName}");
}
}
// 执行动作方法
$result = $reflection->invokeArgs($controller, $callParams);
// 输出结果(如果有)
if ($result !== null) {
$this->output($result);
}
}
/**
* 输出信息
* @param string|array $message 要输出的信息
* @param string $color 颜色名称
*/
protected function output($message, $color = 'reset')
{
if (is_array($message)) {
$message = json_encode($message, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
}
echo $this->colors[$color] . $message . $this->colors['reset'] . PHP_EOL;
}
/**
* 输出错误信息
* @param string $message 错误信息
*/
protected function error($message)
{
$this->output("错误: {$message}", 'red');
}
/**
* 显示帮助信息
*/
public function showHelp()
{
$this->output("命令行工具使用帮助", 'blue');
$this->output("用法: php cmd.php CONTROL ACTION [--参数1=值1 --参数2=值2 ...]", 'blue');
$this->output("", 'reset');
$this->output("可用控制器:", 'blue');
foreach ($this->controllerMap as $name => $class) {
$this->output(" {$name} ({$class})", 'yellow');
$this->showControllerActions($name, $class);
}
}
/**
* 显示控制器的可用动作
* @param string $controllerName 控制器名称
* @param string $controllerClass 控制器类名
*/
protected function showControllerActions($controllerName, $controllerClass)
{
$controllerClass = $this->getControllerName($controllerName);
$reflection = new ReflectionClass($controllerClass);
$methods = $reflection->getMethods(ReflectionMethod::IS_PUBLIC);
$actions = [];
foreach ($methods as $method) {
if (str_starts_with($method->getName(), 'action') && $method->getName() !== 'action') {
$actionName = substr($method->getName(), 6);
$params = [];
foreach ($method->getParameters() as $param) {
$paramInfo = '$' . $param->getName();
if ($param->isOptional()) {
$default = $param->getDefaultValue();
$defaultStr = is_string($default) ? "'{$default}'" : $default;
$paramInfo .= " = {$defaultStr}";
}
$params[] = $paramInfo;
}
$actions[] = " {$actionName}(" . implode(', ', $params) . ")";
}
}
if (!empty($actions)) {
$this->output(" 可用动作:", 'cyan');
foreach ($actions as $action) {
$this->output($action, 'purple');
}
$this->output("", 'reset');
}
}
}
// 创建并运行命令行工具
$command = new CommandRunner();
// 检查是否有 --help 或 -h 参数
if (in_array('--help', $argv) || in_array('-h', $argv)) {
$command->showHelp();
} else {
$command->run();
}
shell/baseCommand.php
<?php
namespace shell;
require_once 'control/interface.php';
use control\interfaces;
class baseCommand
{
use interfaces;
/** @var array 颜色定义 */
protected $colors = [
'reset' => "\033[0m",
'red' => "\033[31m",
'green' => "\033[32m",
'yellow' => "\033[33m",
'blue' => "\033[34m",
'purple' => "\033[35m",
'cyan' => "\033[36m",
];
public string $date;
public string $time;
public function __construct()
{
$this->date = date('Y-m-d');
$this->time = date('Y-m-d H:i:s');
$this->init();
}
/**
* 运行例子测试
* php .\cmd.php base test --name=zhangsan [--age=32]
* @param $name
* @return void
*/
public function actionTest($name, $age=23)
{
$this->output("hello world: {$name}, age: {$age}", 'green');
}
/**
* 输出信息
* @param string|array $message 要输出的信息
* @param string $color 颜色名称
*/
protected function output($message, $color = 'reset')
{
if (is_array($message)) {
$message = json_encode($message, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
}
echo $this->colors[$color] . $message . $this->colors['reset'] . PHP_EOL;
}
/**
* 输出错误信息
* @param string $message 错误信息
*/
protected function error($message)
{
$this->output("错误: {$message}", 'red');
}
/**
* 显示帮助信息
*/
public function showHelp()
{
$this->output("帮助信息", 'blue');
}
// 自动加载类
public function loadClass($className)
{
$className = str_replace('shell\\', '', $className);
if (stristr(PHP_OS, 'LINUX')) {
$className = str_replace('\\', '/', $className);
}
$paths = [
"{$className}.php",
"control/{$className}.php",
"shell/{$className}.php",
];
foreach ($paths as $fileName) {
$path = serverRoot . $fileName;
if (file_exists($path)) {
require_once $fileName;
return;
}
}
}
}