在 PHP 中使用操作系统进程

发布于 作者

Working with OS process in PHP image

有时您需要从您的 PHP 应用程序中使用操作系统级命令。让我们看看如何做到这一点,并看看是否可以改善开发者体验。

在过去几年中,我一直专注于代码编写方式的各个方面以及如何改进它。我从研究如何更好地更面向对象地与 HTTP 集成开始。我相信我找到了实现这一目标的方法,现在将注意力集中在其他地方。

在某些情况下,您希望在应用程序中使用操作系统 CLI。无论是在 Web 应用程序中还是在另一个 CLI 应用程序中。过去,我们使用过诸如 execpassthrushell_execsystem 这样的方法。然后 Symfony Process 组件出现了,我们得救了。

Symfony Process 组件使得与操作系统进程集成并获取输出变得超级容易。但是我们如何与这个库集成仍然有点令人沮丧。我们创建一个新进程,传入一个参数数组,该数组构成我们希望运行的命令。让我们看一下

$command = new Process(
command: ['git', 'push', 'origin', 'main'],
);
 
$command->run();

这种方法有什么问题吗?老实说,没有。但有没有办法改善开发者体验?假设我们从 git 切换到 svn(我知道这不太可能)。

为了改善开发者体验,首先,我们需要了解逻辑上构成操作系统命令的组件。我们可以将它们分解为

可执行文件参数

我们的可执行文件是我们直接交互的东西,例如 php、git、brew 或系统上安装的任何其他二进制文件。然后参数是我们如何进行交互;它们可以是子命令、选项、标志或参数。

因此,如果我们稍微抽象一下,我们将拥有一个 process 和一个带有参数的 command。我们将使用接口/契约来定义我们的组件,以控制我们的工作流程应该如何工作。让我们从 Process 契约开始

declare(strict_types=1);
 
namespace JustSteveKing\OS\Contracts;
 
use Symfony\Component\Process\Process;
 
interface ProcessContract
{
public function build(): Process;
}

我们在这里说的是,每个进程都必须能够被构建,并且构建的进程的结果应该是一个 Symfony Process。我们的进程应该为我们构建一个 Command 来运行,所以现在让我们看一下我们的 Command 契约

declare(strict_types=1);
 
namespace JustSteveKing\OS\Contracts;
 
interface CommandContract
{
public function toArgs(): array;
}

我们主要想从我们的命令中得到的是能够作为参数返回,以便我们可以将其作为命令传递给 Symfony Process。

关于想法就说这么多,让我们看一下一个真实的例子。我们将使用 git 作为例子,因为我们大多数人应该都能理解 git 命令。

首先,让我们创建一个 Git 进程,它实现我们刚刚描述的 Process 契约

class Git implements ProcessContract
{
use HandlesGitCommands;
 
private CommandContract $command;
}

我们的 Process 实现该契约,并且有一个 command 属性,我们将使用它来允许我们的进程被流畅地构建和执行。我们有一个 trait,它将使我们能够集中化 Git 进程的构建和制作方式。让我们看一下

trait HandlesGitCommands
{
public function build(): Process
{
return new Process(
command: $this->command->toArgs(),
);
}
 
protected function buildCommand(Git $type, array $args = []): void
{
$this->command = new GitCommand(
type: $type,
args: $args,
);
}
}

因此,我们的 trait 显示了 Process 契约本身的实现,并提供了有关如何构建进程的说明。它还包含一个方法,允许我们抽象化命令构建。

到目前为止,我们可以创建一个进程并构建一个潜在的命令。但是,我们还没有创建命令。我们在 trait 中创建一个新的 Git Command,该命令使用 Git 类作为类型。让我们看一下这个其他的 Git 类,它是一个枚举。不过,我将显示一个简化版本 - 因为实际上,您希望它映射到您希望支持的所有 git 子命令

enum Git: string
{
case PUSH = 'push';
case COMMIT = 'commit';
}

然后我们将它传递给 Git Command

final class GitCommand implements CommandContract
{
public function __construct(
public readonly Git $type,
public readonly array $args = [],
public readonly null|string $executable = null,
) {
}
 
public function toArgs(): array
{
$executable = (new ExecutableFinder())->find(
name: $this->executable ?? 'git',
);
 
if (null === $executable) {
throw new InvalidArgumentException(
message: "Cannot find executable for [$this->executable].",
);
}
 
return array_merge(
[$executable],
[$this->type->value],
$this->args,
);
}
}

在这个类中,我们接受来自 Process 的参数,这些参数目前由我们的 HandledGitCommands trait 处理。然后我们可以将其转换为 Symfony Process 可以理解的参数。我们使用 Symfony 包中的 ExecutableFinder 来允许我们最小化路径中的错误。但是,我们还希望在找不到可执行文件时抛出异常。

当我们将所有内容放在 Git Process 中时,它看起来就像这样

use JustSteveKing\OS\Commands\Types\Git as SubCommand;
 
class Git implements ProcessContract
{
use HandlesGitCommands;
 
private CommandContract $command;
 
public function push(string $branch): Process
{
$this->buildCommand(
type: SubCommand:PUSH,
args: [
'origin',
$branch,
],
);
 
return $this->build();
}
}

现在我们剩下要做的就是运行代码本身,以便我们可以很好地在我们的 PHP 应用程序中使用 git

$git = new Git();
$command = $git->push(
branch: 'main',
);
 
$result = $command->run();

push 方法的结果将允许您与 Symfony Process 交互 - 意味着您可以对另一侧的命令执行所有操作。我们唯一改变的是在创建这个进程的基础上构建一个面向对象的包装器。这使我们能够很好地开发和保持上下文,并以可测试和可扩展的方式扩展它。

您在应用程序中使用操作系统命令的频率如何?您能想到任何这种用例吗?我已经 在 GitHub 上的一个仓库中发布了示例代码,以便您可以使用它,看看是否可以改进您的操作系统集成。

SSH、MySQL 甚至 ansible 或 terraform 的一个很好的例子!想象一下,如果您可以从 Laravel artisan 中高效地按计划运行 MySQL 转储,而无需一直使用第三方软件包!

Steve McDougall photo

Laravel News 的技术作家,Treblle 的开发者倡导者。API 专家,资深 PHP/Laravel 工程师。 YouTube 直播主.

Cube

Laravel 时事通讯

加入 40,000 多位其他开发者,永不错过新的技巧、教程等。

Laravel Forge logo

Laravel Forge

轻松创建和管理您的服务器,并在几秒钟内部署您的 Laravel 应用程序。

Laravel Forge
Tinkerwell logo

Tinkerwell

Laravel 开发人员必备的代码运行器。使用 AI、自动完成和对本地和生产环境的即时反馈进行调试。

Tinkerwell
No Compromises logo

No Compromises

Joel 和 Aaron,来自 No Compromises 播客的两位经验丰富的开发人员,现在可以为您的 Laravel 项目聘用。⬧ 每月固定费用 7500 美元。⬧ 没有冗长的销售流程。⬧ 没有合同。⬧ 100% 退款保证。

No Compromises
Kirschbaum logo

Kirschbaum

提供创新和稳定性,以确保您的 Web 应用程序取得成功。

Kirschbaum
Shift logo

Shift

运行旧版本的 Laravel?即时、自动化的 Laravel 升级和代码现代化,让您的应用程序保持新鲜。

Shift
Bacancy logo

Bacancy

只需每月 2500 美元,即可为您的项目配备经验丰富的 Laravel 开发人员,拥有 4-6 年的经验。获得 160 小时的专业知识和 15 天的无风险试用。立即安排电话!

Bacancy
Lucky Media logo

Lucky Media

立即获得幸运 - Laravel 开发的理想选择,拥有十多年的经验!

Lucky Media
Lunar: Laravel E-Commerce logo

Lunar: Laravel 电子商务

Laravel 的电子商务。一个开源软件包,将现代无头电子商务功能的强大功能带到 Laravel。

Lunar: Laravel 电子商务
LaraJobs logo

LaraJobs

官方 Laravel 招聘网站

LaraJobs
SaaSykit: Laravel SaaS Starter Kit logo

SaaSykit: Laravel SaaS 启动工具包

SaaSykit 是一款 Laravel SaaS 启动套件,包含运行现代 SaaS 所需的所有功能。支付、精美结账、管理面板、用户仪表板、身份验证、就绪组件、统计、博客、文档等。

SaaSykit: Laravel SaaS 启动工具包
Rector logo

Rector

您的无缝 Laravel 升级合作伙伴,帮助您降低成本,加速创新,助力企业成功。

Rector
MongoDB logo

MongoDB

通过将 MongoDB 与 Laravel 强力集成,增强您的 PHP 应用程序,使开发人员能够轻松高效地构建应用程序。支持事务、搜索、分析和移动用例,同时使用熟悉的 Eloquent API。探索灵活的现代数据库 MongoDB 如何改变您的 Laravel 应用程序。

MongoDB
Maska is a Simple Zero-dependency Input Mask Library image

Maska 是一个简单的零依赖输入掩码库。

阅读文章
Add Swagger UI to Your Laravel Application image

将 Swagger UI 添加到您的 Laravel 应用程序

阅读文章
Assert the Exact JSON Structure of a Response in Laravel 11.19 image

在 Laravel 11.19 中断言响应的精确 JSON 结构

阅读文章
Build SSH Apps with PHP and Laravel Prompts image

使用 PHP 和 Laravel Prompts 构建 SSH 应用程序

阅读文章
Building fast, fuzzy site search with Laravel and Typesense image

使用 Laravel 和 Typesense 构建快速模糊网站搜索

阅读文章
Add Comments to your Laravel Application with the Commenter Package image

使用 Commenter 包为您的 Laravel 应用程序添加评论

阅读文章