Configuration reference

Settings live under [guard] in mago.toml. The configuration has two parts: [guard.perimeter] for dependency rules and [[guard.structural.rules]] for structural conventions.

Top-level options

OptionTypeDefaultDescription
modestring"default"Which checks to run. One of "default", "structural", "perimeter".
excludesstring[][]Paths or glob patterns to exclude from analysis. Additive to [source].excludes.
baselinestringunsetPath to a baseline file. Equivalent to passing --baseline on every run.
baseline-variantstring"loose"Format for newly generated baselines. "loose" (count-based) or "strict" (exact line matching). See baseline.
minimum-fail-levelstring"error"Minimum severity that causes a non-zero exit. One of "note", "help", "warning", "error". Overridden by --minimum-fail-level.

mode controls which half of the guard runs:

  • "default" runs both halves.
  • "structural" runs only structural checks.
  • "perimeter" runs only perimeter checks.
[guard]
mode = "structural"

The --structural and --perimeter flags override the configured mode. See the command reference.

excludes here is added to whatever you set in [source].excludes; it never narrows the global list.

[source]
excludes = ["cache/**"]

[guard]
excludes = ["src/ThirdParty/**"]

Perimeter guard

The perimeter section defines dependency rules between parts of the project.

[guard.perimeter]
layering = [
    "CarthageSoftware\\Domain",
    "CarthageSoftware\\Application",
    "CarthageSoftware\\UI",
    "CarthageSoftware\\Infrastructure",
]

[guard.perimeter.layers]
core = ["@native", "Psl\\**"]
psr = ["Psr\\**"]
framework = ["Symfony\\**", "Doctrine\\**"]

[[guard.perimeter.rules]]
namespace = "CarthageSoftware\\Domain"
permit = ["@layer:core"]

[[guard.perimeter.rules]]
namespace = "CarthageSoftware\\Application"
permit = ["@layer:core", "@layer:psr"]

[[guard.perimeter.rules]]
namespace = "CarthageSoftware\\Infrastructure"
permit = ["@layer:core", "@layer:psr", "@layer:framework"]

[[guard.perimeter.rules]]
namespace = "CarthageSoftware\\Tests"
permit = ["@all"]

layering

An ordered list of namespaces, from the most independent core down to the outermost layer. Each layer can only depend on layers defined before it. A dependency that points to a layer further out triggers a violation.

Layer aliases

[guard.perimeter.layers] defines reusable groups of namespaces and paths, referenced from rules with @layer:<name>.

Rules

Each [[guard.perimeter.rules]] table defines one rule:

  • namespace: the namespace this rule applies to. Either a namespace ending in \ or the special keyword @global for the global namespace.
  • permit: the dependencies that are allowed. Either a list of strings or a list of detailed objects.

permit values

permit accepts paths. A path can be a keyword, a namespace, a symbol, or a glob pattern.

PathDescription
@globalSymbols defined in the global namespace.
@allAny symbol anywhere in the project, including vendor packages. Useful for tests.
@self / @thisAny symbol within the same root namespace as the rule's namespace.
@native / @phpPHP's built-in functions, classes, and constants.
@layer:<name>All namespaces and paths in the named alias from [guard.perimeter.layers].
App\Shared\\**Glob pattern. * matches a single namespace segment, ** matches zero or more.
App\ServiceExact fully qualified symbol name.
App\Service\\Exact namespace. Allows symbols directly within it.

You can narrow a permission by symbol kind using an object form:

[[guard.perimeter.rules]]
namespace = "DoctrineMigrations\\"
permit = [{ path = "@all", kinds = ["class-like"] }]
  • path: any of the path forms above.
  • kinds: which kinds of symbols are permitted. Values are class-like (covers classes, interfaces, traits, enums), function, constant, and attribute.

Dependency restrictions

[[guard.perimeter.restrictions]] defines target-oriented restrictions. Use these when a dependency must only be used from selected namespaces, or must be forbidden from selected namespaces.

[[guard.perimeter.restrictions]]
dependency = "App\\Http\\Controllers\\Controller"
allow-from = ["App\\Http\\Controllers\\"]
kinds = ["class-like"]

[[guard.perimeter.restrictions]]
dependency = "Illuminate\\Foundation\\Bus\\Dispatchable"
deny-from = ["App\\"]
KeyDescription
dependencyRequired symbol selector. Accepts an exact fully qualified name, a namespace ending in \, or a glob pattern.
allow-fromOptional source namespace patterns. When non-empty, matching dependencies are only allowed from these namespaces.
deny-fromOptional source namespace patterns. Matching dependencies are forbidden from these namespaces.
kindsOptional dependency-kind filter. Values: class-like, function, constant, attribute. An empty list applies to every kind.

Restrictions are evaluated before ordinary permit rules and layering, so a permit cannot override a restriction. If both allow-from and deny-from match, deny-from wins. Matching allow-from only satisfies the restriction; ordinary perimeter rules and layering must still allow the dependency when configured. A configuration containing only restrictions allows unrelated dependencies; restrictions do not create an implicit allowlist.

Structural guard

[[guard.structural.rules]] defines structural conventions. Each entry combines selectors that pick which symbols to inspect with constraints that the selected symbols must satisfy.

[[guard.structural.rules]]
on = "CarthageSoftware\\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."

[[guard.structural.rules]]
on = "CarthageSoftware\\Domain\\**\\Repository\\**"
target = "interface"
must-be-named = "*RepositoryInterface"
reason = "Domain repository interfaces must follow a standard naming convention."

[[guard.structural.rules]]
on = "CarthageSoftware\\Infrastructure\\**\\Repository\\**"
target = "class"
must-be-final = true
must-extend = "CarthageSoftware\\Infrastructure\\Shared\\Repository\\AbstractRepository"
reason = "Infrastructure repositories must extend our abstract class."

[[guard.structural.rules]]
on = "CarthageSoftware\\Domain\\**\\Enum\\**"
must-be = ["enum"]
reason = "This namespace is designated for enums only."

Selectors

KeyDescription
onRequired. Glob pattern matching the fully qualified name of the symbols this rule applies to.
not-onOptional glob pattern excluding symbols that would otherwise match on.
targetOptional filter restricting the rule to one symbol kind. One of class, interface, trait, enum, function, constant.

Constraints

KeyDescription
must-beRestrict the selected namespace to contain only the listed symbol kinds. Values: class, interface, trait, enum, function, constant.
must-be-namedGlob pattern the symbol name must match (e.g. *Controller).
must-be-finalBoolean. true requires final; false forbids it.
must-be-abstractBoolean. true requires abstract; false forbids it.
must-be-readonlyBoolean. true requires readonly; false forbids it.
must-implementOne or more interfaces the class must implement.
must-extendA class the symbol must extend.
must-use-traitOne or more traits the symbol must use.
must-use-attributeOne or more attributes the symbol must carry.
only-public-methodsPublic method names that matched classes may declare.
reasonHuman-readable explanation shown in error messages.

Public method allowlists

only-public-methods applies to classes and lists allowed method names. Names are matched case-insensitively. The listed methods are allowed but are not required to exist.

[[guard.structural.rules]]
on = "App\\Http\\Controllers\\**"
target = "class"
only-public-methods = ["__construct", "__invoke"]

The rule checks public methods declared directly by each matched class, including methods with no explicit visibility because PHP treats them as public. Private and protected methods are unrestricted. Inherited methods and methods provided by traits are not checked.

Inheritance constraint shapes

must-implement, must-extend, must-use-trait, and must-use-attribute accept a single string, an array of strings (AND), or an array of arrays of strings (OR of ANDs). The literal "@nothing" forbids any value.

must-extend = "App\\BaseClass"

must-implement = ["App\\InterfaceA", "App\\InterfaceB"]

must-extend = [
    ["App\\AbstractA", "App\\AbstractB"],
    ["App\\AbstractC"],
]

must-implement = "@nothing"

↳ Edit this page →