Reason Layer

“Everything that exists has a reason for its existence”

  —Leibniz, Principle of Sufficient Reason (1714)

Reason for Existence

ExpressDelivery can exist as such because it has express shipping capabilities. StandardDelivery can exist as such because it has standard shipping capabilities. This foundation for “why it can be in that existence” is the raison d’être.

The Reason Layer is a design pattern that expresses this raison d’être as a single object.

final readonly class ExpressDelivery
{
    public Fee $fee;

    public function __construct(
        #[Input] OrderData $order,             // Immanence
        #[Inject] ExpressShipping $reason      // Reason for existence
    ) {
        $this->fee = $reason->calculateFee($order->weight);
    }
}

ExpressShipping is the raison d’être of ExpressDelivery. It provides the complete tool set needed for express delivery.

Reason as $being

When a reason object is passed as $being, it takes on an additional role. Its type serves as the basis for determining the transformation destination, while simultaneously providing the methods specific to that mode of existence.

final readonly class ExpressDelivery
{
    public Fee $fee;

    public function __construct(
        #[Input] OrderData $order,
        #[Input] ExpressShipping $being    // Type determines transformation and provides express-specific methods
    ) {
        $this->fee = $being->calculateFee($order->weight);
    }
}

final readonly class StandardDelivery
{
    public Fee $fee;

    public function __construct(
        #[Input] OrderData $order,
        #[Input] StandardShipping $being   // Type determines transformation and provides standard-specific methods
    ) {
        $this->fee = $being->calculateFee($order->weight);
    }
}

The type ExpressShipping $being itself is the reason why it becomes ExpressDelivery. The framework reads this type and automatically selects the corresponding transformation destination.

Any Reason object can serve as either #[Inject] (providing transcendent capabilities) or $being (determining destiny). The difference is not in the object itself, but in how it is used. A JTASProtocol that evaluates patients as #[Inject] in one context could determine destiny as $being in another.

Defining Reason Classes

Reason classes bundle the services necessary to realize a specific mode of existence:

namespace App\Reason;

final readonly class ExpressShipping
{
    public function __construct(
        private PriorityCarrier $carrier,
        private RealTimeTracker $tracker,
    ) {}

    public function calculateFee(Weight $weight): Fee        // Express rate
    {
        return $this->carrier->expressFee($weight);
    }

    public function guaranteeDeliveryBy(Address $address): \DateTimeImmutable  // Guaranteed delivery date
    {
        return $this->carrier->guaranteedDate($address);
    }

    public function realTimeTrack(TrackingId $id): TrackingStatus  // Real-time tracking
    {
        return $this->tracker->realTimeStatus($id);
    }
}
final readonly class StandardShipping
{
    public function __construct(
        private RegularCarrier $carrier,
        private BatchTracker $tracker,
    ) {}

    public function calculateFee(Weight $weight): Fee        // Standard rate
    {
        return $this->carrier->standardFee($weight);
    }

    public function estimateDeliveryWindow(Address $address): DateRange  // Estimated delivery window
    {
        return $this->carrier->estimateWindow($address);
    }
}

Difference from Individual Injection

The Reason Layer uses #[Inject]. So how does it differ from using multiple #[Inject] attributes separately?

Individual injection:

public function __construct(
    #[Input] OrderData $order,
    #[Inject] PriorityCarrier $carrier,
    #[Inject] RealTimeTracker $tracker,
    #[Inject] InsuranceService $insurance,
    #[Inject] DeliveryScheduler $scheduler
) {
    // Using scattered tools individually
}

Reason Layer:

public function __construct(
    #[Input] OrderData $order,
    #[Inject] ExpressShipping $reason    // Related tools bundled as reason for existence
) {
    $this->fee = $reason->calculateFee($order->weight);
}

“What is needed to become ExpressDelivery?” — a single reason object answers that question. Objects themselves declare “what to become”, while reason objects realize “how to achieve that state”.

Reason Returning Potential

So far, Reason has returned immediate values like Fee. But consider order processing: inventory must be reserved, payment captured, and shipping arranged—and all three must succeed before any of them commit. If payment fails after inventory is reserved, that reservation must not persist.

For this kind of all-or-nothing coordination, Reason returns a Potential—an object that holds a deferred operation, realized later via be(). This pattern is only needed when multiple external operations must all commit atomically.

Potential: Prepared but Uncommitted

A Reason method prepares the external operation and returns a Potential:

final class PaymentGateway
{
    public function authorize(string $cardNumber, int $amount): PaymentCapture
    {
        $authCode = $this->api->authorize($cardNumber, $amount);

        return new PaymentCapture(
            $authCode,
            $amount,
            fn () => $this->api->capture($authCode, $amount),
        );
    }
}

PaymentCapture is a Potential—it holds the authorization code and a deferred capture operation. The payment is authorized but not yet captured. Calling be() commits it:

$capture = $gateway->authorize($cardNumber, $amount);
$capture->authorizationCode;  // Available immediately
$capture->be();               // Commits the capture

Moment: Holding Potential

A class that holds a Potential from Reason is called a Moment (Hegel’s 契機—an essential aspect that only makes sense as part of a whole). A Moment implements MomentInterface, provided by the framework:

interface MomentInterface
{
    public function be(): void;
}
final readonly class PaymentCompleted implements MomentInterface
{
    public PaymentCapture $capture;

    public function __construct(
        #[Input] public string $cardNumber,
        #[Input] public int $amount,
        #[Inject] PaymentGateway $gateway,
    ) {
        $this->capture = $gateway->authorize($cardNumber, $amount);
    }

    public function be(): void
    {
        $this->capture->be();
    }
}

Convergence: Final Realizes Moments

When multiple Moments must all succeed together, a Final Object receives them and calls be() on each. This is not an external command—it is self-completion:

final readonly class OrderConfirmed
{
    public string $orderId;
    public string $status;

    public function __construct(
        public InventoryReserved $inventory,
        public PaymentCompleted $payment,
        public ShippingArranged $shipping,
    ) {
        $this->inventory->be();
        $this->payment->be();
        $this->shipping->be();

        $this->orderId = 'ORD-' . date('Ymd') . '-' . bin2hex(random_bytes(4));
        $this->status = 'confirmed';
    }
}

If any Moment cannot be created (because its Reason failed), the Final Object is never constructed. If all Moments exist, be() commits every deferred operation. No manual rollback flags, no nested try-catch.

When to Use This Pattern

Use Potential-returning Reason when multiple external operations must succeed atomically—all commit together, or none at all. For simple cases where Reason returns an immediate value, Potential is unnecessary.


The inability to exist is itself an existence. Learn how to handle this in Validation and Error Handling ➡️