Laravel Rulebook: Time-Based Business Rules in PHP | Mohamed Said        [  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MH.png)   Mohamed Said Laravel Backend Engineer  ](https://www.msaied.com) [ Home ](https://www.msaied.com) [ Projects ](https://www.msaied.com/projects) [ Articles  ](https://www.msaied.com/articles) [ Certificates ](https://www.msaied.com/certificates) [ Contact ](https://www.msaied.com#contact-section) 

       [  ](https://github.com/EG-Mohamed)       

 [ Home ](https://www.msaied.com) [ Projects ](https://www.msaied.com/projects) [ Articles ](https://www.msaied.com/articles) [ Certificates ](https://www.msaied.com/certificates) [ Contact ](https://www.msaied.com#contact-section) 

  [ home ](https://www.msaied.com)    [ articles ](https://www.msaied.com/articles)    Laravel Rulebook: Manage Business Rules That Change by Date        On this page       1. [  What Is Laravel Rulebook? ](#what-is-laravel-rulebook)
2. [  Key Features ](#key-features)
3. [  Building a Time-Versioned Refund Policy ](#building-a-time-versioned-refund-policy)
4. [  Storing Decisions with Snapshots ](#storing-decisions-with-snapshots)
5. [  When Not to Use Rulebook ](#when-not-to-use-rulebook)
6. [  Real Takeaways ](#real-takeaways)

  ![Laravel Rulebook: Manage Business Rules That Change by Date](https://cdn.msaied.com/640/abbdec8836738d39a6650923a6b6ca5e.png)

 [  Laravel ](https://www.msaied.com/articles?category=laravel) [  Composer Pacakge ](https://www.msaied.com/articles?category=composer-pacakge)  #Laravel   #PHP   #Business Rules   #Composer Package   #Policy Versioning  

 Laravel Rulebook: Manage Business Rules That Change by Date 
=============================================================

     7 Sep 2026      4 min read    ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MJ.jpg)  Mohamed Said  

       Table of contents

1. [  01   What Is Laravel Rulebook?  ](#what-is-laravel-rulebook)
2. [  02   Key Features  ](#key-features)
3. [  03   Building a Time-Versioned Refund Policy  ](#building-a-time-versioned-refund-policy)
4. [  04   Storing Decisions with Snapshots  ](#storing-decisions-with-snapshots)
5. [  05   When Not to Use Rulebook  ](#when-not-to-use-rulebook)
6. [  06   Real Takeaways  ](#real-takeaways)

 What Is Laravel Rulebook?
-------------------------

[Laravel Rulebook](https://laravel-news.com/laravel-rulebook) by Mathias Onea is a package for encoding business rules that change over time. Each rule is a plain PHP class with a declared validity window. When you resolve a rulebook at a specific date, you get exactly one winning rule, the outcome it produced, and a full explanation of every rule that was considered.

The core use case is decisions that must be explained after the fact — a refund calculated under last March's terms, or an invoice raised at last year's commission rate. Editing a policy in place destroys that history; Rulebook keeps every version of the policy alive in the codebase.

Key Features
------------

- **Validity windows** — declare a rule's active period with `always()`, `from()`, `until()`, or `between()`. Windows are half-open, so consecutive periods never overlap.
- **Priority-based resolution** — exactly one winner is chosen by `priority()`, never by array position.
- **Rich result objects** — every rule returns a status (`applicable`, `does_not_apply`, `outside_validity`) and a human-readable reason with an optional `reasonCode`.
- **Explicit failure modes** — throws `NoMatchingRule` when nothing applies, and `AmbiguousRuleMatch` when two rules tie.
- **Snapshots** — freeze a decision into a JSON-serializable record you can store alongside the data it governs.
- **No magic** — no facade, no config file, no migration, no database-stored rules.

Requires PHP 8.3 and Laravel 12 or 13:

```bash
composer require mathiasonea/laravel-rulebook

```

Building a Time-Versioned Refund Policy
---------------------------------------

Consider an events platform with two flexible-fare refund policies. Until end of 2025: full refund with 7 days' notice, no fee. From January 2026: 14 days' notice required and a $3.50 handling fee.

A shared abstract class holds the evaluation logic; each year's rule supplies its own numbers and validity window:

```php
final class FlexibleFareRefund2025 extends FlexibleFareRefund
{
    public function validity(): ValidityPeriod
    {
        return ValidityPeriod::between(
            from:  new DateTimeImmutable('2025-01-01T00:00:00-05:00'),
            until: new DateTimeImmutable('2026-01-01T00:00:00-05:00'),
        );
    }

    protected function noticeInDays(): int        { return 7; }
    protected function handlingFeeInCents(): int  { return 0; }
}

final class FlexibleFareRefund2026 extends FlexibleFareRefund
{
    public function validity(): ValidityPeriod
    {
        return ValidityPeriod::from(new DateTimeImmutable('2026-01-01T00:00:00-05:00'));
    }

    protected function noticeInDays(): int        { return 14; }
    protected function handlingFeeInCents(): int  { return 350; }
}

```

Resolving at a specific point in time is a single call:

```php
$decision = $rulebook->resolveAt(
    subject: new Ticket(reference: 'TCK-4193', priceInCents: 89_00),
    at:      new DateTimeImmutable('2025-11-02T09:00:00-05:00'),
    context: new Cancellation(fare: 'flexible', daysBeforeEvent: 10),
);

$decision->outcome()->formatted();        // $89.00
class_basename($decision->winningRule()); // FlexibleFareRefund2025

```

Shift the date to 2026 and the same call returns $0.00 — ten days falls short of the 2026 policy's fourteen-day requirement. The `FlexibleFareRefund2025` rule shows status `outside_validity`, making it clear the policy simply did not exist at that date rather than actively rejecting the claim.

Storing Decisions with Snapshots
--------------------------------

```php
$snapshot = $decision->snapshot(
    normalizeOutcome: static fn (Refund $r): array => ['amount_in_cents' => $r->amountInCents],
);

$refund->update(['policy_snapshot' => json_encode($snapshot)]);

```

The snapshot is `JsonSerializable` and also exposes `toArray()` for Eloquent models with an `array` cast. One important detail: the rule identifier defaults to the class name. Rename a class and every stored snapshot loses its link. Assign a stable `key()` — such as `refunds.flexible-fare.2026` — before any snapshots reach a database.

When Not to Use Rulebook
------------------------

A single date check in one service is clearer as a `match` expression. Rulebook pays off when:

- Decisions span multiple policy eras.
- Someone will ask months later why a specific number was calculated.
- You need a stored, human-readable explanation alongside the record.

It is not a DSL, not a workflow engine, and not a full audit trail — resolving an old date reproduces the policy as today's classes express it, not a replay of the original execution.

Real Takeaways
--------------

- Keep every policy version in code; validity windows prevent overlap without extra logic.
- `resolveAt()` makes time-travel testing trivial — change one argument, get a different winner.
- The full evaluation table (not just the winner) is available on every decision object.
- Assign stable `key()` values to rules before persisting any snapshots.
- The package has zero infrastructure requirements: no migrations, no config, no facade.

---

Source: [Laravel Rulebook: Business Rules That Change by Date — Laravel News](https://laravel-news.com/laravel-rulebook)

 Found this useful?

          [  ](https://twitter.com/intent/tweet?url=https%3A%2F%2Fwww.msaied.com%2Farticles%2Flaravel-rulebook-manage-business-rules-that-change-by-date&text=Laravel+Rulebook%3A+Manage+Business+Rules+That+Change+by+Date) [  ](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fwww.msaied.com%2Farticles%2Flaravel-rulebook-manage-business-rules-that-change-by-date) 

 Frequently Asked Questions 
----------------------------

  3 questions  

     Q01  How does Laravel Rulebook handle two rules with overlapping validity periods?        Validity windows are half-open intervals, so a rule closing on 2026-01-01 and a rule opening on 2026-01-01 never overlap. If two rules do match the same point in time and share the same priority, Rulebook throws an `AmbiguousRuleMatch` exception rather than silently picking one. 

      Q02  Can I replay an old decision exactly as it was originally executed?        Not exactly. Resolving at a past date reproduces the policy as today's classes express it. If you have since changed the logic inside a rule class, the re-resolution will use the updated code. For a true replay you need to store a snapshot at decision time and read that back instead. 

      Q03  What happens if I rename a rule class after snapshots have been stored?        By default the rule's identifier is its fully-qualified class name, so renaming the class breaks the link in every stored snapshot. To avoid this, override the `key()` method on each rule with a stable string such as `refunds.flexible-fare.2026` before any snapshots reach the database. 

  Continue reading

 More Articles 
---------------

 [ View all    ](https://www.msaied.com/articles) 

 [ ![Contextual Binding and Method Injection in Laravel's Service Container](https://cdn.msaied.com/639/ce580b3b521a5e965bf80bb1e7ba7ced.png) laravel service-container dependency-injection 

### Contextual Binding and Method Injection in Laravel's Service Container

Go beyond basic singleton registration. Learn how contextual binding, tagged services, and method injection le...

  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MJ.jpg)  Mohamed Said 

 7 Sep 2026     3 min read  

  Read    

 ](https://www.msaied.com/articles/contextual-binding-and-method-injection-in-laravels-service-container-3) [ ![Filament v4 Schema-Based Forms: Unified Schema API and Infolist Patterns](https://cdn.msaied.com/638/f9bf7d5a5195f8a61e97ccc196cf96d6.png) filament laravel filament-v4 

### Filament v4 Schema-Based Forms: Unified Schema API and Infolist Patterns

Filament v4 replaces scattered form/infolist definitions with a single Schema API. Learn how unified schemas,...

  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MJ.jpg)  Mohamed Said 

 7 Sep 2026     3 min read  

  Read    

 ](https://www.msaied.com/articles/filament-v4-schema-based-forms-unified-schema-api-and-infolist-patterns) [ ![The Pipeline Pattern in Laravel: Building Custom Pipelines Beyond Middleware](https://cdn.msaied.com/637/1b6b067bc3805768f8e1f546d2ba7545.png) laravel pipeline clean-architecture 

### The Pipeline Pattern in Laravel: Building Custom Pipelines Beyond Middleware

Laravel's Pipeline class powers middleware, but it's equally powerful for domain workflows. Learn how to build...

  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MJ.jpg)  Mohamed Said 

 6 Sep 2026     3 min read  

  Read    

 ](https://www.msaied.com/articles/the-pipeline-pattern-in-laravel-building-custom-pipelines-beyond-middleware-2) 

   [  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MH.png)   Mohamed Said Laravel Backend Engineer  ](https://www.msaied.com)Senior Backend Engineer specializing in Laravel, scalable SaaS platforms, APIs, and cloud infrastructure. I build secure, high-performance web applications that help businesses grow.

Explore

- [Home](https://www.msaied.com)
- [Projects](https://www.msaied.com/projects)
- [Articles](https://www.msaied.com/articles)
- [Certificates](https://www.msaied.com/certificates)
- [Contact](https://www.msaied.com#contact-section)

Connect

- [   hello@msaied.com ](mailto:hello@msaied.com)
- [   +20 109 461 9204 ](tel:+201094619204)

© 2026 Mohamed Said. All rights reserved.

 [  ](https://github.com/EG-Mohamed) [  ](https://www.linkedin.com/in/msaiedm/) [  ](https://wa.me/201094619204) [  ](mailto:hello@msaied.com) [  ](https://drive.google.com/file/u/0/d/1MF20IPRJyzfy32mhEutjL5EpSls0w2Q8/view)
