migears / domain
Minimalist Domain — data containers with array access and self-validation
Requires
- php: ^8.1
- migears/validator: ^2.0@dev
Requires (Dev)
- phpunit/phpunit: ^10
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-09-17 22:00:02 UTC
README
Minimalist Domain layer — pure data containers with zero mapping.
Philosophy
- No Getter/Setter — properties are
public readonly - No Hydrator/Mapper — direct
new XxxDomain(...$row)construction - No base class inheritance — use the
DataAccesstrait - Field names match database columns 1:1 — no camelCase conversion
- No persistence logic — Domain knows nothing about SQL or DAO
Installation
composer require migears/domain
Requires: PHP 8.1+.
Quick Start
Define a Domain
use MiGears\Domain\DataAccess; class UserDomain { use DataAccess; public function __construct( public readonly int $id, public readonly string $user_name, public readonly string $email, public readonly int $age, public readonly string $created_at, ) {} }
Array → Domain
// From a database row $row = ['id' => 1, 'user_name' => 'Alice', 'email' => 'a@b.com', 'age' => 25, 'created_at' => '2024-01-01']; $user = UserDomain::fromArray($row); echo $user->user_name; // "Alice"
fromArray() uses PHP 8.x named arguments via ...$row array spreading, so array keys must match constructor parameter names exactly.
Domain → Array
$row = $user->toArray(); // ['id' => 1, 'user_name' => 'Alice', 'email' => 'a@b.com', 'age' => 25, 'created_at' => '2024-01-01']
toArray() uses get_object_vars($this), returning all properties as an associative array.
Round Trip
$domain = UserDomain::fromArray($row); $back = $domain->toArray(); // $back === $row ✅
Naming Convention
| Layer | Convention | Example |
|---|---|---|
| Database column | snake_case | user_name |
| Domain property | snake_case (matches column) | $user_name |
| Constructor param | snake_case (matches column) | string $user_name |
No camelCase ↔ snake_case conversion anywhere. What you see in the database is what you see in code.
Architecture
Domain is the middle layer of miGears' three-layer data architecture:
Service Layer (business logic)
↓ calls
DAO Layer (receives/returns Domain objects) → migears/dao
↓ internally calls
SQL Layer (SQL + params → arrays) → migears/sql
↓
PDO / MySQL
- Domain knows nothing about SQL or DAO
- DAO uses
fromArray()to convert SQL results into Domain objects - DAO uses
toArray()to convert Domain objects back to arrays for SQL
Self-Validation with Validatable
Domain objects can validate their own data using the Validatable trait. Validation rules are defined in the domain class itself, and errors are returned as structured error codes + params (i18n-ready).
Depends on migears/validator.
use MiGears\Domain\DataAccess; use MiGears\Domain\Validatable; class UserDomain { use DataAccess; use Validatable; public function __construct( public readonly string $username, public readonly string $email, public readonly int $age = 0, ) {} protected static function validationRules(): array { return [ 'username' => ['required' => true, 'minLength' => 3, 'maxLength' => 20], 'email' => ['required' => true, 'email' => true], 'age' => ['integer' => true, 'min' => 0, 'max' => 150], ]; } }
Validate an instance
$user = new UserDomain('ab', 'invalid', -1); $errors = $user->validate(); // [ // 'username' => ['rule' => 'minLength', 'params' => ['min' => 3]], // 'email' => ['rule' => 'email', 'params' => []], // 'age' => ['rule' => 'min', 'params' => ['min' => 0]], // ] $user->isValid(); // false
Validate before construction
$errors = UserDomain::validateArray($_POST); if ($errors === []) { $user = UserDomain::fromArray($_POST); }
Error format
Errors use structured codes instead of hardcoded messages, ready for i18n:
['field' => ['rule' => 'minLength', 'params' => ['min' => 3]]]
Pair with migears/i18n to translate:
$message = $translator->get( "validation.{$error['rule']}", ['field' => $fieldLabel, ...$error['params']] );
Why a Trait Instead of a Base Class?
- No inheritance constraint — Domain classes can extend whatever they need
- Zero overhead — trait methods are inlined into the class
- Maximum readability — two methods, total ~15 lines of code
License
MIT
migears/domain
极简 Domain 层 — 纯数据容器,零映射。
设计哲学
- 不用 Getter/Setter — 属性全部
public readonly - 不用 Hydrator/Mapper — 直接
new XxxDomain(...$row)构造 - 不用基类继承 — 使用
DataAccesstrait - 字段名与数据库列名完全一致 — 不做驼峰/下划线互转
- 不含持久化逻辑 — Domain 不知道 SQL 和 DAO 的存在
安装
composer require migears/domain
要求:PHP 8.1+。
快速开始
定义 Domain
use MiGears\Domain\DataAccess; class UserDomain { use DataAccess; public function __construct( public readonly int $id, public readonly string $user_name, public readonly string $email, public readonly int $age, public readonly string $created_at, ) {} }
数组 → Domain
// 从数据库行构造 $row = ['id' => 1, 'user_name' => 'Alice', 'email' => 'a@b.com', 'age' => 25, 'created_at' => '2024-01-01']; $user = UserDomain::fromArray($row); echo $user->user_name; // "Alice"
fromArray() 通过 ...$row 展开关联数组,利用 PHP 8.x 命名参数特性,数组键名必须与构造函数参数名完全匹配。
Domain → 数组
$row = $user->toArray(); // ['id' => 1, 'user_name' => 'Alice', 'email' => 'a@b.com', 'age' => 25, 'created_at' => '2024-01-01']
toArray() 使用 get_object_vars($this),返回所有属性组成的关联数组。
往返转换
$domain = UserDomain::fromArray($row); $back = $domain->toArray(); // $back === $row ✅
命名规范
| 层级 | 规范 | 示例 |
|---|---|---|
| 数据库列名 | 下划线 | user_name |
| Domain 属性 | 下划线(与列名一致) | $user_name |
| 构造函数参数 | 下划线(与列名一致) | string $user_name |
全程不做驼峰/下划线互转。数据库里是什么,代码里就是什么。
架构
Domain 是 miGears 三层数据架构的中间层:
Service 层(业务逻辑)
↓ 调用
DAO 层(接收/返回 Domain 对象)→ migears/dao
↓ 内部调用
SQL 层(SQL + 参数 → 数组)→ migears/sql
↓
PDO / MySQL
- Domain 不知道 SQL 和 DAO 的存在
- DAO 用
fromArray()把 SQL 结果转为 Domain 对象 - DAO 用
toArray()把 Domain 对象转回数组供 SQL 使用
Validatable 自验证
Domain 对象可以使用 Validatable trait 自验证数据。验证规则定义在 domain 类自身,错误以结构化的错误码 + 参数形式返回(i18n 就绪)。
依赖 migears/validator。
use MiGears\Domain\DataAccess; use MiGears\Domain\Validatable; class UserDomain { use DataAccess; use Validatable; public function __construct( public readonly string $username, public readonly string $email, public readonly int $age = 0, ) {} protected static function validationRules(): array { return [ 'username' => ['required' => true, 'minLength' => 3, 'maxLength' => 20], 'email' => ['required' => true, 'email' => true], 'age' => ['integer' => true, 'min' => 0, 'max' => 150], ]; } }
验证实例
$user = new UserDomain('ab', 'invalid', -1); $errors = $user->validate(); // [ // 'username' => ['rule' => 'minLength', 'params' => ['min' => 3]], // 'email' => ['rule' => 'email', 'params' => []], // 'age' => ['rule' => 'min', 'params' => ['min' => 0]], // ] $user->isValid(); // false
构造前验证
$errors = UserDomain::validateArray($_POST); if ($errors === []) { $user = UserDomain::fromArray($_POST); }
错误格式
错误使用结构化代码而非硬编码消息,i18n 就绪:
['字段名' => ['rule' => 'minLength', 'params' => ['min' => 3]]]
配合 migears/i18n 翻译:
$message = $translator->get( "validation.{$error['rule']}", ['field' => $fieldLabel, ...$error['params']] );
为什么用 Trait 而不是基类?
- 不受继承约束 — Domain 类可以继承任何需要的父类
- 零开销 — trait 方法会被内联到类中
- 最大可读性 — 两个方法,总共约 15 行代码
许可证
MIT