PHPackages                             innis/coding-standards - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Utility &amp; Helpers](/categories/utility)
4. /
5. innis/coding-standards

ActivePhpstan-extension[Utility &amp; Helpers](/categories/utility)

innis/coding-standards
======================

PHPStan rules that enforce the Innis coding conventions: clean-architecture layering, banned traits, interface/collection/exception placement, value-object immutability, and naming smells.

v0.1.6(2w ago)082↓77.8%7MITPHPPHP ^8.4CI passing

Since Jul 3Pushed 1mo agoCompare

[ Source](https://github.com/johninnis/coding-standards)[ Packagist](https://packagist.org/packages/innis/coding-standards)[ RSS](/packages/innis-coding-standards/feed)WikiDiscussions master Synced 1w ago

READMEChangelogDependencies (11)Versions (8)Used By (7)

innis/coding-standards
======================

[](#inniscoding-standards)

[![CI](https://github.com/johninnis/coding-standards/actions/workflows/ci.yml/badge.svg)](https://github.com/johninnis/coding-standards/actions/workflows/ci.yml)

> These rules encode the conventions of the **Innis ecosystem**. They are deliberately opinionated and almost certainly not to everyone's taste — that is by design. They are published for anyone extending the Innis libraries, or who simply wants to hold their own code to the same standards.

PHPStan rules that enforce the Innis coding conventions mechanically, inside the `phpstan analyse` run every package already has. Because they run on the real parse tree and PHPStan's reflection, aliases, grouped `use`, inline fully-qualified names, and non-standard formatting are all handled correctly.

Installation
------------

[](#installation)

```
composer require --dev innis/coding-standards
```

Then include the extension in `phpstan.neon.dist` alongside the project's other includes:

```
includes:
    - vendor/innis/coding-standards/extension.neon
```

This manual `includes:` entry is required unless the project has [`phpstan/extension-installer`](https://github.com/phpstan/extension-installer) installed, in which case the rules are registered automatically and no `includes:` line is needed.

Requires PHP 8.4+ and PHPStan `~2.2.2`. It runs at any PHPStan level; the rules are independent of the level setting.

What it enforces
----------------

[](#what-it-enforces)

Every rule reports with a stable `innis.*` identifier so it can be targeted in `ignoreErrors`. There is no separate "warning" tier in PHPStan — everything below is a reported error — but the smell rules (the `*Suffix` names and `tooManyParameters`) and `portPlacement` are the ones you most often silence per case. Prefer a code-pinned `// Deliberate:` / ADR fence (see *Deliberate departures*) over a project-wide `ignoreErrors` for a genuine one-off exception.

IdentifierWhat it flags`innis.strictTypes`A file that declares a type but is missing `declare(strict_types=1)`.`innis.noTraits`Any `trait` (banned ecosystem-wide; share behaviour through an injected collaborator).`innis.cleanArchitecture`A `use` import that violates inward-only layering — Domain→Domain only; Application→Application/Domain; Infrastructure→Infrastructure/Application/Domain; Presentation→any. Catches cross-package leaks too.`innis.interfaceNaming`An `interface` whose name does not end in `Interface`.`innis.collectionPlacement`A `*Collection` class, or one extending `TypedCollection`, outside a `Collection/` namespace.`innis.collectionFlat`Sub-grouping under `Collection/` by concept (`Collection/Reference/…`).`innis.valueObjectImmutable`A concrete `ValueObject/` class that is not `final` or not a `readonly` class. Property-level `readonly` (the sanctioned memory-zeroing exception) reports with a tip so a human confirms the reason. `abstract` sum-type bases and fenced classes are exempt.`innis.failureNotThrowable`A `*Failure` (returned-outcome) type that is throwable — a fault uses the `*Exception` suffix.`innis.exceptionPlacement`A throwable / `*Exception` class outside an `Exception/` namespace.`innis.exceptionShape`An exception class that is neither `final` (leaf) nor `abstract` (base).`innis.repositoryInterfacesOnly`A concrete type in `Domain/Repository` (interfaces only).`innis.portPlacement`An `Application/Port/` interface whose implementation is an `Application/Service/` class the package constructs itself — a mis-filed internal collaborator; its interface belongs in `Application/Service/`.`innis.domainPurity`A `Domain/` namespace that reaches for impurity directly: a built-in that reads the clock, uses randomness, performs I/O, reads the environment, writes output, pauses, spawns a process or opens a socket; a zero-argument `DateTime`/`DateTimeImmutable`; or a request/session/global superglobal. Push the effect behind a port.`innis.tryFromReturnsNullable` / `innis.tryFromDoesNotThrow` / `innis.fromIsTotal`Named constructors follow PHP's from/tryFrom split. A `tryFrom*` parses untrusted input: it returns `?self` (or a `*Failure`) and never throws, so a missed failure is an analyser error. A `from*` is total, trusted construction: it may throw to assert an invariant and does not return nullable — a nullable `from*` is a `tryFrom*` under the wrong name.`innis.overrideAttribute`A method that implements an interface method or overrides a parent method without `#[\Override]`.`innis.promoteConstructorProperties`A `ValueObject/`, `DTO/` or `Entity/` constructor that declares a field and assigns a same-named parameter to it in the body, instead of promoting the property. Only a plain pass-through assignment is flagged.`innis.typedConstants`A class, interface or enum constant declared without a type.`innis.collectionContract`A concrete typed collection (a `*Collection`, or a leaf extending `TypedCollection`) that is not `final` or does not implement `IteratorAggregate` + `Countable`.`innis.errorSuffix`A non-throwable class named `*Error` — a returned outcome value uses the `*Failure` suffix (`\Error` is a `Throwable`).`innis.valueObjectAccessors`A property hook on a `ValueObject/`, or asymmetric visibility (`private(set)`) on a `ValueObject/` or `Entity/` — keep a uniform `getX()` read surface.`innis.noEmojis`An emoji anywhere in a source file (code, comment, or string).`innis.noSingleton`A static property typed as its own class, or a static `getInstance()` accessor — a singleton/service locator; inject an interface instead.`innis.ukEnglish`A US spelling in a declared identifier (type/method/property/parameter/constant/enum-case name); string values are left alone.`innis.collectionOverArray`A public signature that passes an array of an element for which a typed collection exists — pass the `Collection`, not a generic array.`innis.primitiveAtBoundary`A `string`/`int` parameter or property in Domain/Application whose name matches an existing value object — parse the primitive to that value object at the boundary and thread it through.`innis.equalsSelf`An equality method (`equals`/`isEqualTo`/`sameValueAs`/…) on a value object or entity whose parameter is not typed `self` — a widened parameter lets a sibling type be compared.`innis.transformationReturnsSelf`A transformation (`with*`/`add*`/`remove*`/`map`/`filter`/…) on an immutable value, entity, or collection that returns `void`/`bool`/`never` instead of a new instance.`innis.adapterSuffix` / `innis.managerSuffix` / `innis.serviceSuffix`A catch-all `*Adapter` / `*Manager` / `*Service` class name — name the class for what it does.`innis.tooManyParameters`A function, method or constructor with more than three parameters — decompose the unit rather than bundling arguments into a parameter object.Deliberate departures
---------------------

[](#deliberate-departures)

The rules that can have a legitimate exception honour the ecosystem's Chesterton's-Fence convention: a `// Deliberate: …` comment or an `ADR-NNNN` reference **pinned at the code** marks a justified departure and is skipped. The marker is read from the reported node's own comments (see ADR-0011), so it is scoped to exactly the unit it sits on — a fence on one method silences that method, not its siblings. Rules that also look at the enclosing class let a class-level fence cover its members; `tooManyParameters`is pinned to the method or function it flags, so a wide constructor is fenced on the constructor, not the class.

- **Structural / type-contract rules** (`valueObjectImmutable`, `repositoryInterfacesOnly`, `portPlacement`, `domainPurity`, `tryFromReturnsNullable`/`tryFromDoesNotThrow`/`fromIsTotal`, `promoteConstructorProperties`, `noSingleton`, `equalsSelf`, `transformationReturnsSelf`) honour a fence on the class (or, for the per-method ones, on the method).
- **Smell / opinion rules** (`tooManyParameters`, the `*Suffix` names, `primitiveAtBoundary`, `ukEnglish`) honour a fence too — on the method for a parameter-count smell, the class for a suffix smell, the method/property/class for a primitive-at-boundary, and the declaration (or its enclosing class) whose name is US-spelled for `ukEnglish`. Pin the fence at the smell site, with an ADR.

Layering, trait, naming, `overrideAttribute`, `typedConstants`, `collectionContract`, `valueObjectAccessors`, `noEmojis` and `collectionOverArray` are **always enforced** — a departure there is an immediate refactor, not a comment. Several rules also relax in test code: `ukEnglish`/`noEmojis`skip test files, `overrideAttribute` enforces only first-party contracts there (framework overrides like `setUp` are exempt), `tooManyParameters` exempts data-record constructors.

To silence a rule project-wide (rather than a single site), target its identifier:

```
parameters:
    ignoreErrors:
        - identifier: innis.serviceSuffix
```

Development
-----------

[](#development)

```
composer test          # phpunit + phpstan (self-analysis, dogfooding these rules)
composer analyse       # phpstan only
composer check-style   # php-cs-fixer dry-run
```

###  Health Score

43

—

FairBetter than 89% of packages

Maintenance93

Actively maintained with recent releases

Popularity13

Limited adoption so far

Community16

Small or concentrated contributor base

Maturity46

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~5 days

Total

7

Last Release

17d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/d2ca81e761ec3b2d4ce98ac59cd4394bb4d70ebf3d1e620ff154ffcfc7a34bfc?d=identicon)[innis](/maintainers/innis)

---

Top Contributors

[![johninnis](https://avatars.githubusercontent.com/u/242370111?v=4)](https://github.com/johninnis "johninnis (1 commits)")

###  Code Quality

TestsPHPUnit

Static AnalysisRector

Code StylePHP CS Fixer

### Embed Badge

![Health badge](/badges/innis-coding-standards/health.svg)

```
[![Health](https://phpackages.com/badges/innis-coding-standards/health.svg)](https://phpackages.com/packages/innis-coding-standards)
```

###  Alternatives

[deptrac/deptrac

Deptrac is a static code analysis tool that helps to enforce rules for dependencies between software layers.

3.0k9.8M298](/packages/deptrac-deptrac)[rector/rector-src

Instant Upgrade and Automated Refactoring of any PHP code

136411.0k14](/packages/rector-rector-src)[ssch/typo3-rector

Instant fixes for your TYPO3 PHP code by using Rector.

2613.3M491](/packages/ssch-typo3-rector)[ergebnis/rector-rules

Provides rules for rector/rector.

10264.7k56](/packages/ergebnis-rector-rules)[psalm/plugin-laravel

Psalm plugin for Laravel

3345.4M354](/packages/psalm-plugin-laravel)[tempest/framework

The PHP framework that gets out of your way.

2.3k37.6k21](/packages/tempest-framework)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
