首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >PHP 也能给 Rust 工具写插件了!Mago 扩展开发实战指南

PHP 也能给 Rust 工具写插件了!Mago 扩展开发实战指南

作者头像
Tinywan
发布于 2026-09-15 14:38:41
发布于 2026-09-15 14:38:41
1310
举报
文章被收录于专栏:开源技术小栈开源技术小栈

Mago 扩展开发实战指南

Mago 是一个用 Rust 编写的高性能 PHP 工具链,集代码格式化、Linter、静态分析于一体。从 1.47.0 开始,它正式开放了 Extension API,并且官方提供了第一方 PHP SDK。这意味着:PHP 开发者无需学习 Rust,就能给这个 Rust 工具写扩展。

本文将带你从零写一个可用的 Mago 扩展:实现一条自定义 Linter 规则,禁止项目中使用 eval(),并给出帮助提示。所有代码均可在 Mago 1.47+ 环境中直接运行。

01先理解 Mago 扩展的架构

Mago 的扩展不是直接加载进 Rust 进程的 DLL/SO,而是运行在独立的 Worker 进程中。Mago 和 Worker 之间通过一套二进制的 worker protocol 通信。

对 PHP 开发者来说,你只需要关心三件事:

概念

作用

Rule / Plugin

你的业务逻辑,比如一条 Linter 规则

Extension

一个逻辑包,把多个 Rule / Plugin 打包在一起

Worker

一个长期运行的 PHP 进程,向 Mago 注册 Extension,并接收回调

这种设计有几个好处:

•  扩展崩溃不会拖垮 Mago 主进程

•  可以用任何语言实现 Worker,不限定 Rust

•  多 Worker 并行,天然支持多核

02环境准备

安装 Mago

如果你还没安装 Mago:

composer require --dev carthage-software/mago

安装完成后,确认版本:

./vendor/bin/mago --version# 期望输出 1.47.0 或更高

carthage-software/mago 这个 Composer 包同时包含 Mago 可执行文件安装器和版本匹配的 PHP SDK,所以扩展项目直接依赖它即可。

PHP 版本要求

Mago 1.47 支持 PHP 8.1 到 PHP 8.6。扩展代码也在这个范围内。

初始化测试项目

mkdir mago-extension-demo && cd mago-extension-democomposer init --no-interaction --name=demo/mago-extension-democomposer require --dev carthage-software/mago

03项目结构

注意: 我这里是把扩展代码放到 .mago/extension/

为了让扩展不污染业务源码,我们把扩展相关代码统一放到 .mago/extension/。业务 src/ 保持干净:

mago-extension-demo/├── .mago/│   ├── tinywan-worker.php          # Worker 入口│   └── extension/                  # 扩展源码目录│       ├── TinywanExtension.php│       └── Linter/│           └── Rules/│               └── NoEvalRule.php├── src/                            # 业务代码,保持干净├── tests/│   └── sample.php                  # 用来测试的坏代码├── mago.toml                       # Mago 配置└── composer.json

然后在 composer.json 里,用 autoload-dev 把 .mago/extension/ 注册到命名空间:

{    "name": "demo/mago-extension-demo",    "autoload": {        "psr-4": {            "Demo\\App\\": "src/"        }    },    "autoload-dev": {        "psr-4": {            "Tinywan\\Mago\\": ".mago/extension/"        }    },    "require-dev": {        "carthage-software/mago": "^1.47"    }}

执行:

composer dump-autoload

04编写扩展代码

写 Linter Rule

创建 .mago/extension/Linter/Rules/NoEvalRule.php:

/** * @desc NoEvalRule.php 描述信息 * @author Tinywan(ShaoBo Wan) */declare(strict_types=1); namespace Tinywan\Mago\Linter\Rules; use Mago\Sdk\Linter\LintContext;use Mago\Sdk\Linter\Rule;use Mago\Sdk\Linter\RuleDefinition;use Mago\Sdk\Reporting\Issue;use Mago\Sdk\Reporting\Level;use Mago\Sdk\Syntax\NodeKind; final class NoEvalRule implements Rule{    public function getDefinition(): RuleDefinition    {        return new RuleDefinition(            code: 'tinywan/no-eval',            name: 'No eval',            description: 'Disallows evaluating dynamically generated PHP code.',            defaultLevel: Level::Error,            defaultEnabled: true,            targets: [NodeKind::EvalConstruct],        );    }     public function lint(LintContext $context): void    {        // 合作式取消检查,长循环中建议加上        $context->cancellation->throwIfCancelled();         $context->report(Issue::new(            'Avoid evaluating dynamically generated PHP code.',            $context->node->span,        ));    }}

关键点:

• code 必须全局唯一,建议用 vendor/rule-name 格式。

• targets 决定了 Mago 会把哪些语法节点发给你的规则。这里我们只关心 EvalConstruct,也就是 eval 表达式。

• LintContext 里包含当前节点、源文件、父节点、取消令牌等。

• defaultLevel 是规则触发 issue 的默认级别。

Mago 的 Level 有四种:

级别

extension list 显示

含义

Level::Help

(help)

建议,最轻

Level::Note

(note)

提示

Level::Warning

(warning)

警告

Level::Error

(error)

错误,最严重

所以当你看到:

tinywan/no-eval (error)

它表示这条规则默认会报 Error 级别的诊断。项目可以在 mago.toml 里覆盖这个级别。

写 Extension 工厂

创建 .mago/extension/TinywanExtension.php:

/** * @desc TinywanExtension.php 描述信息 * @author Tinywan(ShaoBo Wan) */declare(strict_types=1); namespace Tinywan\Mago; use Mago\Sdk\Extension;use Tinywan\Mago\Linter\Rules\NoEvalRule; final class TinywanExtension{    private function __construct() {}     public static function create(): Extension    {        return new Extension(            identifier: 'tinywan/project-rules',            name: 'Tinywan project rules',            version: '1.0.0',            linterRules: [new NoEvalRule()],        );    }}

工厂的作用是把扩展的元数据、规则列表、插件列表封装起来。消费者直接调用 create(),不需要自己拼 Extension 对象。

写 Worker 入口

创建 .mago/tinywan-worker.php:

/** * @desc tinywan-worker.php 描述信息 * @author Tinywan(ShaoBo Wan) */declare(strict_types=1); use Mago\Sdk\Worker;use Tinywan\Mago\TinywanExtension; require dirname(__DIR__) . '/vendor/autoload.php'; (new Worker(    TinywanExtension::create(),))->run();

注意:Worker 的标准输出(STDOUT)被 SDK 协议占用,调试信息必须写到标准错误(STDERR)。

05配置 Mago

在项目根目录创建 mago.toml:

[source]paths = ["src", "tests"] [extension-hosts.tinywan]command = ["php", ".mago/tinywan-worker.php"]

这里 [extension-hosts.tinywan] 的 tinywan 是本地 host pool 名称,跟 Extension 的全局 identifier 是两回事。

command 不会被 shell 解析,是一个直接的程序 + 参数数组。

06准备测试代码

创建 tests/sample.php:

declare(strict_types=1); $code = 'echo 开源技术小栈;';eval($code);  // 这里应该触发 tinywan/no-eval

07运行扩展

验证扩展注册

./vendor/bin/mago extension validate Validated 1 extension(s) from 1 host(s).

如果输出没有错误,说明 Worker 能正常启动并向 Mago 注册。

查看已注册的规则

./vendor/bin/mago extension list

你应该能看到类似:

Extension hosts:  tinywan (adaptive, up to 4 workers)Registered extensions:Tinywan project rules (tinywan/project-rules)  Version: 1.0.0  Linter rules: 1    tinywan/no-eval (error)

tinywan/no-eval 后面的 (error) 就是规则的默认级别。

运行 Linter

./vendor/bin/mago lint --only tinywan/no-eval

预期输出:

error[tinywan/no-eval]: Avoid evaluating dynamically generated PHP code.   ┌─ tests/sample.php:10:1   │10 │ eval($code);  // 这里应该触发 tinywan/no-eval   │ ^^^^^^^^^^^ error: found 1 issues: 1 error(s)

因为规则默认启用,直接执行 mago lint 也会报这个问题。

08进阶:给规则加上帮助提示

Mago 的 Issue 支持 withHelp() 和 withNote(),可以给开发者更清晰的指引。

修改 .mago/extension/Linter/Rules/NoEvalRule.php:

public function lint(LintContext $context): void{    $context->cancellation->throwIfCancelled();     $context->report(        Issue::new(            'Avoid evaluating dynamically generated PHP code.',            $context->node->span,        )        ->withHelp('Consider refactoring to avoid runtime code evaluation.')        ->withNote('eval() is dangerous and often disabled in secure environments.')    );}

重新运行 mago lint,就会看到 help 和 note 信息。

error[tinywan/no-eval]: Avoid evaluating dynamically generated PHP code.   ┌─ tests/sample.php:10:1   │10 │ eval($code);  // 这里应该触发 tinywan/no-eval   │ ^^^^^^^^^^^   │   = eval() is dangerous and often disabled in secure environments.   = Help: Consider refactoring to avoid runtime code evaluation. error: found 2 issues: 2 error(s)

如果你想给出自动修复建议,可以构造 TextEdit:

use Mago\Sdk\Reporting\TextEdit; $context->report(    Issue::new(        'Avoid evaluating dynamically generated PHP code.',        $context->node->span,    )    ->withEdit(TextEdit::replace($context->node->span, '// eval() removed')));

09配置扩展主机的更多选项

mago.toml 中的扩展主机有很多可配置项:

[extension-hosts.tinywan]enabled = truecommand = ["php", ".mago/tinywan-worker.php"]workers = 0                      # 0 表示自适应进程池working-directory = "."inherit-environment = trueenvironment = { APP_ENV = "analysis" }maximum-payload-size = 67108864request-timeout-ms = 30000shutdown-timeout-ms = 250stderr-tail-size = 65536

• workers = 0:自适应进程池,最多到 Mago 线程数。

•  如果你的扩展有外部依赖(比如要连数据库),可以固定 workers = 2。

• environment 可以给 Worker 传环境变量。


10覆盖规则的默认级别

扩展里设置的 defaultLevel 只是默认值。使用项目的 mago.toml 可以覆盖它。

比如把 tinywan/no-eval 从 Error 降级为 Warning:

[linter]default = true [linter.rules]"tinywan/no-eval" = { level = "warning" }

再次运行:

./vendor/bin/mago extension list

显示还是 (error),因为 extension list 展示的是扩展自己声明的默认值。但实际 mago lint 输出会变成 warning[tinywan/no-eval]。

如果想彻底禁用这条规则:

[linter.rules]"tinywan/no-eval" = false

11调试扩展

查看扩展是否注册成功

./vendor/bin/mago extension list --json

关闭扩展对比

./vendor/bin/mago lint --no-extensions

如果关掉扩展就没有问题,说明问题跟扩展无关。

查看 Worker 错误输出

Mago 会保留 Worker 的 STDERR 尾部。你也可以在 Worker 里主动写日志:

fwrite(STDERR, "Tinywan extension initialized.\n");

只跑指定规则

./vendor/bin/mago lint --only tinywan/no-eval

使用 trace 日志

MAGO_LOG=trace ./vendor/bin/mago lint

12测试扩展:用 Corpus 做回归测试

Mago 官方推荐用真实 CLI 跑小型 corpus 来做回归测试。

创建 tests/corpus/mago.toml:

[source]paths = ["src"] [extension-hosts.tinywan]command = ["php", "../worker.php"]

创建 tests/corpus/worker.php:

declare(strict_types=1); use Mago\Sdk\Worker;use Tinywan\Mago\TinywanExtension; require dirname(__DIR__, 2) . '/vendor/autoload.php'; (new Worker(    TinywanExtension::create(),))->run();

创建 tests/corpus/src/bad.php:

// @mago-expect lint:tinywan/no-evaleval('echo 1;');

运行:

./vendor/bin/mago --workspace tests/corpus lint --reporting-format count

如果 @mago-expect 被满足,输出为 0。如果规则没触发,会报 unfulfilled-expect。

也可以用 baseline:

./vendor/bin/mago --workspace tests/corpus lint --generate-baseline./vendor/bin/mago --workspace tests/corpus lint --verify-baseline

13总结

通过本文,你应该已经掌握了:

•  Mago 扩展的基本架构:Worker → Extension → Rule/Plugin

•  把扩展代码放到 .mago/extension/,不污染业务 src/

•  用 PHP SDK 编写一条 Linter 规则

•  理解 defaultLevel 和 extension list 中的 (error) 含义

•  配置 mago.toml 注册扩展主机,并覆盖规则级别

•  验证、运行、调试扩展

•  用 corpus 做回归测试

最重要的是:Mago 的核心是用 Rust 写的,但扩展可以用 PHP 写。这对广大 PHP 开发者来说门槛极低,你可以为团队定制框架特定的规则、分析器插件,甚至整合公司内部的代码规范。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2026-08-20,如有侵权请联系 cloudcommunity@tencent.com 删除
目录
  • Mago 扩展开发实战指南
    • 01先理解 Mago 扩展的架构
    • 02环境准备
      • 安装 Mago
      • PHP 版本要求
      • 初始化测试项目
    • 03项目结构
    • 04编写扩展代码
      • 写 Linter Rule
      • 写 Extension 工厂
      • 写 Worker 入口
    • 05配置 Mago
    • 06准备测试代码
    • 07运行扩展
      • 验证扩展注册
      • 查看已注册的规则
      • 运行 Linter
    • 08进阶:给规则加上帮助提示
    • 09配置扩展主机的更多选项
    • 10覆盖规则的默认级别
    • 11调试扩展
      • 查看扩展是否注册成功
      • 关闭扩展对比
      • 查看 Worker 错误输出
      • 只跑指定规则
      • 使用 trace 日志
    • 12测试扩展:用 Corpus 做回归测试
    • 13总结
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档