yaroslavsagun/laravel-attributes

Rinvex Attributes 是一个强大、智能且集成的实体-属性-值模型(EAV)实现,用于 Laravel Eloquent,它可以通过关系轻松管理实体属性。它利用 Laravel Eloquent 的强大功能,实现平滑无缝的集成。

dev-master 2022-10-13 13:39 UTC

This package is auto-updated.

Last update: 2024-09-13 17:40:41 UTC


README

⚠️ 此包已弃用,不再维护。没有建议的替代包。 ⚠️

👉 如果您有兴趣维护它,请联系我!

Rinvex Attributes 是一个强大、智能且集成的实体-属性-值模型(EAV)实现,用于 Laravel Eloquent,它可以通过关系轻松管理实体属性。它利用 Laravel Eloquent 的强大功能,实现平滑无缝的集成。

Packagist Scrutinizer Code Quality Travis StyleCI License

致谢通知

此包是基于IsraelOrtuno的出色EAV 包重写的分支,原始荣誉归他所有。它已被广泛重写,核心概念与我们认为的非常优秀。此分支的主要区别包括

  • 利用 rinvex/laravel-cacheable 大幅提高性能
  • 使用它序列化和反序列化实体及其关系
  • 与 Laravel 集成,没有框架无关的复杂开销
  • 属性可以通过中继表附加到零个、一个或多个实体
  • 属性可排序、可生成短链接、可翻译、可分组,并且最令人兴奋的是可缓存
  • 实体属性以更自然的方式处理,就像正常的 属性 一样,以 Eloquent 的所有可能方式
  • 实体属性也以更自然的方式处理,就像正常的 关系 一样,以 Eloquent 的所有可能方式

目录

简介

基础

前言

EAV 定义来自 维基百科

实体-属性-值模型(EAV)是一种数据模型,以空间有效的方式编码实体,其中可用于描述实体的属性(属性、参数)的数量可能很大,但实际上应用于特定实体的数量相对较小。

实体

实体代表一个需要动态扩展其属性的实模型。例如:ProductCustomerCompany 模型很可能是实体。

在这种情况下,实体将由 Eloquent 模型表示。

属性

属性充当我们希望添加到实体中的“列”。属性获得一个像 pricecitiescolors 这样的短链接以进行识别,并将附加到实体。它还将非常紧密地与数据类型实例配合,该实例将格式化其值,当从数据库写入或读取时进行转换。

此属性还将负责定义一些默认行为,例如数据验证或默认值。

这负责存储与特定属性和特定实体实例(行)相关联的数据值。

Rinvex Attributes 实现中,一个 Value 实例将代表与特定实体实例相关的属性内容。

根据数据类型,值存储在不同的表中。字符串值将存储在默认称为 attribute_varchar_values 的表中,而整数值将使用 attribute_integer_values,依此类推。这两个表的列相同,除了 content 列的数据类型,它是根据存储的数据类型进行适配的。

性能损失

EAV 模型以其性能不足而闻名。与查询任何其他水平结构相比,它在查询数据方面的复杂性也为人所知。这种范式在许多文章中被标记为反模式,并且关于是否应该使用它的争论很多。

由于我们将在不同的表中存储实体、属性和值,因此执行任何操作都需要执行多个查询。这意味着如果我们为实体注册了 4 个属性,则包将至少执行 5 个查询。

select * from `companies`
select * from `attribute_varchar_values` where `attribute_id` = '1' and `attribute_varchar_values`.`entity_id` in ('1', '2', '3', '4', '5') and `eav_attribute_varchar_values`.`entity_type` = 'App\Models\Company'
select * from `attribute_varchar_values` where `attribute_id` = '2' and `attribute_varchar_values`.`entity_id` in ('1', '2', '3', '4', '5') and `eav_attribute_varchar_values`.`entity_type` = 'App\Models\Company'
select * from `attribute_varchar_values` where `attribute_id` = '3' and `attribute_varchar_values`.`entity_id` in ('1', '2', '3', '4', '5') and `eav_attribute_varchar_values`.`entity_type` = 'App\Models\Company'
select * from `attribute_varchar_values` where `attribute_id` = '4' and `attribute_varchar_values`.`entity_id` in ('1', '2', '3', '4', '5') and `eav_attribute_varchar_values`.`entity_type` = 'App\Models\Company'

但是,有好消息! Rinvex Attributes 利用 Rinvex Cacheable 透明地缓存模型结果,可能将这些查询减少到只有一个甚至零个查询!是的,这是可能的,并且已默认实现!!

获得的灵活性

然而,尽管存在性能问题,EAV 提供了非常高的灵活性。它让我们可以拥有动态属性,可以在任何时候添加/删除而不影响数据库结构。当处理主要用于存储 NULL 值的列时,它也很有帮助。

考虑到您接受 EAV 带来的性能不足,该包已开发考虑到了灵活性,以便至少可以对抗性能问题。可以通过一次性加载所有与实体相关的值并在 PHP 逻辑中组织它们以建立关系来提高性能,但决定不这样做,以使数据库查询更加灵活。

如以下所述,此包将实体值加载得就像它们是自定义 Eloquent 关系一样。这就是我们为什么可以像常规 Eloquent 关系一样轻松查询它们的原因。

将值作为关系加载将允许我们只加载可能需要用于特定情况的值,而其他一些值则未加载。它还将允许我们使用强大的 Eloquent 查询关系工具,这样我们就可以根据将直接应用于值内容条件的条件轻松过滤从数据库中检索的实体。

更多技术细节

Rinvex\Attributes\Traits\Attributable

此特质是最重要的,让其他类协同工作。

它负责处理实体内部的交互。此特质执行 EAV 属性的 setget 操作,调用 RelationBuilder 类,它将关系方法添加到 $entityAttributeRelations 数组。这些关系可以像通常一样调用,因为我们正在重写 __call 魔法方法以查找这些调用。它负责为保存和删除设置事件监听器,添加全局范围,并检索与该实体相关的属性。

当尝试访问实体属性时,如果它与 EAV 属性对应,则此特质包含提供其值、创建新值实例、更新集合或任何其他 set/get 交互的逻辑。

在读取值时没有太多事情要检查,如果值存在,我们将对其进行格式化并提供,否则我们将返回 null 或空集合。

在设置值时,事情变得稍微复杂一些。在设置值时,我们需要考虑三件事

  • 设置一个在数据库中不存在的单个值,因此我们必须创建新的模型实例并将其与属性和实体相关联。
  • 更新现有单个值模型(数据库行)的内容。
  • 用新的集合替换现有的(或空的)值集合,因此我们需要删除之前存储的值(从数据库中删除)。

它还覆盖了一些实体方法,如 bootIfNotBootedrelationsToArraysetRelationgetRelationValue,以便以尽可能多的方式提供与 Eloquent 模型的平滑和无缝集成。它将所有内容连接在一起。

// To build entity relations for every instance
bootIfNotBooted();

// To include attributes as relations when converting to array/json
relationsToArray();

// To link entity & attribute to value collections (multi-valued attributes)
setRelation()

// To let Eloquent use our attribute relations as part of the model
getRelationValue()

Rinvex\Attributes\Support\RelationBuilder

该类根据它们的类型创建 Eloquent 关系到属性值。如果是多值,则提供 hasMany 关系,否则仅提供一个 hasOne。此类创建返回此类关系的闭包,可以直接从实体模型中调用。这些闭包存储在 \Rinvex\Attributes\Traits\Attributable 特性中的 $entityAttributeRelations 属性中。

安装

  1. 通过 composer 安装包

    composer require rinvex/laravel-attributes
  2. 发布资源(迁移和配置文件)

    php artisan rinvex:publish:attributes
  3. 执行以下命令执行迁移

    php artisan rinvex:migrate:attributes
  4. 完成!

使用

将 EAV 添加到 Eloquent 模型

Rinvex Attributes 是专门为 Eloquent 制作的,并且对于任何其他 Laravel 相关方面,都非常重视简洁性。要向 Eloquent 模型添加 EAV 功能,只需像这样使用 \Rinvex\Attributes\Traits\Attributable 特性

class Company extends Model
{
    use \Rinvex\Attributes\Traits\Attributable;
}

就是这样,我们只需要将此特性包含在我们的 Eloquent 模型中!

核心类型

\Rinvex\Attributes\Models\Type\Text::class
\Rinvex\Attributes\Models\Type\Boolean::class
\Rinvex\Attributes\Models\Type\Integer::class
\Rinvex\Attributes\Models\Type\Varchar::class
\Rinvex\Attributes\Models\Type\Datetime::class

注册您的类型

Rinvex Attributes 默认不注册任何类型,因为这被认为是实现细节,因此您需要注册上面列出的核心类型,或扩展它们并仅注册您自定义的类型。

use Rinvex\Attributes\Models\Attribute;

Attribute::typeMap([
    'varchar' => Rinvex\Attributes\Models\Type\Varchar::class,
    // ...
    'custom' => \Path\To\Your\Type::class,
]);

注意:虽然您可以从应用程序的任何地方注册自定义类型,但建议在服务提供者的 boot 方法中这样做。

注册您的实体

// Push your entity fully qualified namespace
app('rinvex.attributes.entities')->push(\Path\To\Your\Entity::class);

// Or push the morph class alias if any
app('rinvex.attributes.entities')->push('entity');

您可以在应用程序的任何地方调用 'rinvex.attributes.entities' 服务,在任何请求生命周期中(建议在服务提供者的 boot 方法中)。它是一个单例对象,包含一个纯 Laravel Collection

创建新属性

就像任何正常的 Eloquent 模型一样,您可以创建属性如下

app('rinvex.attributes.attribute')->create([
    'slug' => 'size',
    'type' => 'varchar',
    'name' => 'Product Size',
    'entities' => ['App\Models\Company', 'App\Models\Product'],
]);

管理属性实体

每当您需要获取附加到特定属性的实体时,您可以这样做

$attribute = app('rinvex.attributes.attribute')->find(1);

// Get attribute entities collection
$attribute->entities

// Get attribute entities query builder
$attribute->entities();

// Delete attached attribute entities
$attribute->entities()->delete();

// Attach attribute entities
$attribute->entities()->createMany([
    [...],
    [...],
    [...],
]);

// Alternative way of attaching attribute entities
$attribute->fill([
    'entities' => ['App\Models\Company', 'App\Models\Product'],
])->save();

// Get all attribute values of type varchar
$values = $attribute->values(\Rinvex\Attributes\Models\Type\Varchar::class)->get();

分配值

您可以将新创建的自定义属性视为正常属性,是的!所有属性都是平等的!😄

您需要证据吗?好吧,看看以下示例,我们假设 price 是我们刚刚创建并链接到我们的 \App\Models\Product 模型的自定义属性

// Single value assignment
$product = \App\Models\Product::find(1);
$product->price = 123;
$product->save();

// Mass assignment
$product = \App\Models\Product::find(1);
$product->fill(['price' => 123])->save();

是的,就是这样。简单!您可以将自定义属性像正常属性一样处理,没有区别。您所知道的关于 eloquent 的所有好东西在这里也都适用,无论是更新单个字段、批量赋值、创建还是更新,它只是工作!

Rinvex\Attributes\Support\ValueCollection

Rinvex Attributes 允许您注册多值属性。为了使处理集合更容易,我们包含了一个新的集合类型,该类型仅扩展 Illuminate\Database\Eloquent\Collection 并提供一些额外功能。此类允许我们从属性中添加和删除值。它基本上允许用户在不担心创建 Value 模型实例的情况下玩转集合类。以下是一些代码示例

// This is how it works
$entity->cities->add('Alexandria');

// And this is what you would have to do without this collection:
$value = new Varchar(['content' => 'Alexandria', 'attribute_id' => 1, 'entity_type' => 'App\Models\Company', 'entity_id' => 1]);
$entity->cities->push($value);

// You could also pass an array
$entity->cities->add(['Alexandria', 'Cairo']);

集合可能会得到改进并添加更多功能,但就目前而言已经足够。基于值的模型替换了 Eloquent 方法 newCollection,以便在处理多值属性时返回此类集合。

查询模型

Rinvex Attributes 尽量以 Eloquent 通常的方式做所有事情。当加载模型时,它内部为每个实体属性创建一个常规关系。这意味着我们可以像在查询 Eloquent 关系时那样查询我们注册的属性值。

// Cities is an entity attribute
$companies = Company::whereHas('Cities', function (\Illuminate\Database\Eloquent\Builder $builder) {
    $builder->where('content', 'Alexandria');
})->get();

或者简单地使用以下内置查询作用域

$companies = Company::hasAttribute('Cities', 'Alexandria')->get();

当然,您可以像正常 Eloquent 属性一样获取实体属性,或者作为原始关系获取

$company = Company::find(1);

// Get entity attributes
$company->cities;

// Get entity raw relation
$company->cities();

预加载

Rinvex 属性 考虑了强大的 Eloquent 预加载系统。当访问 Eloquent 模型中的实体属性时,它将像 Eloquent 在处理关系时那样,及时加载。然而,我们可以使用 Eloquent 预加载来操作 Rinvex 属性,以提高性能并避免 n+1 查询问题。

Rinvex 属性 预留了一个特殊的关系名称用于加载所有注册的属性。这个关系称为 eav。当使用 eav 来加载值时,它将加载与我们要处理的实体相关的所有属性,就像您在 $with 模型属性中明确包含所有关系一样。

延迟预加载

同样,就像任何常规的 Eloquent 关系一样,我们可以决定何时加载我们的属性。就像您通常加载一个关系一样去做。

$company->load('eav');
$company->load('cities', 'colors');

使用 $with 进行自动加载

Eloquent 提供了一个 $with,它接受一个数组,包含应该预加载的关系。我们也可以使用它。

namespace App\Models;

use Rinvex\Attributes\Traits\Attributable;

class Company extends Model
{
    use Attributable;

    // Eager loading all the registered attributes
    protected $with = ['eav'];

    // Or just load a few of them
    protected $with = ['cities', 'colors'];
}

注意:如果您的模型预加载了 eav 关系,并且它已被排队用于发送通知,这可能会引起一些问题,因为 eav 关系是通过全局范围评估的,而用于排队通知的反序列化 SerializesAndRestoresModelIdentifiers 特性不会使用全局范围,因此您将遇到 "Call to undefined relationship [eav] on model [App\Models\Company]" 异常。

变更日志

有关项目的完整历史记录,请参阅 变更日志

支持

以下支持渠道尽在您的指尖

贡献 & 协议

感谢您考虑为这个项目做出贡献!贡献指南可以在 CONTRIBUTING.md 中找到。

欢迎提交错误报告、功能请求和拉取请求。

安全漏洞

如果您在此项目中发现安全漏洞,请发送电子邮件至 help@rinvex.com。所有安全漏洞都将得到及时解决。

关于 Rinvex

Rinvex 是一家成立于埃及亚历山大,自 2016 年 6 月以来专门为中小企业提供集成企业解决方案的软件解决方案初创公司。我们相信,我们的动力“价值、影响力和影响力”是我们与众不同的地方,并通过软件的力量释放我们哲学的无限可能性。我们喜欢称之为“生活速度的创新”。这就是我们如何为推进人类文明做出自己的贡献。

许可证

本软件按照 MIT 许可证(MIT) 发布。

(c) 2016-2022 Rinvex LLC,部分权利保留。