# Mago - PHP Toolchain Mago is a comprehensive toolchain for PHP written in Rust, designed to provide extremely fast linting, formatting, and static analysis capabilities. Inspired by the Rust ecosystem, it combines a blazing-fast PER-CS compliant formatter, an intelligent linter with auto-fixing capabilities, a powerful static type analyzer, and an architectural guard for enforcing dependency boundaries. Mago processes PHP codebases of any size with maximum performance through parallelization and a high-performance arena allocator. The toolchain operates through a unified CLI with four main tools: `mago fmt` for code formatting, `mago lint` for style and correctness checking, `mago analyze` for deep static type analysis, and `mago guard` for architectural validation. Configuration is managed through a `mago.toml` file that can be auto-generated using `mago init`. Mago supports PHP versions 8.1 through 8.6 and can be installed via shell script, Composer, Homebrew, Cargo, or Docker. ## Installation Install Mago using the shell script for macOS and Linux, or via package managers for other environments. ```bash # Install via shell script (macOS/Linux - recommended) curl --proto '=https' --tlsv1.2 -sSf https://carthage.software/mago.sh | bash # Install a specific version curl --proto '=https' --tlsv1.2 -sSf https://carthage.software/mago.sh | bash -s -- --version=1.16.0 # Install via Composer (PHP projects) composer require --dev "carthage-software/mago:^1.16.0" # Install via Homebrew (macOS) - run self-update after brew install mago && mago self-update # Install via Cargo (Rust) cargo install mago && mago self-update # Run via Docker docker run --rm -v $(pwd):/app -w /app ghcr.io/carthage-software/mago lint # Verify installation mago --version ``` ## Project Initialization The `mago init` command generates a `mago.toml` configuration file through an interactive setup process, detecting project structure from `composer.json`. ```bash # Initialize a new project configuration mago init # Example interactive output: # $ mago init # # Mago # ⬩ Welcome! Let's get you set up. # # ╭─ Step 1: Project Setup # │ # │ Found `composer.json`. Use it to auto-configure your project? › (Y/n) # │ # │ Reading composer.json... # │ Project settings detected! # ╰─ ``` ## Configuration File Structure Mago uses a `mago.toml` file for all configuration. The file supports global options, source paths, and tool-specific settings. ```toml # mago.toml - Complete configuration example # Global options php-version = "8.2" threads = 8 editor-url = "phpstorm://open?file=%file%&line=%line%&column=%column%" # Source configuration [source] # Your application code - will be analyzed, linted, and formatted paths = ["src", "tests"] # Vendor dependencies - only parsed for type information includes = ["vendor"] # Completely ignored by all tools excludes = ["cache/**", "build/**", "var/**"] # File extensions to treat as PHP extensions = ["php"] # Parser settings [parser] enable-short-tags = false # Formatter settings [formatter] preset = "laravel" print-width = 120 use-tabs = false single-quote = true # Linter settings [linter] baseline = "lint-baseline.toml" [linter.rules] cyclomatic-complexity = { threshold = 10 } no-empty = { enabled = true, level = "warning" } prefer-static-closure = { exclude = ["tests/"] } # Analyzer settings [analyzer] baseline = "analysis-baseline.toml" find-unused-expressions = true find-unused-definitions = true check-missing-type-hints = true plugins = ["psl", "flow-php"] ignore = [ "mixed-argument", { code = "missing-return-type", in = "tests/" }, ] # Guard settings [guard] mode = "default" ``` ## Formatter Command (mago fmt) The formatter automatically formats PHP code according to PER-CS coding standards with customizable options. ```bash # Format all source files in the project mago fmt # Check formatting without making changes (CI mode) mago fmt --check # Exit code 0 = all formatted, 1 = needs formatting # Preview changes without writing (dry run) mago fmt --dry-run # Format specific files or directories mago fmt src/Service.php mago fmt src/ # Format code from stdin cat src/Service.php | mago fmt --stdin-input # Format only staged files (for pre-commit hooks) mago fmt --staged ``` ## Formatter Configuration Configure formatter behavior through the `[formatter]` section in `mago.toml` with presets or individual settings. ```toml [formatter] # Use a preset: "default", "psr-12", "laravel", "drupal" preset = "laravel" # Or customize individual settings print-width = 120 tab-width = 4 use-tabs = false single-quote = true trailing-comma = true indent-heredoc = true remove-trailing-close-tag = true # Brace styles: "same-line", "next-line", "always-next-line" control-brace-style = "same-line" function-brace-style = "next-line" method-brace-style = "next-line" classlike-brace-style = "always-next-line" closure-brace-style = "same-line" # Method chaining method-chain-breaking-style = "next-line" first-method-chain-on-new-line = true # Empty braces inline-empty-function-braces = true inline-empty-method-braces = true inline-empty-classlike-braces = true # Sorting and organization sort-uses = true sort-class-methods = false separate-use-types = true expand-use-groups = true # Spacing empty-line-after-opening-tag = true empty-line-after-namespace = true empty-line-after-use = true empty-line-after-method = true # Tool-specific excludes (additive to source.excludes) excludes = ["src/**/AutoGenerated/**/*.php"] ``` ## Linter Command (mago lint) The linter finds stylistic issues, inconsistencies, and code smells with optional auto-fixing capabilities. ```bash # Lint all source files mago lint # Auto-fix issues mago lint --fix # Preview fixes without applying mago lint --fix --dry-run # Apply unsafe fixes (may alter behavior) mago lint --fix --unsafe # Format files after fixing mago lint --fix --format-after-fix # Run only specific rules mago lint --only no-empty,use-compound-assignment # Run only semantic checks (faster than full linting) mago lint --semantics # or mago lint -s # Lint staged files only (pre-commit hooks) mago lint --staged # Enable all rules for exhaustive analysis mago lint --pedantic # List all enabled rules mago lint --list-rules mago lint --list-rules --json # Get documentation for a specific rule mago lint --explain no-redundant-nullsafe ``` ## Linter Configuration Configure linter rules with individual settings, severity levels, and per-rule path exclusions. ```toml [linter] # Path to baseline file for ignoring existing issues baseline = "lint-baseline.toml" baseline-variant = "loose" # or "strict" # Tool-specific excludes excludes = ["database/migrations/**"] # Minimum severity to fail (note, help, warning, error) minimum-fail-level = "error" # Rule configuration [linter.rules] # Enable/disable rules no-empty = { enabled = true } literal-named-argument = { enabled = false } # Set severity level no-variable-variable = { enabled = true, level = "error" } no-redundant-use = { enabled = true, level = "warning" } # Configure thresholds cyclomatic-complexity = { threshold = 10 } halstead = { effort-threshold = 7000 } kan-defect = { threshold = 1.9 } too-many-enum-cases = { threshold = 30 } # Per-rule path exclusions prefer-static-closure = { exclude = ["tests/"] } single-class-per-file = { exclude = ["scripts/", "migrations/"] } file-name = { exclude = ["scripts/"] } # Framework integrations (auto-detected from composer.json) [linter.integrations] symfony = true laravel = true phpunit = true ``` ## Analyzer Command (mago analyze) The static analyzer performs deep type checking to find logical errors, type mismatches, and potential bugs. ```bash # Analyze all source files mago analyze # Alternative spelling mago analyse # Analyze without built-in PHP stubs mago analyze --no-stubs # Analyze staged files only mago analyze --staged # Enable watch mode for continuous analysis mago analyze --watch # List all available issue codes mago analyze --list-codes # Filter output to specific issue codes mago analyze --retain-code invalid-argument --retain-code type-mismatch # Generate baseline for existing issues mago analyze --generate-baseline --baseline analysis-baseline.toml # Use existing baseline mago analyze --baseline analysis-baseline.toml # Ignore baseline temporarily mago analyze --ignore-baseline ``` ## Analyzer Configuration Configure analyzer features, strictness levels, plugins, and performance tuning. ```toml [analyzer] # Baseline for ignoring existing issues baseline = "analysis-baseline.toml" baseline-variant = "loose" # Tool-specific excludes excludes = ["tests/**/*.php"] # Minimum severity to fail minimum-fail-level = "error" # Feature flags find-unused-expressions = true find-unused-definitions = true analyze-dead-code = true check-throws = false check-missing-override = true find-unused-parameters = true check-missing-type-hints = true check-closure-missing-type-hints = true check-arrow-function-missing-type-hints = true check-property-initialization = true check-use-statements = true # Strictness settings strict-list-index-checks = true no-boolean-literal-comparison = true allow-possibly-undefined-array-keys = false trust-existence-checks = false enforce-class-finality = false require-api-or-internal = false # Property initialization class-initializers = ["setUp", "initialize", "boot"] # Exception filtering (when check-throws = true) unchecked-exceptions = ["LogicException", "InvalidArgumentException"] unchecked-exception-classes = ["App\\Exception\\IgnoredException"] # Path-scoped ignoring ignore = [ "mixed-argument", { code = "missing-return-type", in = "tests/" }, { code = "unused-parameter", in = ["tests/", "src/Generated/"] }, ] # Plugins: stdlib (default), psl, flow-php, psr-container plugins = ["psl", "flow-php", "psr-container"] # disable-default-plugins = true # Disable stdlib # Performance tuning [analyzer.performance] saturation-complexity-threshold = 8192 disjunction-complexity-threshold = 4096 string-combination-threshold = 128 integer-combination-threshold = 128 array-combination-threshold = 128 ``` ## Guard Command (mago guard) The architectural guard enforces dependency boundaries (perimeter guard) and coding conventions (structural guard). ```bash # Run all guard checks (perimeter + structural) mago guard # Run only structural checks mago guard --structural # Run only perimeter checks mago guard --perimeter # Generate baseline mago guard --generate-baseline --baseline guard-baseline.toml # Use baseline mago guard --baseline guard-baseline.toml ``` ## Guard Perimeter Configuration Define architectural layers and dependency rules to enforce boundaries between namespaces. ```toml [guard] mode = "default" # "default", "structural", or "perimeter" excludes = ["src/ThirdParty/**"] [guard.perimeter] # Define architectural layers (inner to outer) layering = [ "App\\Domain", "App\\Application", "App\\UI", "App\\Infrastructure" ] # Create reusable layer aliases [guard.perimeter.layers] core = ["@native", "Psl\\**"] psr = ["Psr\\**"] framework = ["Symfony\\**", "Doctrine\\**"] # Dependency rules [[guard.perimeter.rules]] namespace = "App\\Domain" permit = ["@layer:core"] # Domain only depends on core [[guard.perimeter.rules]] namespace = "App\\Application" permit = ["@layer:core", "@layer:psr"] [[guard.perimeter.rules]] namespace = "App\\Infrastructure" permit = ["@layer:core", "@layer:psr", "@layer:framework"] [[guard.perimeter.rules]] namespace = "App\\Tests" permit = ["@all"] # Tests can depend on anything # Permit syntax examples: # @global - global namespace symbols # @all - any symbol in codebase # @self / @this - same root namespace # @native / @php - PHP built-in symbols # @layer: - layer alias # App\Shared\** - glob pattern # App\Service - exact symbol # App\Service\ - exact namespace # Detailed permissions with kinds filter [[guard.perimeter.rules]] namespace = "DoctrineMigrations\\" permit = [ { path = "@all", kinds = ["class-like"] } ] # kinds: "class-like", "function", "constant", "attribute" ``` ## Guard Structural Configuration Enforce naming conventions, modifiers, and inheritance constraints on symbols. ```toml # Controllers must be final and follow naming convention [[guard.structural.rules]] on = "App\\UI\\**\\Controller\\**" target = "class" must-be-named = "*Controller" must-be-final = true must-be-readonly = true reason = "Controllers must be final and follow naming conventions." # Repository interfaces must follow naming convention [[guard.structural.rules]] on = "App\\Domain\\**\\Repository\\**" target = "interface" must-be-named = "*RepositoryInterface" reason = "Domain repository interfaces must follow naming convention." # Infrastructure repositories must extend base class [[guard.structural.rules]] on = "App\\Infrastructure\\**\\Repository\\**" target = "class" must-be-final = true must-extend = "App\\Infrastructure\\Shared\\Repository\\AbstractRepository" reason = "Infrastructure repositories must extend our abstract class." # Enum-only namespace [[guard.structural.rules]] on = "App\\Domain\\**\\Enum\\**" must-be = ["enum"] reason = "This namespace is designated for enums only." # Selector keys: # on - glob pattern for symbol names (required) # not-on - glob pattern to exclude # target - symbol kind filter: class, interface, trait, enum, function, constant # Constraint keys: # must-be - restrict to specific symbol kinds # must-be-named - naming pattern (e.g., "*Controller") # must-be-final - boolean # must-be-abstract - boolean # must-be-readonly - boolean # must-implement - interface(s) to implement # must-extend - parent class to extend # must-use-trait - trait(s) to use # must-use-attribute - attribute(s) required # reason - human-readable explanation # Complex inheritance constraints [[guard.structural.rules]] on = "App\\Handler\\**" target = "class" # Must implement ALL interfaces must-implement = ["App\\Handler\\HandlerInterface", "App\\Handler\\LoggableInterface"] # OR syntax: must implement (A AND B) OR (C) # must-extend = [["App\\AbstractA", "App\\AbstractB"], ["App\\AbstractC"]] # Explicit "nothing" constraint # must-implement = "@nothing" ``` ## Reporting Options Shared reporting options for lint, analyze, and ast commands to customize output format and behavior. ```bash # Output formats mago lint --reporting-format=rich # Default, detailed mago lint --reporting-format=medium mago lint --reporting-format=short mago lint --reporting-format=emacs mago lint --reporting-format=github # GitHub Actions annotations mago lint --reporting-format=gitlab # GitLab CI annotations mago lint --reporting-format=json # Machine-readable JSON mago lint --reporting-format=checkstyle mago lint --reporting-format=sarif mago lint --reporting-format=count # Issue count summary mago lint --reporting-format=code-count # Per-code count # Filtering and sorting mago lint --sort mago lint --minimum-report-level=warning # Filter output by severity mago lint --minimum-fail-level=error # Set exit code threshold mago lint --retain-code=no-empty # Show only specific codes mago lint --fixable-only # Show only fixable issues # Output target mago lint --reporting-target=stdout mago lint --reporting-target=stderr # Baseline management mago lint --generate-baseline --baseline lint-baseline.toml mago lint --baseline lint-baseline.toml mago lint --baseline lint-baseline.toml --backup-baseline mago lint --ignore-baseline ``` ## AST Command (mago ast) Inspect the Abstract Syntax Tree of PHP files for debugging and understanding code structure. ```bash # Display AST for a file mago ast src/Service.php # Output AST in different formats mago ast src/Service.php --reporting-format=json # Display tokens from lexer mago ast src/Service.php --tokens ``` ## Utility Commands Additional utility commands for configuration, file listing, shell completions, and updates. ```bash # Display current merged configuration mago config mago config --show linter mago config --show formatter mago config --show analyzer mago config --default # Show defaults mago config --schema # Output JSON schema # List files that will be processed mago list-files mago list-files --command formatter mago list-files --command analyzer mago list-files --command linter # Generate shell completions mago generate-completions bash > ~/.bash_completion.d/mago mago generate-completions zsh > ~/.zfunc/_mago mago generate-completions fish > ~/.config/fish/completions/mago.fish mago generate-completions powershell > mago.ps1 # Update Mago to latest version mago self-update ``` ## Global CLI Options Global options that apply to all commands, specified before the subcommand. ```bash # Correct usage: global options BEFORE subcommand mago --workspace /path/to/project lint mago --config custom-mago.toml lint mago --php-version 8.3 analyze mago --threads 4 lint mago --colors=never lint # always, never, auto mago --allow-unsupported-php-version lint # Environment variables export MAGO_LOG=debug # debug, info, warn export MAGO_EDITOR_URL="vscode://file/%file%:%line%:%column%" # Exit codes # 0 = Success, no issues found # 1 = Issues found that need attention # 2 = Tool error (configuration, I/O, parse failure) ``` ## GitHub Actions Integration Automate code quality checks in GitHub workflows using the setup-mago action or Docker image. ```yaml # .github/workflows/mago.yml name: Mago Code Quality on: push: pull_request: jobs: mago: name: Run Mago Checks runs-on: ubuntu-latest steps: - name: Checkout Code uses: actions/checkout@v4 - name: Setup PHP with Composer cache uses: shivammathur/setup-php@v2 with: php-version: "8.4" coverage: none tools: composer env: COMPOSER_ALLOW_SUPERUSER: 1 - name: Install Composer Dependencies run: composer install --prefer-dist --no-progress - name: Setup Mago uses: nhedger/setup-mago@v1 - name: Run Mago run: | mago format --check mago lint --reporting-format=github mago analyze --reporting-format=github ``` ## Pre-commit Hook Integration Configure pre-commit hooks to format and lint staged files before commits. ```bash #!/bin/sh # .git/hooks/pre-commit # Format staged files mago fmt --staged # Lint staged files mago lint --staged --fix # Exit with lint status exit $? ``` ## Summary Mago provides a unified, high-performance toolchain for PHP development that replaces multiple separate tools (PHP-CS-Fixer, PHPStan, Psalm, PHP_CodeSniffer, deptrac, arkitect) with a single binary. The formatter ensures consistent PER-CS compliant code style, the linter catches stylistic issues and code smells with auto-fix capabilities, the analyzer performs deep static type checking, and the guard enforces architectural boundaries and coding conventions. All tools share a common configuration format and reporting system. Integration patterns include CI/CD pipelines through GitHub Actions with `--reporting-format=github` for inline annotations, pre-commit hooks using `--staged` flags, editor integration via LSP-compatible diagnostics, and Docker containers for zero-install environments. The baseline feature enables gradual adoption in existing codebases by capturing current issues and preventing new ones. Configuration can be customized per-tool with specific excludes, rule settings, and strictness levels, allowing teams to adopt Mago incrementally while maintaining full control over which checks apply to different parts of their codebase.