cooldevguys/phpstan-dhexpendency-rules

A set of rules for PHPStan based on the Hexagonal architecture layers dependency

Maintainers

Package info

github.com/CoolDevGuys/phpstan-dhexpendency-rules

Type:phpstan-extension

pkg:composer/cooldevguys/phpstan-dhexpendency-rules

Transparency log

Statistics

Installs: 198

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

1.1.1 2026-08-28 22:45 UTC

This package is auto-updated.

Last update: 2026-08-28 22:45:43 UTC


README

Latest Version PHP Version CI License

A set of additional PHPStan rules that help enforce the dependency flow of a hexagonal (ports & adapters) architecture โ€” so a stray use statement in the wrong direction gets caught in CI, not in code review. ๐Ÿšฆ

๐Ÿ“ The rule this package enforces

These rules assume the typical hexagonal architecture with 3 layers: Infrastructure, Application and Domain, and that dependencies must only ever flow one way:

Infrastructure  โžก๏ธ  Application  โžก๏ธ  Domain

In other words:

Layer May depend on May not depend on
๐Ÿ—๏ธ Infrastructure Application, Domain, external vendors (nothing forbidden)
โš™๏ธ Application Domain only Infrastructure
๐Ÿง  Domain itself only Infrastructure, Application

The Domain layer is the one that matters most to protect: it's your business logic, and it should never know that a database, an HTTP client, or a specific framework exists.

โœจ What you get

  • LayersDependencyFlowRule โ€” flags any use statement that points from a layer to a layer it isn't allowed to depend on (per the table above).
  • NoExternalVendorsAllowedRule (opt-in via vendorStrictMode) โ€” flags any use statement in the Application or Domain layers that imports a class from outside your own project vendor namespace (e.g. a third-party library), so your business logic only talks to your own code โ€” never directly to a framework or SDK. Infrastructure is always exempt, since it's exactly the layer meant to talk to the outside world.

๐Ÿ“ฆ Installation

โš ๏ธ Dev-only package. This is a static analysis tool โ€” its rules only run while phpstan analyse runs, never in your application's runtime code. Always install it with --dev (as below) so it lands in require-dev, not require, and never ships to production. If you forget the flag, Composer itself will notice (this package is tagged static analysis) and offer to re-run the command with --dev for you.

composer require --dev cooldevguys/phpstan-dhexpendency-rules

If you use PHPStan's extension installer, you're all set. ๐ŸŽ‰ Otherwise, register the rules manually in your phpstan.neon:

includes:
  - vendor/cooldevguys/phpstan-dhexpendency-rules/rules.neon

Requirements: PHP >=7.4 and phpstan/phpstan ^2.0.

โš™๏ธ Configuration

Add your project's values nested under a single dhexpendency key in phpstan.neon:

parameters:
  dhexpendency:
    myVendorName: CoolDevGuys
    infrastructureLayerName: Infra
    applicationLayerName: App
    domainLayerName: Dom
    vendorStrictMode: true
    ignoredExternalVendors: ['IgnoredVendor']
Parameter Type Required Default Description
myVendorName string โœ… โ€” Your project's top-level namespace segment (e.g. CoolDevGuys in CoolDevGuys\App\...). Must be a valid PHP identifier.
infrastructureLayerName string โœ… โ€” The namespace segment you use for the Infrastructure layer.
applicationLayerName string โœ… โ€” The namespace segment you use for the Application layer.
domainLayerName string โœ… โ€” The namespace segment you use for the Domain layer.
vendorStrictMode bool โŒ false Enables NoExternalVendorsAllowedRule (see above).
ignoredExternalVendors string[] โŒ [] Vendor namespaces exempted from vendorStrictMode. Only has an effect when vendorStrictMode is true.

The three layer names must all be different from each other, and every name above must be a valid PHP namespace segment (letters, digits, underscores; can't start with a digit) โ€” an invalid value fails fast with a clear error instead of silently misbehaving.

โฌ†๏ธ Upgrading from 1.x?

This is a breaking change (v2.0): parameters moved from the top level of parameters: into a nested dhexpendency: key, and NoExternalVendorsAllowedFromDomainRule was renamed to NoExternalVendorsAllowedRule (it always checked Application and Domain, not just Domain โ€” the new name matches what it actually does). To migrate, just move your existing six keys under dhexpendency: as shown above; nothing else changes, and no error messages or rule identifiers were affected, so any phpstan-baseline.neon you already generated stays valid.

๐Ÿงฉ How your namespaces should look

Each layer is identified by a segment in the namespace, placed after your vendor name and an optional "context" segment โ€” for example, with the config above:

CoolDevGuys\Infra\...          ๐Ÿ—๏ธ Infrastructure
CoolDevGuys\Billing\App\...    โš™๏ธ Application  (context: "Billing")
CoolDevGuys\Billing\Dom\...    ๐Ÿง  Domain

When a namespace could plausibly match more than one configured layer name at different depths, the leftmost (earliest, shallowest) match wins โ€” so a Domain module with an unrelated nested segment that happens to share a name with another layer is still correctly recognized as Domain. Files whose namespace doesn't start with your configured vendor name, or has no recognizable layer segment at all, are silently skipped by both rules (they're none of this package's business).

๐Ÿšจ What a violation looks like

// src/CoolDevGuys/App/PlaceOrder.php
namespace CoolDevGuys\App;

use CoolDevGuys\Infra\PaymentGatewayClient; // โŒ Application โ†’ Infrastructure
The layer <App> can not contain references to the layers: Infra.
Use statement: <CoolDevGuys\Infra\PaymentGatewayClient>
๐Ÿชช dhexpendencyRules.forbiddenLayerReference
// src/CoolDevGuys/Dom/Order.php
namespace CoolDevGuys\Dom;

use Ramsey\Uuid\Uuid; // โŒ Domain โ†’ external vendor (with vendorStrictMode: true)
The layer <Dom> can not contain references to the external vendor: Ramsey.
Use statement: <Ramsey\Uuid\Uuid>
๐Ÿชช dhexpendencyRules.externalVendorReference

Both errors carry a stable ๐Ÿชช identifier, so if you ever need to allow a specific case, you can target it precisely in phpstan.neon:

parameters:
  ignoreErrors:
    - identifier: dhexpendencyRules.externalVendorReference
      path: src/CoolDevGuys/Dom/LegacyOrder.php

๐Ÿงญ Scope & limitations

This package covers the classic 3-layer hexagonal setup with a single project vendor namespace. It does not support additional layers (e.g. a separate Presentation/UI layer) or checking more than one vendor namespace at once.

๐Ÿงช Development

composer install        # install dependencies
vendor/bin/phpunit       # run the test suite
make tests               # same, via the prerequisites target
make docker-run          # run the test suite inside php:7.4.33-cli-alpine (the minimum supported PHP)

Pull requests are welcome! Please make sure vendor/bin/phpunit passes before opening one.

๐Ÿ“„ License

MIT ยฉ Alejandro Romero โ€” see LICENSE.md.