PHPackages                             harrisrafto/laravel-aegis - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Validation &amp; Sanitization](/categories/validation)
4. /
5. harrisrafto/laravel-aegis

ActiveLibrary[Validation &amp; Sanitization](/categories/validation)

harrisrafto/laravel-aegis
=========================

Aegis for Laravel — scaffolding and validation helpers for Value Objects.

v0.2.1(1mo ago)65475—5.6%4MITPHPPHP ^8.3CI passing

Since May 19Pushed 1mo agoCompare

[ Source](https://github.com/harris21/laravel-aegis)[ Packagist](https://packagist.org/packages/harrisrafto/laravel-aegis)[ RSS](/packages/harrisrafto-laravel-aegis/feed)WikiDiscussions main Synced 1w ago

READMEChangelog (4)Dependencies (16)Versions (5)Used By (0)

 [![Aegis for Laravel](art/logo.png)](art/logo.png)

 **Aegis for Laravel**

 *\[EE-jis\]*

 Scaffolding and validation helpers for Value Objects.

 Wrap your primitives and validate them at the boundary in one Artisan command.

---

Why Aegis
---------

[](#why-aegis)

A string isn't an email until something validates it. An int isn't money until something tags its currency.

A Value Object fixes that. Its constructor accepts the input and produces a valid instance, or throws. Bad values never reach the rest of the system.

The catch: a Value Object that does its job is around 70 lines of PHP. `final readonly` class, validation in the constructor, normalization, an `equals()` method, the `Castable` block with `get`/`set`/`compare` for Eloquent. Typing that for every string a team wants to harden costs more than the string was costing.

Aegis writes those lines for you. One Artisan command produces the Value Object class with everything wired up, plus a Pest test stub. If you pass `--cast=Model.column`, it also patches the model. You write the methods that belong to your domain.

About the name
--------------

[](#about-the-name)

In Greek mythology the *aegis* was Athena's shield, carried into battle to deflect what shouldn't reach what it protected. The package borrows the name because that's what the constructor of a Value Object does: accepts what's valid and refuses everything else.

Requirements
------------

[](#requirements)

- PHP 8.3+
- Laravel 13

Installation
------------

[](#installation)

```
composer require harrisrafto/laravel-aegis
```

The service provider is registered automatically via Laravel's package auto-discovery.

Optional — publish the config to override the default namespace for generated Value Objects:

```
php artisan vendor:publish --tag=aegis-config
```

Quick start
-----------

[](#quick-start)

### Scaffold a Value Object

[](#scaffold-a-value-object)

```
php artisan make:value-object Email \
    --rule=email \
    --normalize=lower \
    --method=domain:string \
    --cast=Order.email
```

Generates:

- `app/Domain/ValueObjects/Email.php` — `final readonly`, validated, normalized, with the Castable block wired for Eloquent. Implements `Stringable` and `JsonSerializable` for the application edges. Contains an empty `domain(): string` stub for you to fill in.
- `tests/Unit/EmailTest.php` — test stub awaiting your assertions. Aegis emits a Pest `it()->todo()` when `pestphp/pest` is in your `vendor/`, and a PHPUnit `markTestIncomplete()` TestCase otherwise.
- `app/Models/Order.php` — patched to add `'email' => Email::class` inside its `casts()` method, preserving the existing indentation.

### Validate with the same Value Object

[](#validate-with-the-same-value-object)

```
use Illuminate\Validation\Rule;

public function rules(): array
{
    return [
        'email' => ['required', Rule::valueObject(Email::class)],
    ];
}
```

Aegis registers `valueObject` as a macro on Laravel's `Illuminate\Validation\Rule`, so the call site reads identically to any other built-in rule (`Rule::in(...)`, `Rule::unique(...)`).

### Resolve the validated instance from a FormRequest

[](#resolve-the-validated-instance-from-a-formrequest)

```
use HarrisRafto\Aegis\Concerns\ResolvesValueObjects;

class StoreUserRequest extends FormRequest
{
    use ResolvesValueObjects;

    public function rules(): array
    {
        return [
            'email' => ['required', Rule::valueObject(Email::class)],
        ];
    }
}

// In your controller:
$email = $request->valueObject('email'); // an Email instance, already validated
```

### Scan your codebase for Value Object candidates

[](#scan-your-codebase-for-value-object-candidates)

```
php artisan vo:scan
```

Aegis walks your Eloquent models and migrations, flags column names that match common patterns (email, url, uuid, country\_code, slug, ip, status, money), and prints the `make:value-object` command you'd run for each one. A final line tells you how much of your codebase is already wrapped:

```
app/Models/Customer.php
  · billing_email   → php artisan make:value-object Email --rule=email --normalize=lower --cast=Customer.billing_email
  · country_code    → php artisan make:value-object CountryCode --rule=regex:/^[A-Z]{2}$/ --normalize=upper --cast=Customer.country_code
  · monthly_amount_cents  candidate — Money column, see cknow/laravel-money

Scanned 3 models, 16 columns total.
7 commands ready, 2 candidates need your input, 1 already wrapped.
Value Object coverage: 6%.

```

The scanner reads model `$fillable`, `$casts`, and `casts()` declarations, plus any `Schema::create` blocks in your migrations. It never touches your database. Pass `--json` for machine-readable output, `--no-cast` to omit the `--cast=Model.column` part of each suggestion, or `--path` and `--migrations-path` to point at non-standard directories.

If your app uses [`nwidart/laravel-modules`](https://github.com/nWidart/laravel-modules), `vo:scan` detects it automatically and also walks every module's models and migrations alongside `app/Models` — no flags needed. Module locations are read from the package's own config, so custom layouts are respected. Pass `--no-modules` to scan only the default `app/Models` directory, or an explicit `--path` to target a single module.

Flags
-----

[](#flags)

FlagPurpose`--rule=NAME[:ARGS]`Validation rule. One of `email`, `url`, `ip`, `uuid`, `alpha_num`, `alpha`, `numeric`, `regex:PATTERN`.`--normalize=FN[,FN]`Normalization. Compose with commas: `lower`, `upper`, `trim`.`--type=PHP_TYPE`Property type. Default `string`. Also accepts `int`, `float`, `bool`, or a fully qualified class name.`--method=NAME[:RETURN_TYPE]`Empty method stub. Repeatable.`--cast=Model.column`Adds the cast wiring to `app/Models/Model.php`. Safe to re-run; the cast is added once.`--namespace=NS`Override the configured default namespace.`--no-test`Skip the Pest test stub.`--dry-run`Print the files that would be written or changed without touching disk.`--force`Overwrite existing files.Credits
-------

[](#credits)

Built by [Harris Raftopoulos](https://x.com/harrisrafto) for [Laravel Live Japan 2026](https://laravellive.jp/en).

YouTube: [@harrisrafto](https://youtube.com/@harrisrafto)

Companion to the talk *"Bulletproof Your Laravel Code with Value Objects"* and the example repository at [harris21/laravel-value-objects-examples](https://github.com/harris21/laravel-value-objects-examples).

Based on the Value Object pattern from Eric Evans' *Domain-Driven Design* and Martin Fowler's *Patterns of Enterprise Application Architecture*.

License
-------

[](#license)

MIT

###  Health Score

47

—

FairBetter than 93% of packages

Maintenance94

Actively maintained with recent releases

Popularity32

Limited adoption so far

Community10

Small or concentrated contributor base

Maturity42

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 96.8% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~12 days

Total

4

Last Release

30d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/0fcd0f4186fd1784c24293005e0c1468bb229f8a07f6b502b72fb8f034a284b9?d=identicon)[harris21](/maintainers/harris21)

---

Top Contributors

[![harris21](https://avatars.githubusercontent.com/u/1542015?v=4)](https://github.com/harris21 "harris21 (30 commits)")[![zigzagdev](https://avatars.githubusercontent.com/u/47250974?v=4)](https://github.com/zigzagdev "zigzagdev (1 commits)")

---

Tags

artisandomain-driven-designlaravellaravel-packagephpscaffoldingvalue-objectslaravelvalidationscaffoldingValue Objectform-request

###  Code Quality

TestsPest

Static AnalysisPHPStan

Code StyleLaravel Pint

### Embed Badge

![Health badge](/badges/harrisrafto-laravel-aegis/health.svg)

```
[![Health](https://phpackages.com/badges/harrisrafto-laravel-aegis/health.svg)](https://phpackages.com/packages/harrisrafto-laravel-aegis)
```

###  Alternatives

[laravel/ai

The official AI SDK for Laravel.

1.0k3.2M246](/packages/laravel-ai)[laravel/mcp

Rapidly build MCP servers for your Laravel applications.

77922.3M186](/packages/laravel-mcp)[roots/acorn

Framework for Roots WordPress projects built with Laravel components.

9762.4M133](/packages/roots-acorn)[propaganistas/laravel-disposable-email

Disposable email validator

6023.0M7](/packages/propaganistas-laravel-disposable-email)[illuminate/queue

The Illuminate Queue package.

20432.6M1.7k](/packages/illuminate-queue)[moonshine/moonshine

Laravel administration panel

1.3k253.1k86](/packages/moonshine-moonshine)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
