azjezz/psl
PSL (PHP Standard Library) offers a consistent, well-typed set of safer, async-ready APIs to replace PHP primitives. Covers async, collections, networking, I/O, cryptography, terminal UI, and type-safe data validation with predictable errors.
EitherOrBoth is a three-variant disjoint union: Left, Right, or Both. It models a value that may be present on either of two sides, or on both simultaneously.
Unlike Either (which carries a loose "one vs the other" convention where Right is the happy path), EitherOrBoth treats all three variants as equal citizens. No side is privileged. The canonical use case is a three-way diff between two collections -- insert / delete / update events -- but the type is useful anywhere two partially-overlapping sources describe the same domain.
Left<TLeft> -- only the left value is presentRight<TRight> -- only the right value is presentBoth<TLeft, TRight> -- both sides are present simultaneouslyEitherOrBoth<TLeft, TRight> -- the common interface all three implementInspired by Rust's itertools::EitherOrBoth and Haskell's Data.These.
Reach for EitherOrBoth any time two collections, streams, or sources describe the same domain with potential partial overlap:
Psl\Iter\merge_join_by or merge_join_by_key to emit insert / delete / update events.Left = key only in defaults, Right = key only in overrides, Both(default, override) = key in both (inspect the conflict before resolving).FULL OUTER JOIN is the mental model.Use the concrete classes or the left() / right() / both() free functions:
@example('types/either-or-both-creating.php')
is* vs has*EitherOrBoth has two tiers of predicates. The exclusive isLeft() / isRight() / isBoth() each return true for exactly one variant. The inclusive hasLeft() / hasRight() return true whenever the corresponding side is present -- so hasLeft() is true for both Left and Both.
@example('types/either-or-both-predicates.php')
@example('types/either-or-both-extracting.php')
mapLeft() and mapRight() each transform one side, leaving the other untouched. On Both, only the addressed side changes. mapAny() applies two closures, one per side. map() applies the same closure to whichever side(s) are present -- useful when both sides have the same type:
@example('types/either-or-both-transforming.php')
proceed() takes three closures -- one per variant -- and dispatches to exactly one. Arguments are positional: left, right, then both (no happy-path convention applies here, since the three variants are equal citizens):
@example('types/either-or-both-proceed.php')
swap() flips Left into Right and vice versa. On Both(l, r) it produces Both(r, l):
@example('types/either-or-both-swap.php')
apply() runs a single side-effect closure on whichever side(s) are present and returns the value unchanged. On Both it runs twice, once per side -- same invocation shape as map(). Useful for logging inside a pipeline:
@example('types/either-or-both-apply.php')
EitherOrBoth is the element type of Psl\Iter\merge_join_by (sorted inputs, O(1) memory on first traversal) and Psl\Iter\merge_join_by_key (keyed inputs, O(|right|) memory). Both return a rewindable Iter\Iterator. The four most common composition patterns -- side effects, pure mapping, filtering before dispatch, and per-side transformation -- all fall out naturally:
@example('types/either-or-both-composing.php')
If "both present" is a meaningful state in your domain, use EitherOrBoth. If it cannot occur, use Either.
See src/Psl/EitherOrBoth/ for the full API.
How can I help you explore Laravel packages today?