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

v0.5.0 2025-11-07 09:46 UTC

README

PHP Version CI

📦 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 IssueName sparingly 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 itself
  • composer analyze – run PHPStan
  • composer fix – apply coding standards

The CI workflow mirrors these steps.

🤝 Contributing

  1. Fork and branch from main.
  2. Keep fixtures under tests/Fixtures/<RuleName>; positive and negative cases should be explicit.
  3. Add a TestDox description for every scenario so failures read naturally in CI output.
  4. Run composer test before submitting a PR.

📄 License

MIT