Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Event Sourcing Phpstan Extension Laravel Package

patchlevel/event-sourcing-phpstan-extension

PHPStan extension for patchlevel/event-sourcing that validates aggregates before runtime: flags uninitialized and unused properties, write-only state, event recording inside apply methods, and state changes outside apply.

View on GitHub
Deep Wiki
Context7

Technical Evaluation

Architecture fit. This is not a runtime component — it is a PHPStan analysis extension that teaches static analysis to reason about patchlevel/event-sourcing aggregate internals. Architecturally it is a leaf-node dev dependency: it hooks into PHPStan's rule registry via extension.neon, contributes custom rules and type extensions for AggregateRoot/ChildAggregate subclasses, and ships no runtime code, no service providers, no config publishes, and no migrations. That makes it trivially removable and impossible to cause a production incident. The only architectural precondition is that the codebase already uses patchlevel/event-sourcing with aggregates that follow the library's apply/recordThat conventions — without that it is inert.

Integration feasibility. Very high feasibility and very low effort: one composer require --dev, plus a single includes: line in phpstan.neon unless phpstan/extension-installer is present (in which case it self-registers). The genuinely valuable rules from a product-risk standpoint are the two correctness guardrails: DontRecordWhenApplying (recording events inside apply produces duplicated events on replay — a data-integrity bug that is expensive to unwind post-hoc) and DontWriteStateWhenNotApplying (state written outside apply silently vanishes on reload — a correctness bug that typically surfaces as intermittent, hard-to-reproduce invariant violations). The property-initialization feature is a developer-experience win that removes the false positives that otherwise force teams to weaken PHPStan levels or litter @phpstan-ignore on aggregates. The unused/write-only property rules are design-hygiene checks that double as architectural guidance toward projections.

Technical risk. Low but non-zero, and concentrated in one place: version pairing with the host library. The rules match on the library's API surface and base-class types. If patchlevel/event-sourcing is upgraded without upgrading this extension, the failure mode is silent — the rules stop matching and report nothing, which is worse than an error because CI stays green while the guardrails are gone. This must be mitigated by treating the two packages as a lockstep pair in the dependency policy. Secondary risks: a single-maintainer, 1-star, 0-dependent package with a correspondingly thin bus factor (mitigated by MIT license and tiny scope — forkable in an afternoon); false positives on nested/child aggregates and on properties read only through dynamic access (__get, reflection, magic methods); and baseline churn on every extension minor bump, since new rules (the jump from 1.0 to 1.3 added three rule families) invalidate an existing PHPStan baseline.

Key questions.

  • Do we actually run patchlevel/event-sourcing in production, and how many aggregate classes are in scope? This is the binary go/no-go.
  • Which PHPStan level do we target, and does the value proposition (unblocking level: max on aggregates) apply, or are we already at a low level where the extension's benefit is marginal?
  • Who owns the lockstep-upgrade discipline between this extension and event-sourcing, and is that encoded in a Renovate/Dependabot grouping rule rather than a human's memory?
  • Do we have magic access, reflection, or trait-based state on aggregates today that would generate noisy false positives?
  • How many PHPStan configs exist across the repo (app, tests, packages), and is CI guaranteed to include the extension in all of them?

Integration Approach

Stack fit. Direct fit for any PHP project running patchlevel/event-sourcing — PHPStan is already the de-facto standard analyzer in Laravel and Symfony shops, and phpstan/extension-installer is commonly present, which reduces onboarding to a single composer command. No Laravel service provider, no Octane/queue/container interaction, no production surface. It belongs in the require-dev block of the root composer.json, not in shared internal packages, unless an internal package ships aggregates — in which case it belongs there too.

Migration path. Baseline-first, then ratchet:

  1. Add the dev dependency and the extension.neon include to the primary PHPStan config.
  2. Run vendor/bin/phpstan analyse and inspect the delta rather than blindly regenerating. Expect three classes of finding: (a) false positives on initialization, which the extension should actually be removing — a net reduction in errors; (b) real recording-in-apply and write-state-outside-apply violations, which are correctness bugs and should be fixed, not baselined; (c) unused/write-only property findings, which are design debt and can be baselined initially.
  3. Generate a baseline scoped by error identifier, never by message regex — message text changes between minor versions.
  4. Add the same include to every secondary config (phpstan-tests.neon, per-package configs, monorepo splits). A missing include in one config is the single most common integration failure and produces a false sense of coverage.
  5. Verify the wiring works by planting a deliberate violation in a throwaway aggregate and confirming it is flagged.

Compatibility. Requires PHPStan (and phpstan/extension-installer if used) at compatible versions, and — critically — an event-sourcing major/minor line that the extension's rules were written against. The 1.1.0 release dropped DCB-related checks because they never landed in event-sourcing 3.x and added PHP 8.5 support, which is a concrete illustration of how tightly the two version ranges track each other. Pin both, upgrade both, and re-run analysis before merging either bump. Also verify the includes: path resolves if a non-standard config.vendor-dir is configured.

Sequencing. Cheap and non-blocking, so sequence it early and in parallel with other work:

  • Week 1: dependency + include on the primary config; run against one representative aggregate module; triage findings into "fix now" (correctness) and "baseline now" (hygiene).
  • Week 2: propagate the include to all configs; land the identifier-scoped baseline; enable as a blocking CI gate.
  • Week 3+: burn down baseline entries for write-only/unused properties as refactors touch each aggregate; add a short team convention doc (validate in command method → record → apply assigns state only) so new aggregates are correct by construction.
  • Ongoing: group this package with event-sourcing in dependency automation so they are never upgraded independently.

Operational Impact

Maintenance. Negligible day-to-day cost: no services, no runtime, no data. The real maintenance obligation is policy, not code — keeping the extension and event-sourcing on matched version ranges and re-validating after each bump. Budget a small recurring allowance (roughly an hour per library upgrade cycle) for PHPStan runs, baseline review, and the deliberate-violation smoke test. Rule additions in minor releases (three new rule families landed between 1.0 and 1.3) will surface new findings, so treat each extension upgrade as a code-review event rather than an automated merge.

Support. Upstream support is effectively single-maintainer; with 1 star and zero dependents there is no community gravity to lean on. The mitigations are structural: MIT license, tiny surface area, and rules that are individually configurable (made so in 1.3.0 per PR #29), meaning you can disable one noisy rule without forking. If the package is ever abandoned, the escape hatch is to vendor the rule classes into an internal tooling repo — a day of work, not a quarter. Recommended posture: adopt, but do not build organizational dependency on its roadmap.

Scaling. Scales well and linearly with codebase size — PHPStan already dominates CI wall-clock time, and the extension's marginal cost is proportional to the number of aggregate classes only. Two scaling pinch points to watch: (a) result-cache invalidation on extension upgrades causes full re-analysis passes, which is noticeable in large monorepos and worth scheduling off-peak; (b) false-positive volume grows with the number of non-standard aggregate shapes (nested aggregates, magic access, trait-composed state), so the cost of adoption is disproportionately higher in legacy domains than in greenfield ones. Scope the initial rollout to the cleanest bounded context and expand outward.

Failure modes. Ranked by impact:

Failure Symptom Impact Mitigation
Silent rule mismatch after event-sourcing upgrade Rules match nothing; CI green; guardrails gone High — false confidence in correctness Lockstep version pinning + planted-violation smoke test in CI
Missing includes: in a secondary config Partial coverage; tests/fixtures unanalyzed Medium Single shared neon file included everywhere; audit configs on change
Message-regex ignores Ignores stop matching after reword; and suppress unrelated future errors Medium Identifier-based ignoreErrors only
Stale baseline after minor bump New real findings masked as pre-existing Medium Regenerate deliberately, reviewing new entries, never blind-regenerate
False positives on child aggregates / dynamic reads Developer friction; pressure to disable rules globally Medium Identifier-scoped ignores with rationale comments; keep typed getters on aggregates
Upstream abandonment No fixes for new PHP/PHPStan versions Low MIT + tiny scope; fork-in-place fallback

Ramp-up. Near-zero for anyone who already knows PHPStan — the mental model is "these are extra error identifiers." The real ramp-up is domain education, not tooling: developers must internalize why recording inside apply duplicates events and why write-only aggregate state belongs in a projection. Budget a 30-minute walkthrough and a one-page convention note in the contributing docs. The extension is unusually effective as a teaching device here, because every violation carries an explanation and a fix — treat it as an opinionated reviewer for event sourcing design, not merely a linter. New joiners get the benefit passively, since correctly structured aggregates pass and incorrect ones fail loudly in CI.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
mehedimi/laravel-amp-mail
happones/laravel-vue-i18n-generator
jwerd/inertia-table
siberfx/flysystem-wasabi
hollyit/token-replace
ippanelcom/laravel-sdk
adachsoft/changelog-linter
adachsoft/collection
payerurl/telegrambot-crypto-payment-instant-payout
dskripchenko/laravel-hybrid-search
riorizkyrainey/lara-ced
mehedimi/static-vel
zharp/zharp
nonsapiens/bigquery-model-sync
lgu-bayambang-ict-office/lgu-laravel-development-standards
andydefer/laravel-support
se7enxweb/flysystem
lampwick/laravel
jeslage/statamic-emojis
micahdshackelford/rmap-laravel