Download the PHP package splitstack/invariants without Composer
On this page you can find all versions of the php package splitstack/invariants. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.
Download splitstack/invariants
More information about splitstack/invariants
Files in splitstack/invariants
Package invariants
Short Description Framework-free invariant enforcement with hydration policies (Strict, Lenient, Quarantine, AutoCorrect). Extracted from splitstack/laravel-domainable.
License MIT
Informations about the package invariants
Splitstack Invariants
Framework-free invariant enforcement with hydration policies. For lightweight domain modeling in PHP 8.4+.
- Zero dependencies
- Manual-first: call
Invariant::make(...)->assert()wherever you need it. - Optional auto-discovery: a trait finds every invariant method on a class by reflection and runs them with one call.
Install
Disclaimer
This package is in alpha. The API might evolve, and the docs might be incomplete. Please open an issue if you find a bug or have a feature request. We can't guarantee there won't be breaking changes, but we will try to keep them to a minimum and document them in the changelog.
The two tiers
Tier 1: core, manual, no reflection
A touched rule needs a subject implementing EnforcesInvariants (see
below). A touchless rule needs nothing.
Tier 2: reflection auto-discovery (opt-in)
Define methods that return Invariant; call assertInvariants() once to run
them all. It discovers them by return type so you don't have to maintain a list.
Auto-discovery, not auto-invocation. This package finds and runs your invariants when you call
assertInvariants(). It does not wrap your methods to call it for you. That would require an interception proxy, which is out of scope here.laravel-domainable'sEntityproxy provides that automatic behavior on top of the same core.
Hydration policies
The policy: argument decides what happens when a rule fails:
| Policy | Behavior on violation | Requires |
|---|---|---|
Strict |
throws StrictViolationException (default) |
— |
Lenient |
ignores the violated property, silent afterward | touches |
Quarantine |
calls $subject->quarantine($message), does not throw |
subject |
AutoCorrect |
rewrites each touched property to default |
touches + default |
AutoCorrect writes via property_exists() || __set, so it works on plain
objects with public properties and on objects exposing attributes through a
magic __set (Eloquent models, proxies).
Quarantine and AutoCorrect need a subject
assert() is typed against EnforcesInvariants, so any invariant with
touches (which includes every Quarantine and AutoCorrect rule) must be
asserted against a subject implementing that contract. AutoCorrect
additionally needs the touched property to be writable (a public property or a
magic __set).
Quarantine: mark, don't throw
Quarantine sets a flag on the instance and keeps going. It does not throw
and it does not remove anything. Acting on the flag is the caller's job:
this package has no repository, so nothing consumes isQuarantined() for you.
Just want the quarantine flag without reflection-based discovery? Use
HasQuarantine instead of AssertsInvariants.
AutoCorrect: repair in place
AutoCorrect rewrites each touched property to default when the rule fails,
then continues without throwing. It requires both touches and a non-null
default (the value written).
Very useful for batch processing or when you have legacy data that you KNOW is wrong but still want to manipulate.
Lenient mode
Will avoid throwing. Useful when you want to check invariants and record events without interrupting the flow of your program, for example in logging or monitoring scenarios.
StateGuard for ad-hoc state checks
StateGuard is used when you want to declare reusable, named state checks
evaluated on demand. Ideal to define guards for specific but repetitive conditions.
Example
StateGuards are just invariants
Dispatching events on violation
You can have a violated invariant fire an event through your own bus. The
package never ships a bus and never implements the dispatch: you point it at a
dispatcher class (and optionally a method name), and it resolves and calls it
for you when assertInvariants() runs.
Declare the dispatcher on the class and the payload on the invariant method:
When statusRequiresInProgress is violated (for any policy, including the
non-throwing ones), the package reads the named subject fields into a payload
and hands it to EventBus::dispatch($event).
Payload shape. With just a field list, a built-in
Splitstack\Invariants\Events\InvariantViolated event is dispatched:
To dispatch your own domain event instead, name it. It's built with named args
pulled from with, so its constructor params must match the property names:
Setting the event on the rule. You can skip the method attribute and hand
Invariant::make() the event directly, either as a ready-made instance or a
class-string. This wins over an #[InvariantEvent] on the method:
An instance is dispatched as-is. A class-string is built with named args from an
#[InvariantEvent([...])] field list if one is present, otherwise with no args.
An invariant with no #[InvariantEvent] and no event on the rule dispatches
nothing, even on a class that declares #[DispatchesEvents].
Resolving the dispatcher. By default, a static via method is called
statically (EventBus::dispatch($event)); otherwise the package does
new EventBus(). To use a container, register a resolver once at boot:
Contract
EnforcesInvariants is a three-method interface: assertInvariants(),
quarantine(), isQuarantined(). The AssertsInvariants trait satisfies all
three; HasQuarantine satisfies the last two.
Testing
License
MIT