kyzegs/laravel-eloquent-embeddables 问题修复 & 功能扩展

解决BUG、新增功能、兼容多环境部署,快速响应你的开发需求

邮箱:yvsm@zunyunkeji.com | QQ:316430983 | 微信:yvsm316

kyzegs/laravel-eloquent-embeddables

Composer 安装命令:

composer require kyzegs/laravel-eloquent-embeddables

包简介

Doctrine-like embeddables for Eloquent using Laravel's native custom cast system.

README 文档

README

Laravel Eloquent Embeddables banner

Laravel Eloquent Embeddables

Doctrine-like embeddables for Eloquent, built on Laravel's native custom cast system.

Group several plain database columns into a rich PHP value object — no JSON column, no extra table, and no trait required on the parent model.

$user->address->street;
$user->address->city = 'Rotterdam';
$user->save();

…while the database keeps ordinary columns:

users.address_street
users.address_city
users.address_postal_code
users.address_country

Mental model

Laravel custom casts, but powerful enough to behave like Doctrine embeddables.

An embeddable owns structure, casting, validation and clean object access. The parent model owns persistence. The embeddable has no table, no identity, and is never saved on its own — it is persisted only through its parent.

Requirements

  • PHP 8.2+ (Laravel 13 requires PHP 8.3+)
  • Laravel 12 or 13

Installation

composer require kyzegs/eloquent-embeddables

Defining an embeddable

Extend EmbeddableModel. It reuses Eloquent's attribute machinery (fillable/guarded, casts, accessors, mutators, hidden, visible, appends, toArray()/toJson(), default attributes) but refuses every persistence and relationship method.

use Kyzegs\EloquentEmbeddables\EmbeddableModel;

final class Address extends EmbeddableModel
{
    protected $fillable = [
        'street',
        'city',
        'postal_code',
        'country',
    ];

    protected function casts(): array
    {
        return [
            'verified' => 'boolean',
        ];
    }

    public function fullAddress(): string
    {
        return trim("{$this->street}, {$this->postal_code} {$this->city}, {$this->country}");
    }
}

Using it in a parent model

Add the cast to the parent's casts() method. No trait is required.

Prefix form

use Illuminate\Database\Eloquent\Model;
use Kyzegs\EloquentEmbeddables\Casts\EmbeddableCast;

final class User extends Model
{
    protected function casts(): array
    {
        return [
            'address' => EmbeddableCast::using(
                Address::class,
                prefix: 'address_',
                attributes: ['street', 'city', 'postal_code', 'country', 'verified'],
                nullable: true,
            ),
        ];
    }
}

Maps:

address.street       => address_street
address.city         => address_city
address.postal_code  => address_postal_code
address.country      => address_country
address.verified     => address_verified

Explicit column-map form

'address' => EmbeddableCast::using(
    Address::class,
    columns: [
        'street'      => 'address_street',
        'city'        => 'address_city',
        'postal_code' => 'address_postal_code',
        'country'     => 'address_country',
    ],
    nullable: true,
),

Working with embeddables

Read like an object:

$user = User::first();

echo $user->address->city;
echo $user->address->fullAddress();
echo $user->address->verified; // true (cast applied)

Assign an array:

$user->address = [
    'street' => 'Coolsingel 1',
    'city' => 'Rotterdam',
    'postal_code' => '3012 AA',
    'country' => 'NL',
];

$user->save();

Assign an instance:

$user->address = new Address([
    'street' => 'Coolsingel 1',
    'city' => 'Rotterdam',
]);

$user->save();

Mutate in place — changes are synced back to the parent columns on save:

$user->address->city = 'Amsterdam';
$user->save();   // updates users.address_city

Nullable embeddables

With nullable: true:

$user->address = null;
$user->save();   // sets every mapped column to null

When reading, if all mapped columns are null, the cast returns null instead of an empty embeddable.

Serialization

Use the optional HasEmbeddables trait on the parent to expose a clean nested object in toArray() / toJson(). It appends each embeddable and hides its backing columns:

use Illuminate\Database\Eloquent\Model;
use Kyzegs\EloquentEmbeddables\Concerns\HasEmbeddables;

final class User extends Model
{
    use HasEmbeddables;

    // ...casts() as above
}
[
    'id' => 1,
    'name' => 'Sebastiaan',
    'address' => [
        'street' => 'Coolsingel 1',
        'city' => 'Rotterdam',
        'postal_code' => '3012 AA',
        'country' => 'NL',
        'verified' => true,
    ],
]

Without the trait, casts/reads/writes still work — the model just serializes the flat columns instead of a nested object.

Querying

Embeddables are plain columns, so query the parent model normally:

User::where('address_city', 'Rotterdam')->get();

What is not supported

An embeddable is not a full Eloquent model. These throw an EmbeddableException:

$user->address->save();
$user->address->delete();
$user->address->refresh();
$user->address->newQuery();
$user->address->hasMany(...);
$user->address->belongsTo(...);

Embeddables are persisted through their parent Eloquent model. Call save() on the parent model instead.

How it works

EmbeddableCast::using() returns an encoded Castable definition, so Laravel's native cast resolver builds the configured cast. On the cast:

  1. get() reads the mapped parent columns and hydrates an embeddable (its own casts apply).
  2. set() flattens an array / instance / null back into the mapped columns.
  3. Because Eloquent re-invokes set() for cached cast objects during save(), direct mutations are synced to the parent automatically.
  4. serialize() renders the embeddable as a nested array for toArray()/toJson().

Testing

composer test

License

MIT.

统计信息

  • 总下载量: 0
  • 月度下载量: 0
  • 日度下载量: 0
  • 收藏数: 0
  • 点击次数: 1
  • 依赖项目数: 0
  • 推荐数: 0

GitHub 信息

  • Stars: 0
  • Watchers: 0
  • Forks: 0
  • 开发语言: PHP

其他信息

  • 授权协议: MIT
  • 更新时间: 2026-07-02

承接程序开发

PHP开发

VUE

Vue开发

前端开发

小程序开发

公众号开发

系统定制

数据库设计

云部署

网站建设

安全加固