Write PHP Extensions for the Rust‑Based Mago Toolchain – A Hands‑On Guide

This guide walks PHP developers through creating a custom Mago extension that adds a Linter rule to forbid eval(), covering Mago’s worker‑process architecture, project setup, extension and rule implementation, configuration, testing, debugging, and advanced options such as help messages, auto‑fixes, and regression testing with a corpus.

Open Source Tech Hub
Open Source Tech Hub
Open Source Tech Hub
Write PHP Extensions for the Rust‑Based Mago Toolchain – A Hands‑On Guide

Mago is a high‑performance PHP toolchain written in Rust that bundles code formatting, linting, and static analysis. Since version 1.47.0 it exposes an Extension API and provides an official PHP SDK, allowing PHP developers to write extensions without learning Rust.

1. Understand Mago’s Extension Architecture

The extension runs in an isolated Worker process that communicates with the Mago core via a binary worker protocol. Three core concepts are:

Rule / Plugin : the business logic, e.g., a Linter rule.

Extension : a package that bundles multiple rules or plugins.

Worker : a long‑running PHP process that registers the extension and receives callbacks.

Benefits of this design include crash isolation, language‑agnostic workers, and native multi‑core parallelism.

2. Prepare the Environment

Install Mago via Composer: composer require --dev carthage-software/mago Verify the version (≥ 1.47.0):

./vendor/bin/mago --version   # expected output: 1.47.0 or higher

The package contains the Mago executable and a matching PHP SDK.

3. Project Structure

Create a demo project and register the extension code under .mago/extension/ so that the business source under src/ stays clean:

mago-extension-demo/
├── .mago/
│   ├── tinywan-worker.php      # Worker entry point
│   └── extension/
│       ├── TinywanExtension.php
│       └── Linter/Rules/NoEvalRule.php
├── src/                       # Business code (unchanged)
├── tests/
│   └── sample.php            # Sample code that triggers the rule
├── mago.toml                 # Mago configuration
└── composer.json

Add the extension namespace to composer.json:

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

Run composer dump-autoload to register the autoloader.

4. Implement the Linter Rule

Create .mago/extension/Linter/Rules/NoEvalRule.php:

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)
        );
    }
}

Key points: code must be globally unique, e.g., vendor/rule-name. targets selects the syntax nodes the rule receives; here it is EvalConstruct (the eval expression). LintContext provides the current node, source file, parent node, and a cancellation token. defaultLevel defines the severity (Help, Note, Warning, Error).

5. Create the Extension Factory

File .mago/extension/TinywanExtension.php:

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()]
        );
    }
}

The factory bundles metadata and the rule list; callers simply invoke TinywanExtension::create().

6. Write the Worker Entrypoint

File .mago/tinywan-worker.php:

declare(strict_types=1);

use Mago\Sdk\Worker;
use Tinywan\Mago\TinywanExtension;

require dirname(__DIR__) . '/vendor/autoload.php';

(new Worker(TinywanExtension::create()))->run();

Note: the worker’s STDOUT is reserved for the SDK protocol; debugging output must go to STDERR.

7. Configure Mago

In the project root, create mago.toml:

[source]
paths = ["src", "tests"]

[extension-hosts.tinywan]
command = ["php", ".mago/tinywan-worker.php"]
workers = 0          # adaptive pool, up to Mago thread count
working-directory = "."
inherit-environment = true
environment = { APP_ENV = "analysis" }
maximum-payload-size = 67108864
request-timeout-ms = 30000
shutdown-timeout-ms = 250
stderr-tail-size = 65536

The workers value of 0 lets Mago scale the pool automatically; set a fixed number if the extension needs external resources.

8. Test the Extension

Create tests/sample.php that triggers the rule:

declare(strict_types=1);
$code = 'echo 开源技术小栈;';
eval($code); // this should trigger tinywan/no-eval

Validate registration: ./vendor/bin/mago extension validate List registered extensions: ./vendor/bin/mago extension list Run the linter for the specific rule: ./vendor/bin/mago lint --only tinywan/no-eval Expected output shows an error[tinywan/no-eval] with the message “Avoid evaluating dynamically generated PHP code.”

9. Add Help and Note Messages

Modify lint() to chain withHelp() and withNote():

$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.')
);

Rerunning the linter now displays the help and note sections.

10. Provide an Auto‑Fix

Use withEdit() and TextEdit::replace() to suggest a replacement:

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

11. Override Rule Severity

In mago.toml you can change the default level:

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

Running mago lint will now emit warning[tinywan/no-eval] while extension list still shows the original (error) declaration.

12. Debugging Tips

Disable extensions with ./vendor/bin/mago lint --no-extensions to isolate issues.

View the worker’s STDERR tail via ./vendor/bin/mago extension list --json or write directly with fwrite(STDERR, "msg\n").

Enable trace logs: MAGO_LOG=trace ./vendor/bin/mago lint.

13. Regression Testing with a Corpus

Create a small corpus under tests/corpus/:

# tests/corpus/mago.toml
[source]
paths = ["src"]

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

Write a test file tests/corpus/src/bad.php containing an @mago-expect lint:tinywan/no-eval comment. Run:

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

If the expectation is met, the exit code is 0; otherwise an unfulfilled-expect error is reported. Baselines can be generated and verified with --generate-baseline and --verify-baseline.

14. Summary

The article demonstrates how PHP developers can extend the Rust‑based Mago toolchain without touching Rust code, covering architecture, project layout, rule implementation, configuration, testing, debugging, and advanced features such as help messages, auto‑fixes, and corpus‑based regression testing.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

RustPHPExtensionLinterToolchainMago
Open Source Tech Hub
Written by

Open Source Tech Hub

Sharing cutting-edge internet technologies and practical AI resources.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.