B

Bootstrapping a CLI PHP application in Vanilla PHP

2025-06-05

在 Vanilla PHP 中引导 CLI PHP 应用程序

介绍

PHP 因其在 Web 应用程序和 CMS 中的流行而闻名,但许多人不知道的是,PHP 也是一门非常适合构建无需 Web 服务器的命令行应用程序的语言。它的易用性和熟悉的语法使其成为一种低门槛的语言,可以用于开发免费工具和小型应用程序,例如与 API 通信或通过 Crontab 执行计划任务,而无需暴露给外部用户。

当然,您可以编写一个单文件 PHP 脚本来满足您的需求,这对于一些小事情来说可能效果不错;但这会使将来维护、扩展或重用该代码变得非常困难。构建命令行应用程序时可以应用相同的 Web 开发原则,只不过我们不再使用前端了——太棒了!此外,外部用户无法访问该应用程序,这增加了安全性,并为实验创造了更大的空间。

最近我有点厌倦了 Web 应用以及围绕前端构建的复杂性,所以在命令行中玩转 PHP 对我个人来说非常新鲜。在本系列文章中,我们将共同构建一个极简主义/无依赖的 CLI AppKit(可以想象成一个微型框架)—— minicli,它可以作为你用 PHP 开发实验性 CLI 应用的基础。

附言:如果您需要的只是一个git clone请转到此处

这是构建 Minicli系列的第 1 部分

先决条件

为了遵循本教程,您需要php-cli在本地计算机或开发服务器上安装Composer来生成自动加载文件。

1. 设置目录结构和入口点

让我们首先创建主项目目录:

mkdir minicli
cd minicli
Enter fullscreen mode Exit fullscreen mode

接下来,我们将为 CLI 应用程序创建入口点。这相当于index.php现代 PHP Web 应用程序中的入口点文件,其中单个入口点将请求重定向到相关的控制器。但是,由于我们的应用程序仅包含 CLI,因此我们将使用不同的文件名,并包含一些安全措施,以阻止从 Web 服务器执行。

minicli使用您喜欢的文本编辑器打开一个名为的新文件:

vim minicli
Enter fullscreen mode Exit fullscreen mode

你会注意到我们这里没有包含.php扩展名。因为我们在命令行上运行这个脚本,所以我们可以添加一个特殊的描述符来告诉你的 shell 程序我们正在使用 PHP 来执行这个脚本。

#!/usr/bin/php
<?php

if (php_sapi_name() !== 'cli') {
    exit;
}

echo "Hello World\n";
Enter fullscreen mode Exit fullscreen mode

第一行是应用程序shebang。它告诉运行此脚本的 shell 使用这个脚本/usr/bin/php作为该代码的解释器。

使用以下命令使脚本可执行chmod

chmod +x minicli
Enter fullscreen mode Exit fullscreen mode

现在您可以使用以下命令运行该应用程序:

./minicli
Enter fullscreen mode Exit fullscreen mode

您应该看到Hello World输出。

2. 设置源目录和自动加载

为了便于在多个应用程序中重复使用此框架,我们将创建两个源目录:

  • app:此命名空间将保留给特定于应用程序的模型和控制器。
  • lib:此命名空间将被核心框架类使用,可在各种应用程序中重复使用。

使用以下命令创建两个目录:

mkdir app
mkdir lib
Enter fullscreen mode Exit fullscreen mode

现在让我们创建一个composer.json文件来设置autoload。这将帮助我们在使用 PHP 中的类和其他面向对象资源时更好地组织我们的应用程序。

在文本编辑器中创建一个新composer.json文件并包含以下内容:

{
  "autoload": {
    "psr-4": {
      "Minicli\\": "lib/",
      "App\\": "app/"
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

保存并关闭文件后,运行以下命令设置自动加载文件:

composer dump-autoload
Enter fullscreen mode Exit fullscreen mode

为了测试自动加载功能是否按预期工作,我们将创建第一个类。该类将代表 Application 对象,负责处理命令的执行。我们尽量简单,将其命名为App

使用您选择的文本编辑器在您的文件夹中创建一个新App.php文件:lib

vim lib/App.php
Enter fullscreen mode Exit fullscreen mode

该类App实现了一个runCommand方法,用于替换我们之前在可执行文件中设置的“Hello World”代码minicli
稍后我们将修改此方法,使其能够处理多个命令。目前,它将使用执行脚本时传递的参数输出“Hello $name”文本;如果没有传递参数,它将使用world该变量的默认值$name

在文件中插入以下内容App.php,完成后保存并关闭文件:

<?php

namespace Minicli;

class App
{
    public function runCommand(array $argv)
    {
        $name = "World";
        if (isset($argv[1])) {
            $name = $argv[1];
        }

        echo "Hello $name!!!\n";
    }
}
Enter fullscreen mode Exit fullscreen mode

现在转到您的minicli脚本并用以下代码替换当前内容,我们将在稍后解释:

#!/usr/bin/php
<?php

if (php_sapi_name() !== 'cli') {
    exit;
}

require __DIR__ . '/vendor/autoload.php';

use Minicli\App;

$app = new App();
$app->runCommand($argv);

Enter fullscreen mode Exit fullscreen mode

这里,我们需要自动生成的autoload.php文件,以便在创建新对象时自动包含类文件。创建App对象后,我们调用该runCommand方法,并传递一个全局$argv变量,该变量包含运行该脚本时使用的所有参数。该$argv变量是一个数组,其中第一个位置 (0) 是脚本的名称,后续位置是传递给命令调用的额外参数。这是一个预定义变量,可在从命令行执行的 PHP 脚本中使用。

现在,为了测试一切是否按预期工作,请运行:

./minicli your-name
Enter fullscreen mode Exit fullscreen mode

您应该看到以下输出:

Hello your-name!!!
Enter fullscreen mode Exit fullscreen mode

现在,如果您不向脚本传递任何其他参数,它应该打印:

Hello World!!!
Enter fullscreen mode Exit fullscreen mode

3. 创建输出助手

由于命令行界面是纯文本的,有时很难识别应用程序中的错误或警报消息,或者难以以更易于阅读的方式格式化数据。我们将其中一些任务外包给一个辅助类,该类负责处理终端的输出。

lib使用您选择的文本编辑器在文件夹内创建一个新类:

vim lib/CliPrinter.php
Enter fullscreen mode Exit fullscreen mode

以下类定义了三个公共方法:一个out用于输出消息的基本方法;一个newline用于打印新行的方法;以及一个display将这两个方法结合起来以强调文本(用新行换行)的方法。稍后我们将扩展此类以包含更多格式选项。

<?php

namespace Minicli;

class CliPrinter
{
    public function out($message)
    {
        echo $message;
    }

    public function newline()
    {
        $this->out("\n");
    }

    public function display($message)
    {
        $this->newline();
        $this->out($message);
        $this->newline();
        $this->newline();
    }
}
Enter fullscreen mode Exit fullscreen mode

现在让我们更新该类App以使用 CliPrinter 辅助类。我们将创建一个名为 的属性,$printer该属性将引用一个CliPrinter对象。该对象是在App构造函数方法中创建的。然后,我们将创建一个getPrinter方法,并在方法中使用它runCommand来显示消息,而不是echo直接使用:

<?php

namespace Minicli;

class App
{
    protected $printer;

    public function __construct()
    {
        $this->printer = new CliPrinter();
    }

    public function getPrinter()
    {
        return $this->printer;
    }

    public function runCommand($argv)
    {
        $name = "World";
        if (isset($argv[1])) {
            $name = $argv[1];
        }

        $this->getPrinter()->display("Hello $name!!!");
    }
}
Enter fullscreen mode Exit fullscreen mode

现在再次运行该应用程序:

./minicli your_name
Enter fullscreen mode Exit fullscreen mode

您应该会得到如下输出(消息周围有换行符):


Hello your_name!!!


Enter fullscreen mode Exit fullscreen mode

在下一步中,我们将把命令逻辑移到App类之外,以便您在需要时更轻松地包含新命令。

4.创建命令注册表

现在,我们将重构该类,使其能够通过一个通用方法和一个命令注册表App来处理多个命令。新命令的注册方式与一些流行的 PHP Web 框架中定义的路由方式类似。runCommand

更新后的App类现在将包含一个新属性,即一个名为 的数组command_registry。该方法registerCommand将使用此变量将应用程序命令存储为由名称标识的匿名函数。

runCommand方法现在会检查 是否$argv[1]设置为已注册的命令名。如果未设置命令,它将默认尝试执行help命令。如果未找到有效命令,它将打印错误消息。

修改后,更新后的类如下所示App.php。将文件的当前内容替换App.php为以下代码:

<?php

namespace Minicli;

class App
{
    protected $printer;

    protected $registry = [];

    public function __construct()
    {
        $this->printer = new CliPrinter();
    }

    public function getPrinter()
    {
        return $this->printer;
    }

    public function registerCommand($name, $callable)
    {
        $this->registry[$name] = $callable;
    }

    public function getCommand($command)
    {
        return isset($this->registry[$command]) ? $this->registry[$command] : null;
    }

    public function runCommand(array $argv = [])
    {
        $command_name = "help";

        if (isset($argv[1])) {
            $command_name = $argv[1];
        }

        $command = $this->getCommand($command_name);
        if ($command === null) {
            $this->getPrinter()->display("ERROR: Command \"$command_name\" not found.");
            exit;
        }

        call_user_func($command, $argv);
    }
}
Enter fullscreen mode Exit fullscreen mode

接下来,我们将更新minicli脚本并注册两个命令:hello和。它们将使用新创建的方法help在我们的对象中注册为匿名函数AppregisterCommand

复制更新后的minicli脚本并更新您的文件:

#!/usr/bin/php
<?php

if (php_sapi_name() !== 'cli') {
    exit;
}

require __DIR__ . '/vendor/autoload.php';

use Minicli\App;

$app = new App();

$app->registerCommand('hello', function (array $argv) use ($app) {
    $name = isset ($argv[2]) ? $argv[2] : "World";
    $app->getPrinter()->display("Hello $name!!!");
});

$app->registerCommand('help', function (array $argv) use ($app) {
    $app->getPrinter()->display("usage: minicli hello [ your-name ]");
});

$app->runCommand($argv);

Enter fullscreen mode Exit fullscreen mode

现在你的应用程序有两个可以运行的命令:helphello。要测试它,请运行:

./minicli help
Enter fullscreen mode Exit fullscreen mode

这将打印:


usage: minicli hello [ your-name ]


Enter fullscreen mode Exit fullscreen mode

现在hello使用以下命令测试该命令:

./minicli hello your_name
Enter fullscreen mode Exit fullscreen mode

Hello your_name!!!


Enter fullscreen mode Exit fullscreen mode

您现在有一个使用极简结构的工作 CLI 应用程序,它将作为实现更多命令和功能的基础。

此时您的目录结构将如下所示:

.
├── app
├── lib
│   ├── App.php
│   └── CliPrinter.php
├── vendor
│   ├── composer
│   └── autoload.php
├── composer.json
└── minicli

Enter fullscreen mode Exit fullscreen mode

在本系列的下一篇中,我们将重构minicli以使用命令控制器,将命令逻辑移至应用程序特定命名空间内的专用类。下次再见!

文章来源:https://dev.to/erikaheidi/bootstrapping-a-cli-php-application-in-vanilla-php-4ee
PREV
使用 Minicli 在 PHP 中创建 Twitch / IRC 聊天机器人
NEXT
3D打印简介