mouf/utils.common.conditioninterface
A small PHP utility from the Mouf framework that defines a ConditionInterface: a simple contract requiring a single method to evaluate whether a condition is met. Implement it to build reusable, composable conditions that can be checked uniformly across your application code.
Install and read the whole package first. This is a five-file, sub-100-line library, so there is no reason to skim:
composer require mouf/utils.common.conditioninterface
The namespace is Mouf\Utils\Common\ConditionInterface. Open src/ConditionInterface/ConditionInterface.php first — it is the entire contract:
interface ConditionInterface
{
public function isOk(): bool; // older tags may omit the return type; check the tag you install
}
Then read AndCondition, OrCondition and NotCondition in the same folder. That is the whole API surface. Anything else you need, you will write yourself.
First use case: replace nested boolean flags in a domain rule. Pick a single "can the user do X" check in your codebase — checkout eligibility is a good one — and express it as conditions instead of a chain of ifs:
use Mouf\Utils\Common\ConditionInterface\ConditionInterface;
use Mouf\Utils\Common\ConditionInterface\AndCondition;
use Mouf\Utils\Common\ConditionInterface\NotCondition;
final class CartHasItems implements ConditionInterface
{
public function __construct(private Cart $cart) {}
public function isOk(): bool { return $this->cart->lines()->isNotEmpty(); }
}
final class UserIsVerified implements ConditionInterface
{
public function __construct(private User $user) {}
public function isOk(): bool { return $this->user->hasVerifiedEmail(); }
}
$canCheckout = new AndCondition([
new CartHasItems($cart),
new UserIsVerified($user),
new NotCondition(new CartIsLocked($cart)),
]);
if ($canCheckout->isOk()) { /* ... */ }
Where to look next: the package was built for Mouf's DI container, so the docs site is about wiring, not about the interface. In a Laravel/Symfony app you can ignore that entirely — the interface plus two composites are the deliverable.
Keep isOk() pure and cheap. No writes, no dispatching events, no throwing on a failed check. It is a predicate, and composites may call it several times or short-circuit past it. If a condition needs remote data, resolve it in the constructor and cache it on the instance.
Compose, don't inherit. The library's only real value is AndCondition / OrCondition / NotCondition. Build a small vocabulary of atomic conditions (FeatureEnabled, WithinBusinessHours, HasPermission) and assemble them per use case. Atomic classes stay one-liners and are trivially unit-testable without mocks.
Build conditions from config, not from code, when the rule is operational. A factory that maps a config array to condition instances lets ops change rules without a deploy:
final class ConditionFactory
{
/** @param array<int, array{class: class-string, args?: array}> $specs */
public function fromSpecs(array $specs): ConditionInterface
{
return new AndCondition(array_map(
fn (array $s) => app()->makeWith($s['class'], $s['args'] ?? []),
$specs
));
}
}
Use it as a specification in your query/validation layers. Two reliable integration points in a Laravel app:
Gate::define('checkout', fn (User $u) => $this->canCheckout->isOk()); — keeps authorization logic out of controllers and out of Blade.Add a "why" wrapper rather than extending the interface. Since isOk() only returns a bool, wrap it when you need diagnostics:
final class EvaluatedCondition
{
/** @param list<ConditionInterface> $failed */
public function __construct(public bool $ok, public array $failed = []) {}
}
Evaluate the atoms yourself and record which ones returned false. This is worth doing the moment a user asks "why can't I check out?".
Bind composites in the container per feature. Register AndCondition instances as singletons keyed by a name (app()->bind('conditions.checkout', ...)) so tests can swap one atom without rebuilding the tree.
AndCondition with zero conditions is vacuously true; an OrCondition with zero conditions is false. Confirm this against the source of the tag you install — it matters a lot when your condition list comes from config and the config array happens to be empty. Guard at the factory level.app/Support/Conditions/ and deleting the dependency is a perfectly reasonable outcome, and frees you to add return types and readonly.composer.json's require before adding it to a modern project. Older tags may pin a low PHP constraint; if composer require refuses, either relax with --ignore-platform-req=php (and verify with a quick test run) or vendor the files instead of fighting the resolver.Result type, no reasons. Any "which condition failed?" UI has to be built on top. Don't be tempted to add a getMessage() to ConditionInterface — you would fork the contract and lose compatibility with anything Mouf-shaped.OrCondition is a performance decision. Short-circuits left to right. Put cheap, frequently-true atoms first; put database or HTTP-backed atoms last.isOk() mutates state, AndCondition will call it a different number of times depending on earlier results. This is the most common bug in hand-rolled conditions; keep them side-effect free.ConditionInterface is a generic name — alias on import (use ... as MoufCondition) if you also have a domain ConditionInterface of your own.XorCondition or AtLeastNConditions, write them as ten-line classes next to the built-ins — they compose the same way.How can I help you explore Laravel packages today?