php-standard-library/either-or-both
A three-variant disjoint union type for PHP. EitherOrBoth models values that may be present on the left, the right, or both sides at once, giving you a type-safe way to express partial successes, asymmetric results, and paired data without nullable workarounds.
Installation
composer require php-standard-library/either-or-both
Add to composer.json under require or require-dev based on use case.
First Use Case: Handling Optional Values
use PhpStandard\EitherOrBoth\EitherOrBoth;
// Create a Left-only value
$left = EitherOrBoth::left('error_message');
// Create a Right-only value
$right = EitherOrBoth::right(['user_data']);
// Create a Both value
$both = EitherOrBoth::both('error_message', ['user_data']);
Where to Look First
Validation with Side Effects
function validateUserInput(string $input): EitherOrBoth {
if (empty($input)) {
return EitherOrBoth::left('Input cannot be empty');
}
if (strlen($input) > 100) {
return EitherOrBoth::both('Input too long', ['truncated' => substr($input, 0, 100)]);
}
return EitherOrBoth::right(['validated' => $input]);
}
Chaining with match()
$result = validateUserInput($_POST['name']);
$processed = $result->match(
fn(string $error) => EitherOrBoth::left($error),
fn(array $data) => EitherOrBoth::right($data),
fn(string $error, array $data) => EitherOrBoth::both($error, $data)
);
Integration with Laravel
EitherOrBoth from rules() or authorize().
public function rules(): array {
return [
'name' => ['required', 'max:100'],
];
}
public function withValidator($validator) {
$validator->after(function ($validator) {
if ($validator->errors()->any()) {
return EitherOrBoth::left($validator->errors()->first());
}
return EitherOrBoth::right(null);
});
}
API Responses
return response()->json(
$result->match(
fn(string $error) => ['success' => false, 'error' => $error],
fn(array $data) => ['success' => true, 'data' => $data],
fn(string $error, array $data) => [
'success' => false,
'error' => $error,
'partial_data' => $data
]
)
);
Service Layer Pattern
class UserService {
public function createUser(array $data): EitherOrBoth {
$validation = $this->validate($data);
return $validation->match(
fn($error) => EitherOrBoth::left($error),
fn($validData) => $this->save($validData),
fn($error, $partialData) => EitherOrBoth::both($error, $partialData)
);
}
}
Type Safety
Both variant is not a tuple or array. Use match() to extract values explicitly:
$result->match(
fn($left) => $left,
fn($right) => $right,
fn($left, $right) => [$left, $right] // Explicit pairing
);
Immutable Values
// ❌ Anti-pattern: Modifying after creation
$both = EitherOrBoth::both('error', ['data']);
$both->getRight()['data'] = 'new_value'; // No effect!
Null Handling
EitherOrBoth does not support null as a valid value. Use Option types (e.g., php-standard-library/option) for nullable cases.Performance
EitherOrBoth in loops. Prefer early returns or flat maps:
// ❌ Inefficient
$result = EitherOrBoth::right([]);
foreach ($items as $item) {
$result = $result->flatMap(fn($acc) => $this->process($item)->map(fn($v) => [...$acc, $v]));
}
// ✅ Better
$results = collect($items)->map(fn($item) => $this->process($item));
Inspecting Values
Use match() with var_dump for debugging:
$result->match(
fn($left) => var_dump($left),
fn($right) => var_dump($right),
fn($left, $right) => var_dump([$left, $right])
);
Laravel Tinker Test logic interactively:
php artisan tinker
>>> $result = EitherOrBoth::both('error', ['data']);
>>> $result->match(...);
Custom Matchers Extend functionality with static methods:
EitherOrBoth::match(
$eitherOrBoth,
fn($left) => $this->handleLeft($left),
fn($right) => $this->handleRight($right),
fn($left, $right) => $this->handleBoth($left, $right)
);
Laravel Service Provider
Bind EitherOrBoth to the container for dependency injection:
$this->app->bind(EitherOrBoth::class, function () {
return new EitherOrBoth();
});
Testing
Use assertSame() for strict comparisons in PHPUnit:
$this->assertSame(
EitherOrBoth::both('error', ['data']),
EitherOrBoth::both('error', ['data'])
);
strict_types=1 for type safety.How can I help you explore Laravel packages today?