PHP 命令行脚本怎么组织

wen PHP项目 1

本文目录导读:

PHP 命令行脚本怎么组织

  1. 基础目录结构
  2. 使用 Symfony Console 组件
  3. 轻量级脚本 - 单文件组织
  4. 编写健壮的 CLI 脚本
  5. 环境检测和依赖验证
  6. 配置管理
  7. 测试组织
  8. 实际使用示例
  9. 关键最佳实践:

在 PHP 中组织命令行脚本,我建议采用以下最佳实践架构:

基础目录结构

project/
├── bin/              # 可执行脚本入口
│   └── app.php
├── src/              # 业务逻辑代码
│   ├── Command/      # 命令类
│   ├── Service/      # 服务层
│   └── Helper/       # 辅助函数
├── config/           # 配置文件
├── vendor/           # 依赖包
└── tests/            # 测试目录

使用 Symfony Console 组件

推荐使用 symfony/console,这是最流行的 PHP CLI 框架:

// bin/app.php
#!/usr/bin/env php
<?php
require __DIR__ . '/../vendor/autoload.php';
use Symfony\Component\Console\Application;
use App\Command\HelloCommand;
$app = new Application('My CLI App', '1.0.0');
$app->add(new HelloCommand());
$app->run();
// src/Command/HelloCommand.php
namespace App\Command;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;
class HelloCommand extends Command
{
    protected function configure()
    {
        $this->setName('hello')
             ->setDescription('Say hello')
             ->addArgument('name', InputArgument::REQUIRED, 'Your name')
             ->addOption('greeting', 'g', InputOption::VALUE_OPTIONAL, 'Custom greeting', 'Hello');
    }
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $name = $input->getArgument('name');
        $greeting = $input->getOption('greeting');
        $output->writeln("<info>{$greeting}, {$name}!</info>");
        return Command::SUCCESS;
    }
}

轻量级脚本 - 单文件组织

对于简单任务,可以组织为:

#!/usr/bin/env php
<?php
// 手动加载
require_once __DIR__ . '/vendor/autoload.php';
// 参数解析辅助函数
function parseArgs(array $argv): array {
    $args = [];
    foreach (array_slice($argv, 1) as $arg) {
        if (str_starts_with($arg, '--')) {
            $parts = explode('=', ltrim($arg, '--'), 2);
            $args[$parts[0]] = $parts[1] ?? true;
        } elseif (str_starts_with($arg, '-')) {
            $args[ltrim($arg, '-')] = true;
        } else {
            $args[] = $arg;
        }
    }
    return $args;
}
// 主逻辑
function main(array $args): void {
    // 你的业务逻辑
}
// 入口
if (PHP_SAPI === 'cli') {
    $args = parseArgs($argv);
    main($args);
}

编写健壮的 CLI 脚本

1 颜色输出辅助类

// src/Helper/Output.php
namespace App\Helper;
class Output {
    const COLORS = [
        'red' => '31',
        'green' => '32',
        'yellow' => '33',
        'blue' => '34',
        'magenta' => '35',
        'cyan' => '36'
    ];
    public static function color(string $text, string $color): string {
        return "\033[" . self::COLORS[$color] . "m{$text}\033[0m";
    }
    public static function success(string $msg): void {
        echo self::color($msg, 'green') . PHP_EOL;
    }
    public static function error(string $msg): void {
        fwrite(STDERR, self::color($msg, 'red') . PHP_EOL);
    }
}

2 进度条和交互

// 进度条示例
class ProgressBar {
    private $total;
    private $current = 0;
    public function __construct(int $total) {
        $this->total = $total;
    }
    public function advance(int $step = 1): void {
        $this->current += $step;
        $percent = round(($this->current / $this->total) * 100);
        printf("\rProgress: [%s] %d%%", 
            str_repeat('=', $percent/2) . str_repeat(' ', 50 - $percent/2),
            $percent
        );
    }
}

环境检测和依赖验证

// bin/app.php
<?php
// 检查运行环境
if (PHP_SAPI !== 'cli') {
    die('This script can only be run from command line');
}
// 检查 PHP 版本
if (version_compare(PHP_VERSION, '8.0.0', '<')) {
    die('PHP 8.0 or higher is required');
}
// 自动加载
require __DIR__ . '/../vendor/autoload.php';
// 错误处理
set_error_handler(function($severity, $message, $file, $line) {
    throw new ErrorException($message, 0, $severity, $file, $line);
});
set_exception_handler(function($e) {
    fwrite(STDERR, "Unhandled exception: " . $e->getMessage() . PHP_EOL);
    exit(1);
});

配置管理

// config/app.php
return [
    'app_name' => 'My CLI Tool',
    'version' => '1.0.0',
    'debug' => getenv('APP_DEBUG') ?: false,
    'database' => [
        'host' => getenv('DB_HOST') ?: 'localhost',
        'port' => getenv('DB_PORT') ?: 3306,
    ]
];

测试组织

// tests/Command/HelloCommandTest.php
namespace Tests\Command;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Tester\CommandTester;
use App\Command\HelloCommand;
class HelloCommandTest extends TestCase {
    public function testExecute() {
        $application = new Application();
        $application->add(new HelloCommand());
        $command = $application->find('hello');
        $commandTester = new CommandTester($command);
        $commandTester->execute([
            'command' => $command->getName(),
            'name' => 'World'
        ]);
        $output = $commandTester->getDisplay();
        $this->assertStringContainsString('Hello, World!', $output);
    }
}

实际使用示例

# 查看帮助
php bin/app.php list
# 运行命令
php bin/app.php hello John --greeting="Hi"
# 后台执行
nohup php bin/app.php long-running-task > /var/log/app.log 2>&1 &
# 定时执行
0 * * * * /usr/bin/php /path/to/bin/app.php backup-database

关键最佳实践:

  1. 参数解析:使用专业的参数解析器(Symfony Console 或 GetOpt)
  2. 错误处理:使用异常和 exit codes
  3. 日志记录:输出到 STDOUT/STDERR,需要时才写文件
  4. 依赖注入:使用容器管理服务
  5. 环境配置:使用环境变量或配置文件
  6. 严格输入验证:验证所有输入参数
  7. 脚本权限:设置可执行权限 chmod +x bin/app.php

这样的组织方式既适合简单的单文件脚本,也适合复杂的命令行应用程序。

抱歉,评论功能暂时关闭!