# PHPat: writing architecture rules as PHPStan extensions

> carlosas/phpat is a PHPStan extension that turns architectural constraints into a natural language rule set, checked as part of a static analysis run rather than as a separate test suite. It ships as phpat/phpat on Packagist, is MIT licensed, and warns that 0.x minor versions can break the API.

**carlosas/phpat** — ✔️ PHP Architecture Tester - Easy architecture testing for PHP 

- Repository: https://github.com/carlosas/phpat
- Website: https://phpat.dev
- Stars: 1,285 · Forks: 61
- Language: PHP
- License: MIT
- Published: 2026-10-19 · Updated: 2026-10-19 · Language: en
- Canonical page: https://hysenlabs.com/projects/carlosas-phpat

## A static analysis extension, not a test framework

PHP Architecture Tester, usually shortened to PHPat, is a PHPStan extension. That single fact determines most of what follows, including what it can and cannot check. The project describes itself as a static analysis tool designed to verify architectural requirements, and the mechanism it offers is a natural language abstraction, which means you define your architectural rules in something that reads like sentences and the tool assesses whether your code complies.

That is a different approach from the one most PHP teams take. The common alternative is to write unit tests that assert on reflection, for example a test that fails if a class in the Domain namespace references something in the Infrastructure namespace. Those tests run in the test suite, they are slow relative to a static pass, and they usually fail with an assertion error rather than a diff. PHPat moves the same check into the analysis phase, which means the rule set lives next to your other PHPStan configuration and is evaluated on every run.

The audience is a PHP team that has already adopted PHPStan, since the badge in the README pins the expectation at PHPStan ^2.1, and that wants its layering rules enforced by the same tool that already finds type errors. The homepage is phpat.dev, the documentation lives at phpat.dev/getting-started, and the examples section at docs/examples.md is offered as a source of typical use cases.

The project is MIT licensed, its home page is phpat.dev, and it is distributed on Packagist under the name phpat/phpat.

## Installing it and activating the extension

Installation is a Composer dev dependency, because architecture rules are a development-time concern rather than a runtime one:

```bash
composer require --dev phpat/phpat
```

That step alone does not wire the extension into PHPStan. There are two ways to activate it, and the README gives both. The automatic route installs the PHPStan extension installer package:

```bash
composer require --dev phpstan/extension-installer
```

The installer discovers the extension and registers it without any configuration file changes. The manual route is a matter of including the shipped neon file from your phpstan.neon:

```neon
# phpstan.neon
includes:
    - vendor/phpat/phpat/extension.neon
```

Both are one step, and the choice is about whether your project already relies on the extension installer for other PHPStan extensions. If it does, adding PHPat to that mechanism is the consistent choice, since it means every extension is discovered the same way.

The repository layout backs this up. The extension.neon file sits at the repository root, next to helpers.php, which is the conventional name for a file that is autoloaded eagerly and provides shared helper functions to the rule implementations. The rest of the tree is src/ for the rules themselves, tests/ for the test suite, docs/ for the documentation source, ci/ for the pipeline configuration, and a mkdocs.yaml, which is what builds the site at phpat.dev.

## Why a natural language abstraction for rules

The design choice that defines PHPat is the natural language abstraction. Instead of a configuration DSL that looks like a data structure, the rules are written in a form the project presents as readable statements, and the tool assesses compliance in the code. The demo animation in the documentation assets is there to show that reading experience rather than to show a configuration file.

The value of that choice is not the syntax, it is the conversation it enables. An architecture rule such as the Domain layer must not depend on Infrastructure is the kind of rule that gets written down in a design document, argued about in a review, and then quietly violated. When the rule can be expressed in a form a developer reads without training, the person deciding whether a new dependency is acceptable can look at the rule itself rather than at a test that fails later.

The mechanical part sits underneath. PHPStan already builds a symbol graph of your code, with the namespace, class, and dependency relationships between them, and an extension can query that graph. So a rule about forbidden dependencies, or about which namespace a class may live in, is a question about the graph, answered during the analysis pass rather than by executing code. That also means the checks are fast, since nothing is run, and that they work on code paths a test would never execute.

The trade-off follows from the same place. A rule that depends on runtime behaviour, on the order of calls, or on a value computed at execution time, is not a question the static graph can answer. Architecture rules that describe structure are a good fit, and rules that describe sequences are not.

## What the rule set can and cannot express

The documentation is the place to find the specific rule types, and the repository points to the examples section for typical use cases. What the project description guarantees is the shape of the vocabulary: each rule is a natural language statement about the code, and the tool reports whether the code complies.

The important boundary is that the rules operate on the static structure of your project. Namespaces, classes, interfaces, and the dependency edges between them are all things PHPStan knows before your code runs. Things that only exist at runtime are not in that set. A rule about a class reaching into another namespace's private internals is a structural rule. A rule about a service being resolved from the container in a particular order is not.

The second boundary is that PHPat is an extension, so it inherits PHPStan's scope. Whatever PHPStan analyses, PHPat can reason about, and whatever you exclude from PHPStan is invisible to the architecture rules as well. That is a feature in one direction, since keeping the two consistent avoids a rule set that thinks it knows about code the analyser never loaded, and a footgun in another, because a rule can silently pass on a directory that was excluded from analysis. A team adopting this should check the excludes in phpstan.neon against the namespaces the rules talk about.

There is also a scope question the project answers plainly. The architecture rules live in your repository, in your configuration, next to the code they describe. They are reviewable in a pull request, they are versioned with the code, and they change when you decide the architecture has changed. That is different from a rule set that lives in a shared package and is updated on someone else's schedule.

## Against writing architecture tests by hand

The realistic alternative is what many PHP projects do already: write the checks as unit tests using reflection, or as a custom PHPStan rule written for one specific check. Both are real options, and both differ in approach rather than in effort.

A reflection-based test asks the class loader what a class depends on and asserts the answer. It runs in the test suite, it needs a fixture, and it fails with a message about a failed assertion rather than a diff of the offending file. A custom PHPStan rule gives you the same speed and the same reporting style as PHPat, but it is written for one check, it is a piece of PHP you maintain, and it does not come with the natural language layer. PHPat is essentially the generalisation of the second approach, with the rule authoring moved from PHP code into a declarative form.

The other comparison is with a different kind of tool, one that checks dependencies by parsing composer.json and other manifests rather than by analysing the code. That approach is faster still and needs no language server, but it only sees declared dependencies. A layer violation that goes through a service locator or a string class name is invisible to it, because nothing in the manifest records it. PHPat works from the code, so it sees the real edges, including the ones nobody wrote down.

The case for PHPat over both is the writing surface. A team that already has PHPStan in CI gets the checks for free on every run, in the same report, with a rule format a non-specialist can read. The case against is the API stability question below.

## The 0.x warning is the thing to plan around

The README carries a warning that deserves attention: the launch of early-stage releases, in the 0.x.x series, could break the API according to Semantic Versioning 2.0, and the project states that it is using the minor version for breaking changes until the stable 1.0.0 release. In other words, a minor bump is a breaking bump, and the usual assumption that 0.x patch versions are safe is the one thing that holds.

The release history shows how the project has been moving. Version 0.12.5 was published on 2026-09-15, 0.12.4 on 2026-03-17, and 0.12.3 on 2026-02-20, and the last push to the repository was on 2026-10-19. The gap between the 0.12.3 and 0.12.4 releases is under a month, while the 0.12.4 to 0.12.5 gap runs about half a year, so the cadence has been uneven.

For an adopter this means two concrete habits. Pin the exact version of phpat/phpat in your lock file and treat an upgrade as a change that can need rule edits, not as a routine dependency bump. And keep the rule set small enough to read when something breaks, because a rule file that nobody understands is a rule file nobody will fix.

The project is open to contributions, it is MIT licensed, and its sponsors list includes Claude and JetBrains. The point of the sponsor list is not the money, it is that this is a project with an outside team using it, which is the context in which a 0.x API is most likely to get feedback and stabilise.

## Conclusion

Adopt PHPat if you run PHPStan already and your architectural rules are the kind that can be written as declarative statements about namespaces and dependencies. Do not adopt it if you need runtime behaviour tests, since it is a static analysis extension and not a test runner. Verify first how your 0.x pin behaves across minor upgrades, because the project states that minor versions carry breaking changes until 1.0.0.

## FAQ

### What is PHPat and how does it work with PHPStan?

PHPat is a PHPStan extension for verifying architectural requirements. You define rules using a natural language abstraction and the tool assesses whether your code complies, using the dependency information PHPStan already gathers. The README pins the expectation at PHPStan ^2.1.

### How do I install and activate PHPat?

Require it as a dev dependency with composer require --dev phpat/phpat, then activate it either automatically with composer require --dev phpstan/extension-installer, or manually by including vendor/phpat/phpat/extension.neon in the includes section of your phpstan.neon.

### Does PHPat break APIs between minor versions?

Yes. The README warns that early-stage 0.x.x releases could break the API according to Semantic Versioning 2.0, and that the project uses the minor version for breaking changes until the stable 1.0.0 release. Pin the exact version and expect rule edits on a minor bump.

### Where can I find examples of PHPat rules?

The README points to the Examples section at docs/examples.md for typical use cases, and the full documentation is hosted at phpat.dev with the getting started page at phpat.dev/getting-started. The repository also contains a docs/ directory and a mkdocs.yaml used to build the site.

### Is PHPat a test framework?

No, it is a static analysis extension, so the checks run as part of PHPStan rather than as part of a test suite. The rules apply to the structure PHPStan analyses, and anything excluded from PHPStan analysis is invisible to the architecture rules as well.

## Sources

- [carlosas/phpat on GitHub](https://github.com/carlosas/phpat)
- [License: MIT](https://github.com/carlosas/phpat/blob/main/LICENSE)
- [Project website](https://phpat.dev)
- [README](https://github.com/carlosas/phpat/blob/main/README.md)
- [Releases](https://github.com/carlosas/phpat/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/carlosas-phpat
