tec-more/attributes

Rinvex Attributes 是 Laravel Eloquent 的一个强大、智能和集成的实体-属性-值模型 (EAV) 实现,它以简洁的方式轻松地管理实体属性作为关系。它利用 Laravel Eloquent 的力量,实现流畅和无缝的集成。

v0.0.3 2017-09-09 18:25 UTC

This package is not auto-updated.

Last update: 2024-09-29 04:37:11 UTC


README

Rinvex Attributes 是 Laravel Eloquent 的一个强大、智能和集成的实体-属性-值模型 (EAV) 实现,它以简洁的方式轻松地管理实体属性作为关系。它利用 Laravel Eloquent 的力量,实现流畅和无缝的集成。

Packagist VersionEye Dependencies Scrutinizer Code Quality Code Climate Travis SensioLabs Insight StyleCI License

致谢通知

此包是基于 IsraelOrtuno 的出色 EAV 包 重新编写的分支,原始荣誉归他所有。经过广泛的修改,保持了其核心概念,我们认为它本质上是好的。此分支的主要差异包括

  • 利用 rinvex/cacheable 大幅提高性能
  • 使用它序列化和反序列化实体及其关系
  • 与 Laravel 集成,没有框架无关的复杂开销
  • 属性可以通过中继表附加到零个、一个或多个实体
  • 属性可排序、可缩写、可翻译、可分组,并且最令人兴奋的是可缓存
  • 实体属性被当作普通属性更自然地处理,以 Eloquent 的每种可能的方式
  • 实体属性也被当作普通关系更自然地处理,以 Eloquent 的每种可能的方式

目录

介绍

基础知识

序言

来自 维基百科 的 EAV 定义

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

实体

实体代表需要动态扩展其属性的实时模型。例如:像 ProductCustomerCompany 这样的模型可能是实体。

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

属性

属性充当我们想要添加到实体中的“列”。属性得到一个名称,如 pricecitycolors,以供识别,并将附加到实体上。它还将非常密切地与数据类型实例配合,在写入或从数据库读取时转换或格式化其值。

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

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

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 属性,则此特性包含提供其值、创建新的值实例、更新集合或其他任何设置/获取交互的逻辑。

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

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

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

它还覆盖了一些实体方法,例如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 (multivalued attributes)
setRelation()

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

Rinvex\Attributes\Support\ValueCollection

Rinvex Attributes 允许您注册多值属性。为了使操作集合更加简单,我们包含了一种新的集合类型,该类型仅扩展了Illuminate\Database\Eloquent\Collection并提供了一些额外功能。此类允许我们从属性中添加和删除值。它基本上是让用户可以玩集合类而无需担心创建值模型实例。以下是一些代码示例来帮助说明

// 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']);

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

Rinvex\Attributes\Support\RelationBuilder

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

安装

  1. 通过composer安装此包

    composer require rinvex/attributes
  2. 执行以下命令进行迁移

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

用法

将 EAV 添加到 Eloquent 模型

Rinvex Attributes 专门为Eloquent设计,并且非常注重简洁性,正如Laravel的其他相关方面一样。要将EAV功能添加到Eloquent模型,只需像这样使用\Rinvex\Attributes\Traits\Attributable特质

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

这就对了,我们只需将此特质包含在我们的Eloquent模型中即可!

注册您的自定义类型

app('rinvex.attributes.types')->push(\Path\To\Your\Type::class);

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

注册您的实体

app('rinvex.attributes.entities')->push(\Path\To\Your\Entity::class);

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

创建新属性

与任何正常的Eloquent模型一样,您可以按以下方式创建属性

app('rinvex.attributes.attribute')->create([
    'slug' => 'size',
    'name' => 'Product Size',
    'type' => 'Rinvex\Attributes\Models\Type\Varchar',
    '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')->get();

查询模型

Rinvex Attributes 尽量按照Eloquent通常的方式执行所有操作。在加载数据模型时,它内部为每个实体属性创建一个常规关系。这意味着我们可以像通常查询Eloquent关系一样查询我们已注册的属性值

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

或者简单地使用内建的查询作用域,如下所示

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

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

$company = Company::find(1);

// Get entity attributes
$company->cities;

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

预加载

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

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

惰性 eager 加载

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

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

使用 $with 进行自动加载

Eloquent 内置了 $with,它接受一个需要 eager 加载的关联数组。我们也可以使用它

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 = ['city', 'colors'];
}

注意:如果你的模型 eager 加载了 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-2017 Rinvex LLC,部分权利保留。