Commands

osdd:check

Check that each layer only uses the layers it requires, and that no requirements form a cycle.

osdd:check reads the PHP files of every layer and fails when a layer uses a class of a layer it does not require in its composer.json, or when layers require each other. See Boundaries for the rule and what is read.

php artisan osdd:check
  ERROR  functional/orders uses functional/billing but does not require it.

  functional/orders/src/Checkout.php:7 ........ Functional\Billing\Models\Invoice
  functional/orders/tests/CheckoutTest.php:5 .. Functional\Billing\Models\Invoice

   WARN  functional/billing requires technical/core but does not use it.

  ERROR  1 boundary violation in 4 layers (1 unused requirement).

A cycle shows as ERROR Cycle in the layer requirements: functional/orders -> functional/users -> functional/orders.. Without violation, the command prints No boundary violation in 4 layers..

Options

OptionDescription
--layerOnly check these layers. Full or package name, repeated (--layer=orders --layer=billing) or comma-separated (--layer=orders,billing).
--transitiveAlso allow the layers that the required layers require. By default, only the layers in the layer's own require are allowed.
--formattext (default), json or github.
php artisan osdd:check --layer=orders,billing
php artisan osdd:check --transitive
php artisan osdd:check --format=json
php artisan osdd:check --format=github

Exit codes

CodeWhen
0No undeclared dependency and no cycle. Unused requirements are warnings and do not fail.
1At least one undeclared dependency or cycle; or an unknown --layer, an unknown --format, an invalid osdd.boundaries.ignore entry or an invalid layer composer.json.

JSON format

php artisan osdd:check --format=json
{
    "layers": ["functional/billing", "functional/orders", "functional/users", "technical/core"],
    "strict": true,
    "undeclared": [
        {
            "layer": "functional/orders",
            "uses": "functional/billing",
            "class": "Functional\\Billing\\Models\\Invoice",
            "file": "functional/orders/src/Checkout.php",
            "line": 7
        }
    ],
    "unused": [
        {"layer": "functional/billing", "requires": "technical/core", "section": "require"}
    ],
    "cycles": [
        ["functional/orders", "functional/users", "functional/orders"]
    ]
}
  • strict is false with --transitive.
  • file is relative to the project. Each file reports a class once, at its first reference.
  • section is require or require-dev.

GitHub format

--format=github prints GitHub Actions workflow commands, so each violation shows as an annotation on the pull request:

::error file=functional/orders/src/Checkout.php,line=7,title=Undeclared layer dependency::functional/orders uses Functional\Billing\Models\Invoice from functional/billing but does not require functional/billing.
::error title=Layer cycle::Cycle in the layer requirements: functional/orders -> functional/users -> functional/orders.
::warning file=functional/billing/composer.json,title=Unused layer requirement::functional/billing requires technical/core but does not use it.
2 boundary violations in 4 layers (1 unused requirement).

Add it as a step of the job that installs the application:

.github/workflows/tests.yml
- name: Check layer boundaries
  run: php artisan osdd:check --format=github

The annotation paths are relative to the Laravel root. Run the step from the repository root, or set working-directory when the application lives in a sub-folder.

Ignoring a dependency

osdd.boundaries.ignore lists class prefixes and layer pairs that osdd:check does not report. See Ignoring a dependency.