PHPackages                             solidframe/phpstan-rules - 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. solidframe/phpstan-rules

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

solidframe/phpstan-rules
========================

PHPStan rules for DDD, CQRS, and Event Sourcing architectural enforcement

v0.1.0(3mo ago)09MITPHP ^8.2

Since Apr 12Compare

[ Source](https://github.com/solidframe/phpstan-rules)[ Packagist](https://packagist.org/packages/solidframe/phpstan-rules)[ RSS](/packages/solidframe-phpstan-rules/feed)WikiDiscussions Synced 3w ago

READMEChangelog (1)Dependencies (1)Versions (2)Used By (0)

SolidFrame PHPStan Rules
========================

[](#solidframe-phpstan-rules)

PHPStan rules for DDD, CQRS, and Event Sourcing architectural enforcement.

Static analysis catches architectural violations before tests even run.

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

[](#installation)

```
composer require solidframe/phpstan-rules --dev
```

The rules are auto-registered via PHPStan's extension mechanism. No manual configuration needed.

Rules
-----

[](#rules)

### CQRS Rules

[](#cqrs-rules)

#### Command Handler Must Return Void

[](#command-handler-must-return-void)

Command handlers perform side effects and must not return values.

```
// OK
final readonly class PlaceOrderHandler implements CommandHandler
{
    public function __invoke(PlaceOrder $command): void { /* ... */ }
}

// ERROR: Command handler must return void
final readonly class PlaceOrderHandler implements CommandHandler
{
    public function __invoke(PlaceOrder $command): Order { /* ... */ }
}
```

#### Query Handler Must Not Return Void

[](#query-handler-must-not-return-void)

Query handlers must return data.

```
// OK
final readonly class GetOrderHandler implements QueryHandler
{
    public function __invoke(GetOrder $query): OrderDto { /* ... */ }
}

// ERROR: Query handler must return a value
final readonly class GetOrderHandler implements QueryHandler
{
    public function __invoke(GetOrder $query): void { /* ... */ }
}
```

#### Handler Must Be Invokable

[](#handler-must-be-invokable)

Handlers must implement `__invoke()` and have only one public method (besides `__construct`).

```
// OK
final readonly class PlaceOrderHandler implements CommandHandler
{
    public function __invoke(PlaceOrder $command): void { /* ... */ }
}

// ERROR: Handler must implement __invoke()
final readonly class PlaceOrderHandler implements CommandHandler
{
    public function handle(PlaceOrder $command): void { /* ... */ }
}

// ERROR: Handler must have only one public method
final readonly class PlaceOrderHandler implements CommandHandler
{
    public function __invoke(PlaceOrder $command): void { /* ... */ }
    public function anotherMethod(): void { /* ... */ }
}
```

#### Messages Must Be Final Readonly

[](#messages-must-be-final-readonly)

Commands and Queries must be declared as `final readonly`.

```
// OK
final readonly class PlaceOrder implements Command {}

// ERROR: Command must be final
class PlaceOrder implements Command {}

// ERROR: Command must be readonly
final class PlaceOrder implements Command {}
```

#### Messages Must Not Extend

[](#messages-must-not-extend)

Commands and Queries must not extend other classes. Use composition.

```
// OK
final readonly class PlaceOrder implements Command
{
    public function __construct(public string $orderId) {}
}

// ERROR: Commands/Queries must not extend other classes
final readonly class PlaceOrder extends BaseCommand implements Command {}
```

### DDD Rules

[](#ddd-rules)

#### Value Object Must Be Readonly

[](#value-object-must-be-readonly)

Value objects are immutable. The class must be declared as `readonly`.

```
// OK
final readonly class Email extends StringValueObject {}

// ERROR: Value object must be readonly
final class Email extends StringValueObject {}
```

#### No Direct Aggregate Construction

[](#no-direct-aggregate-construction)

Aggregate roots must be created via static factory methods, not `new`.

```
// OK
$order = Order::place($orderId, $customerId);

// ERROR: Use a static factory method instead of new
$order = new Order($orderId);
```

Construction inside the aggregate class itself is allowed.

### Event Sourcing Rules

[](#event-sourcing-rules)

#### Events Must Be Final Readonly

[](#events-must-be-final-readonly)

Domain events are immutable data structures.

```
// OK
final readonly class OrderPlaced implements DomainEventInterface {}

// ERROR: Event must be final and readonly
class OrderPlaced implements DomainEventInterface {}
```

#### Apply Method Must Exist

[](#apply-method-must-exist)

For every event recorded via `recordThat()`, a corresponding `apply{EventName}()` method must exist.

```
// OK
final class Order extends AbstractEventSourcedAggregateRoot
{
    public function place(): void
    {
        $this->recordThat(new OrderPlaced(/* ... */));
    }

    protected function applyOrderPlaced(OrderPlaced $event): void
    {
        // apply state change
    }
}

// ERROR: Missing method applyOrderPlaced
final class Order extends AbstractEventSourcedAggregateRoot
{
    public function place(): void
    {
        $this->recordThat(new OrderPlaced(/* ... */));
    }
    // no applyOrderPlaced method!
}
```

Configuration
-------------

[](#configuration)

Rules work out of the box with SolidFrame interfaces. To use with custom interfaces, override in your `phpstan.neon`:

```
parameters:
    solidframe:
        commandHandlerInterface: App\Cqrs\CommandHandler
        queryHandlerInterface: App\Cqrs\QueryHandler
        commandInterface: App\Cqrs\Command
        queryInterface: App\Cqrs\Query
        eventInterface: App\Event\DomainEvent
        valueObjectInterface: App\Ddd\ValueObject
        aggregateRootClass: App\Ddd\AggregateRoot
```

Rule Summary
------------

[](#rule-summary)

RuleIDAreaCommand handler returns void`solidframe.commandHandlerMustReturnVoid`CQRSQuery handler returns data`solidframe.queryHandlerMustNotReturnVoid`CQRSHandler is invokable`solidframe.handlerMustBeInvokable`CQRSHandler has single public method`solidframe.handlerSinglePublicMethod`CQRSCommand is final`solidframe.commandMustBeFinal`CQRSCommand is readonly`solidframe.commandMustBeReadonly`CQRSQuery is final`solidframe.queryMustBeFinal`CQRSQuery is readonly`solidframe.queryMustBeReadonly`CQRSMessage has no parent class`solidframe.messageMustNotExtend`CQRSValue object is readonly`solidframe.valueObjectMustBeReadonly`DDDNo direct aggregate construction`solidframe.noDirectAggregateConstruction`DDDEvent is final`solidframe.eventMustBeFinal`Event SourcingEvent is readonly`solidframe.eventMustBeReadonly`Event SourcingApply method exists for recorded events`solidframe.applyMethodMustExist`Event SourcingComplementary: ArchTest vs PHPStan Rules
----------------------------------------

[](#complementary-archtest-vs-phpstan-rules)

ConcernArchTestPHPStan RulesNamespace dependencies`doesNotDependOn()`—Class structure (final, readonly)`areFinal()`, `areReadonly()`Message/VO/Event rulesHandler return types—Command void, Query non-voidHandler conventions—Invokable, single public methodEvent apply methods—`applyMethodMustExist`Aggregate construction—`noDirectAggregateConstruction`Module isolationModular preset—Use both for comprehensive architectural enforcement.

Related Packages
----------------

[](#related-packages)

- [solidframe/core](../core) — DomainEventInterface
- [solidframe/cqrs](../cqrs) — Command, Query, Handler interfaces
- [solidframe/ddd](../ddd) — ValueObjectInterface
- [solidframe/event-sourcing](../event-sourcing) — AbstractEventSourcedAggregateRoot
- [solidframe/archtest](../archtest) — Complementary PHPUnit-based architecture tests

Contributing
------------

[](#contributing)

This repository is a read-only split of the [solidframe/solidframe](https://github.com/solidframe/solidframe) monorepo, auto-synced on every push to `main`. Issues, pull requests, and discussions belong in the monorepo.

###  Health Score

33

—

LowBetter than 72% of packages

Maintenance80

Actively maintained with recent releases

Popularity5

Limited adoption so far

Community2

Small or concentrated contributor base

Maturity36

Early-stage or recently created project

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

Unknown

Total

1

Last Release

103d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/97de2a466719393a21a8ce5caef247a1afb8aec5a8974248da0583f4197765b2?d=identicon)[abdulkadir-posul](/maintainers/abdulkadir-posul)

### Embed Badge

![Health badge](/badges/solidframe-phpstan-rules/health.svg)

```
[![Health](https://phpackages.com/badges/solidframe-phpstan-rules/health.svg)](https://phpackages.com/packages/solidframe-phpstan-rules)
```

###  Alternatives

[deptrac/deptrac

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

3.0k8.8M207](/packages/deptrac-deptrac)[phpstan/phpstan-doctrine

Doctrine extensions for PHPStan

67372.8M1.5k](/packages/phpstan-phpstan-doctrine)[ticketswap/phpstan-error-formatter

A minimalistic error formatter for PHPStan

87718.9k58](/packages/ticketswap-phpstan-error-formatter)[ec-europa/toolkit

Toolkit packaged for Drupal projects based on Robo.

40252.8k34](/packages/ec-europa-toolkit)[ergebnis/rector-rules

Provides rules for rector/rector.

10245.6k52](/packages/ergebnis-rector-rules)[johnbillion/wp-compat

PHPStan extension to help verify that your PHP code is compatible with a given version of WordPress

25166.1k18](/packages/johnbillion-wp-compat)

PHPackages © 2026

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