Laravel 的 Aegis:值对象的脚手架和验证辅助工具

发布日期 经过

Laravel 的 Aegis:值对象的脚手架和验证辅助工具

I just released Aegis for Laravel on stage at Laravel Live Japan 2026. It's a package I built around a pattern I keep coming back to: Value Objects. They quietly remove a whole class of bugs that primitives cause, but writing them by hand gets tedious fast. Aegis writes the tedious part for you.

问题

A string isn't an email until something validates it. An int isn't money until something tags it with a currency. We pass these raw primitives around our apps and trust they're correct, then lose an afternoon to the one place they weren't.

A Value Object fixes that. Its constructor takes the input and either produces a valid instance or throws. Once you're holding an Email , it's a real email. Bad values never reach the rest of the system.

The catch is the boilerplate. A Value Object that does its job properly runs about 70 lines of PHP. A final readonly class. Validation in the constructor. Normalization. An equals() 方法。这 Castable block with get , set , 和 compare so Eloquent can store it. Typing all of that for every string a team wants to harden costs more than the string was costing you. So people skip it, and the primitives stay.

Aegis writes those lines for you. One Artisan command produces the class with everything wired up, plus a test stub. You fill in the methods that belong to your domain.

安装

composer require harrisrafto/laravel-aegis

The service provider registers itself through Laravel's package auto-discovery.

If you want to change the namespace for generated Value Objects, publish the config:

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

Scaffolding a Value Object

Here's the command for an Email :

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

That generates three things.

First, app/Domain/ValueObjects/Email.php . It's final readonly , validated, and normalized. It implements StringableJsonSerializable so it behaves at the edges of your app, and it carries the Castable block for Eloquent. The domain(): string method you asked for is there as an empty stub, waiting for your logic.

Second, tests/Unit/EmailTest.php . If Pest is in your vendor/ , Aegis writes a Pest it()->todo() . If it isn't, you get a PHPUnit markTestIncomplete() case instead. Either way the test exists and is asking to be filled in.

Third, it patches app/Models/Order.php , adding 'email' => Email::class to the model's casts() method. It preserves your existing indentation, and it's safe to run again. The cast gets added once.

Validating With the Same Value Object

The Value Object already knows what a valid email is. So you validate with it directly instead of repeating the rules in a FormRequest:

use Illuminate\Validation\Rule;
公共函数规则():数组
{
返回 [
'email' => ['required', Rule::valueObject(Email::class)],
];
}

Aegis registers valueObject as a macro on Illuminate\Validation\Rule . The call site reads like any other built-in rule, right next to Rule::in() 或者 Rule::unique()

Resolving the Validated Instance

Once validation passes, you usually want the object, not the raw string. Add the ResolvesValueObjects trait to your FormRequest:

use HarrisRafto\Aegis\Concerns\ResolvesValueObjects;
class StoreUserRequest extends FormRequest
{
use ResolvesValueObjects;
公共函数规则():数组
{
返回 [
'email' => ['required', Rule::valueObject(Email::class)],
];
}
}

Then pull the instance out in your controller:

$email = $request->valueObject('email'); // an Email instance, already validated

The object that validated the input is the one you carry forward, so there's no second copy of the truth to keep in sync.

Finding Candidates in Your Codebase

If you're adding Value Objects to an app that already exists, the hard part is knowing where to start. Run:

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 exact make:value-object command for each one:

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%.

It reads model $fillable , $casts , 和 casts() declarations, plus any Schema::create blocks in your migrations. It never touches your database. Pass --json for machine-readable output, --no-cast to drop the cast suggestion from each command, or --path--migrations-path to point at non-standard directories.

That coverage figure at the bottom is the part I find myself watching. It turns "we should really use Value Objects more" into a number you can actually move.

The Flags

make:value-object takes a handful of flags:

Flag 目的
--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. Defaults to string . Also accepts int , float , bool , or a fully qualified class name.
--method=NAME[:RETURN_TYPE] Adds an empty method stub. Repeatable.
--cast=Model.column Wires the cast into app/Models/Model.php . Safe to re-run.
--namespace=NS Overrides the configured default namespace.
--no-test Skips the test stub.
--dry-run Prints the files that would be written or changed without touching disk.
--force Overwrites existing files.

要求

  • PHP 8.3+
  • Laravel 13

链接

About the Name

In Greek mythology the aegis was Athena's shield, the thing she carried into battle to deflect what shouldn't reach what it protected. That's exactly what the constructor of a Value Object does: it accepts what's valid and refuses everything else.

The pattern goes back to Eric Evans' Domain-Driven Design and Martin Fowler's Patterns of Enterprise Application Architecture . None of that is new. Aegis just takes care of the typing, so you'll actually reach for it the next time a raw string starts causing trouble.

哈里斯·拉夫托普洛斯摄

高级软件工程师 • Laravel News 员工及讲师 • Laravel Greece Meetup 联合组织者

归档于:
立方体

Laravel 时事通讯

加入超过 4 万名开发者的行列,不错过任何新的技巧、教程等内容。

图像
了解软科技

以每小时 20 美元的价格聘请具备人工智能专业知识的 Laravel 开发人员。48 小时内即可开始工作。

访问 Acquaint Softtech
鱼叉:新一代时间跟踪和发票标志

Harpoon:新一代时间跟踪和发票系统

新一代时间跟踪和计费软件,帮助您的机构规划和预测盈利的未来。

Harpoon:新一代时间跟踪和发票系统
Lucky Media 标志

幸运传媒

Get Lucky Now——拥有十余年经验的 Laravel 开发理想之选!

幸运传媒
SaaSykit:Laravel SaaS 入门套件徽标

SaaSykit:Laravel SaaS 入门套件

SaaSykit 是一个多租户 Laravel SaaS 入门套件,包含运行现代 SaaS 所需的所有功能,例如支付、美观的结账界面、管理面板、用户仪表盘、身份验证、现成组件、统计数据、博客、文档等等。

SaaSykit:Laravel SaaS 入门套件
Shift 标志

转移

还在运行旧版本的 Laravel?立即实现 Laravel 自动升级和代码现代化,让您的应用程序保持最新状态。

转移
绝不妥协标志

绝不妥协

来自 No Compromises 播客的两位经验丰富的开发者 Joel 和 Aaron 现在可以为您的 Laravel 项目提供服务。⬧ 固定费用 9500 美元/月。⬧ 无冗长的销售流程。⬧ 无需签订合同。⬧ 100% 退款保证。

绝不妥协
Laravel Cloud 标志

Laravel 云

轻松创建和管理服务器,并在几秒钟内部署 Laravel 应用程序。

Laravel 云
了解 Softtech 的标志

了解软科技

Acquaint Softtech 提供 AI 就绪的 Laravel 开发人员,48 小时内即可上手,每月费用为 3000 美元,没有冗长的销售流程,并提供 100% 退款保证。

了解软科技
Tinkerwell 徽标

廷克威尔

Laravel 开发者必备的代码运行器。可在本地和生产环境中体验 AI、自动补全和即时反馈功能。

廷克威尔
Kirschbaum 标志

樱桃树

提供创新和稳定性,确保您的Web应用程序取得成功。

樱桃树
PhpStorm 标志

PhpStorm

首选的 PHP IDE,对 Laravel 及其生态系统提供广泛的开箱即用支持。

PhpStorm
Laracon AU 2026 公布完整演讲嘉宾阵容、日程安排和研讨会图片

Laracon AU 2026 公布完整演讲嘉宾阵容、日程安排和研讨会信息

阅读文章
Parsel:使用 PHP 图像解析 PDF、Office 文档和图像

Parsel:使用 PHP 解析 PDF、Office 文档和图像

阅读文章
带有表现力图像的 Eloquent 类型对象

Eloquent 的类型化对象及其表达方式

阅读文章
Composer 2.10 镜像中的恶意软件阻止和依赖项策略

Composer 2.10 中的恶意软件阻止和依赖项策略

阅读文章
Playa:基于 Cookie 的 Laravel 临时播放器

Playa:基于 Cookie 的 Laravel 临时播放器

阅读文章
Laravel 13.12.0 镜像中的调度器属性和监听器发现控制

Laravel 13.12.0 中的调度器属性和监听器发现控制

阅读文章