haspadar / psalm-eo-rules
Psalm plugin providing Elegant Objects rules: forbids static, null, isset, empty, instanceof, promotes immutability and object composition
Installs: 27
Dependents: 1
Suggesters: 0
Security: 0
Stars: 0
Watchers: 0
Forks: 0
Open Issues: 0
pkg:composer/haspadar/psalm-eo-rules
Requires
- php: ^8.2
- ext-simplexml: *
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.60
- nunomaduro/collision: ^8.0
- phpstan/phpstan: ^1.12
- phpunit/phpunit: ^10.5
- rector/rector: ^1.2
- roave/security-advisories: dev-latest
- vimeo/psalm: ^6.13
This package is auto-updated.
Last update: 2025-11-07 09:47:58 UTC
README
📦 About
Psalm EO Rules is a Psalm plugin that codifies the principles from Elegant Objects. Every rule enforces immutability, explicit composition, and clear ownership.
The plugin focuses on eliminating hidden shared state, encouraging immutable objects, and making composition and contracts explicit.
⚙️ Installation
composer require --dev haspadar/psalm-eo-rules
Enable the plugin in psalm.xml:
<psalm> <plugins> <pluginClass class="Haspadar\PsalmEoRules\Plugin"/> </plugins> </psalm>
Requirements: PHP 8.1+ and Psalm 5.25 or newer.
Suppressions are supported for every rule listed below. Use
@psalm-suppress IssueNamesparingly when a deviation is intentional.
🧭 Rules
| Issue code | Trigger | EO rationale |
|---|---|---|
NoStaticMethodDeclaration |
Declaring a static function |
Static helpers break encapsulation and make behaviour context dependent |
NoStaticProperty |
Declaring or reading a static property | Shared state hides dependencies and produces hidden coupling |
NoMutableProperty |
Property declared without the readonly flag |
Objects should be immutable after construction |
NoNullableType |
Parameter typed as ?Type |
Optional behaviour should be modelled explicitly (Optional, Null Object, Either, etc.) |
NoNull |
Using null in returns, assignments, or as function/method arguments |
null represents absence and breaks object integrity. Prefer explicit Optional or NullObject |
NonFinalOrAbstractClass |
Class that is neither final nor abstract |
Every class should either be closed for inheritance or clearly designed for extension |
NoInterfaceImplementation |
Concrete class that does not implement any interface | Keeps polymorphism explicit and substitution possible |
NoTraitUsage |
Using traits in a class | Traits blur object boundaries; prefer composition or delegation |
NoConstructorException |
throw statements inside a constructor |
Constructors must not fail; delegate validation to factories |
NoProtected |
Protected methods or properties | Without subclassing there is no need for protected members |
🚀 Usage Tips
- Add selected suppressions near the expression or class you need to relax:
/** @psalm-suppress NoEmpty */. - Combine with Psalm baselines to track legacy violations separately from new code.
- Rules rely on Psalm’s standard autoloader; ensure Composer’s autoload file is available when Psalm runs.
🧪 Tests
Each rule has a dedicated PHPUnit suite that executes Psalm against curated fixtures and asserts on the reported issues. Run everything locally with:
composer test
Additional helpers:
composer psalm– run Psalm on the plugin itselfcomposer analyze– run PHPStancomposer fix– apply coding standards
The CI workflow mirrors these steps.
🤝 Contributing
- Fork and branch from
main. - Keep fixtures under
tests/Fixtures/<RuleName>; positive and negative cases should be explicit. - Add a TestDox description for every scenario so failures read naturally in CI output.
- Run
composer testbefore submitting a PR.