rinvex / attributes
Rinvex Attributes是一个强大、智能且集成的实体-属性-值(EAV)模型实现,用于Laravel Eloquent,能够轻松地将实体属性隐式地作为关系进行管理。它利用Laravel Eloquent的力量,实现了平滑无缝的集成。
Requires
- php: ^7.1.3
- illuminate/console: ~5.6.0
- illuminate/database: ~5.6.0
- illuminate/support: ~5.6.0
- jeremeamia/superclosure: ^2.3.0
- rinvex/cacheable: dev-develop
- rinvex/support: dev-develop
- spatie/eloquent-sortable: ^3.4.0
- spatie/laravel-sluggable: ^2.1.0
- spatie/laravel-translatable: ^2.1.0
- watson/validating: ^3.1.0
Requires (Dev)
- codedungeon/phpunit-result-printer: ^0.19.0
- illuminate/container: ~5.6.0
- orchestra/testbench: ^3.6
- phpunit/phpunit: ^7.0.0
This package is auto-updated.
Last update: 2022-02-01 13:09:40 UTC
README
Rinvex Attributes是一个强大、智能且集成的实体-属性-值(EAV)模型实现,用于Laravel Eloquent,能够轻松地将实体属性隐式地作为关系进行管理。它利用Laravel Eloquent的力量,实现了平滑无缝的集成。
⚠️ 此包已重命名,现在由 rinvex/laravel-attributes 维护,作者建议使用新包。旧包支持Laravel v5.6及以下版本,而新包支持Laravel v5.7及以上版本。
致谢说明
此包是IsraelOrtuno的出色EAV包的重写分支,原始致谢归原作者所有。经过广泛重写,虽然核心概念相同,但我们认为其本质上是好的。此分支的主要差异包括:
- 利用rinvex/cacheable大幅提升性能
- 使用它序列化和反序列化实体及其关系
- 无缝集成Laravel,没有框架无关的复杂开销
- 属性可以通过中继表附加到零个、一个或多个实体
- 属性可以排序、生成缩写、可翻译、分组,并且最激动人心的是可以缓存
- 实体属性被更自然地当作普通的 属性,以Eloquent的每一种可能的方式处理
- 实体属性也被更自然地当作普通的 关系,以Eloquent的每一种可能的方式处理
目录
简介
基础
前言
EAV定义来自维基百科
实体-属性-值模型(EAV)是一种数据模型,以空间高效的方式编码实体,其中可以用作描述它们的属性(属性、参数)的数量可能很大,但实际上将应用于特定实体的属性数量相对较小。
实体
实体代表需要动态扩展其属性的实体模型。例如:Product
、Customer
或Company
等模型很可能是实体。
在这种情况下,实体将由Eloquent模型表示。
属性
属性充当我们想要添加到实体中的“列”。属性获得一个slug,例如price
、cities
或colors
,以便进行识别,并将其附加到实体上。它还将与数据类型实例紧密协作,在写入或从数据库读取时对值进行转换或格式化。
此属性还负责定义一些默认行为,如数据验证或默认值。
值
这负责存储与特定属性和特定实体实例(行)相关的数据值。
在Rinvex Attributes
实现中,值实例将代表与特定实体实例相关的属性内容。
值根据其数据类型存储在不同的表中。字符串值将存储在默认称为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属性的set
和get
操作,调用添加关系方法到$entityAttributeRelations
数组的RelationBuilder
类。这些关系可以像通常一样调用,因为我们重写了魔法方法__call
以查找这些调用。它负责设置保存和删除的事件监听器,添加全局作用域,并获取与该实体相关的属性。
当尝试访问实体属性时,如果它对应于EAV属性,这个特性包含提供其值的逻辑,创建新的值实例,更新集合或其他任何set/get交互。
在读取值时没有太多要检查的事情,如果值存在,我们只需格式化并提供它,否则我们将返回null或空集合。
在设置值时,事情变得稍微复杂一些。在设置值时,我们目前要考虑三件事
- 设置一个在数据库中不存在的单个值,因此我们必须创建新的模型实例并将其与属性和实体关联。
- 更新现有单个值模型的(数据库行)内容。
- 用新的替换现有(或空的)值集合,因此我们必须删除先前存储的值(从数据库中删除)。
它还重写了几个实体方法,如bootIfNotBooted
、relationsToArray
、setRelation
、getRelationValue
,以提供与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
属性中。
安装
-
使用composer安装此包
composer require rinvex/attributes
-
执行以下命令执行迁移
php artisan rinvex:migrate:attributes
-
完成!
用法
将EAV添加到Eloquent模型
Rinvex Attributes专门为Eloquent设计,简洁性被高度重视,就像在Laravel相关方面一样。要将EAV功能添加到您的Eloquent模型,只需像这样使用\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, // ... '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 集合。
创建新属性
就像任何正常的 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('varchar')->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
并提供了一些额外功能。这个类允许我们从属性中添加和删除值。它基本上是让用户在没有担心创建值模型实例的情况下玩转集合类。一些代码将帮助这里
// 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 Attributes 考虑了强大的 Eloquent 预加载系统。当在 Eloquent 模型中访问实体属性时,它将像 Eloquent 在处理关系时那样及时加载。但是,我们可以使用 Eloquent 预加载与 Rinvex Attributes 一起工作,以获得更好的性能并避免 n+1 查询问题。
Rinvex Attributes 为加载所有已注册属性保留了一个特殊的关系名称。这个关系称为 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
特性在反序列化排队中的模型时没有全局作用域,因此你将遇到 "在模型 [App\Models\Company] 上调用未定义的关系 [eav]" 异常。
更新日志
有关项目的完整历史,请参阅更新日志。
支持
以下支持渠道随时可供您使用
贡献 & 协议
感谢您考虑为该项目做出贡献!贡献指南可在 CONTRIBUTING.md 中找到。
欢迎提交错误报告、功能请求和拉取请求。
安全漏洞
如果您在此项目中发现了安全漏洞,请发送电子邮件至 help@rinvex.com。所有安全漏洞都将得到及时处理。
关于 Rinvex
Rinvex 是一家软件解决方案初创公司,自 2016 年 6 月以来在埃及亚历山大成立,专门为中小企业提供集成企业解决方案。我们相信,我们的驱动力是“价值、影响力和影响力”,这是我们与众不同的地方,通过软件的力量释放我们哲学的无限可能性。我们喜欢称之为“生活速度的创新”。这就是我们为推动人类进步所做的一份贡献。
许可
本软件根据MIT 许可证 (MIT)发布。
(c) 2016-2018 Rinvex LLC,部分权利保留。