【问题标题】:Laravel ORM dynamic model attributesLaravel ORM 动态模型属性
【发布时间】:2019-12-21 08:41:53
【问题描述】:

我设置了一个数据库,但不知道如何在 laravel 中编写代码。我正在尝试从自定义输入中获取动态属性名称。

这是数据库设置:

Category:
-ID
-Name

Product:
-ID
-Category_id

Product_Attribute:
-ID
-Category_id
-Attribute_Name

Attribute_value:
-ID
-Product_id
-Product_attribute_id
-Value

每个属性可以有多个值,我没有一组属性列表,因为它可以根据类别/产品而改变。有些产品有一些属性,有些则没有。有些将具有与其他类别相同的键/名称,但出于我的目的很少重叠,但如果需要,我可以解决这个问题。

我有一种设置 laravel 的方法,这样我就可以查看键/值并按名称调用它们

echo $product->$Attribute_Name;

or

echo $product->attributes[$Attribute_Name];

or something similar

但我还需要提取属性名称 = y 且属性值 = X 的所有产品

select * from Products join Attribute_value on products.ID = Attribute_value.Product_id join Product_Attribute on Category_id = Products.Category_id and Product_Attribute.ID = Attribute_value.Product_attribute_id where Product_Attribute = '{attribute_name}' and Attribute_value = '{Attribute_value}'

这只是返回产品,而不是关联的数据或其他属性。我找不到简单的方法来加载这些数据,而无需构建一个类来填充它。理想情况下,我希望能够使用 ORM 更改值并保存它们,类似于一对多关系的工作方式。

我以前在数据库中见过这种类型的结构。我想知道是否有一种方法可以在 laravel 中轻松做到这一点,而无需创建一堆自定义函数来加载每个产品的属性。

谢谢

【问题讨论】:

    标签: php laravel orm


    【解决方案1】:

    我对您的问题有些困惑,但您首先要做的是创建所有模型和关系。您不一定需要为四个表中的每一个提供模型,但我强烈推荐它。

    class Category
    {
        public function products(): HasMany
        {
            return $this->hasMany(Product::class);
        }
    
        public function productAttributes(): HasMany
        {
            return $this->hasMany(ProductAttribute::class);
        }
    }
    
    class Product
    {
        public function category(): BelongsTo
        {
            return $this->belongsTo(Category::class);
        }
    
        public function attributeValues(): HasMany
        {
            return $this->hasMany(AttributeValue::class);
        }
    }
    
    class ProductAttribute
    {
        public function category(): BelongsTo
        {
            return $this->belongsTo(Category::class);
        }
    
        public function attributeValues(): HasMany
        {
            return $this->hasMany(AttributeValue::class);
        }
    }
    
    class AttributeValue
    {
        public function product(): BelongsTo
        {
            return $this->belongsTo(Product::class);
        }
    
        public function productAttribute(): BelongsTo
        {
            return $this->belongsTo(ProductAttribute::class);
        }
    }
    

    此代码希望您考虑 Laravel 的表和属性命名标准。

    定义类和关系后,您可以像这样加载带有属性的产品:

    $products = Product::query()
        ->with('attributeValues.productAttribute')
        ->where('category_id', $categoryId)
        ->get();
    

    因为这使得通过名称访问属性变得很痛苦......

    $product = $products->first();
    $color   = optional($product->attributeValues
                                ->where('productAttribute.name', 'color')
                                ->first())->value ?? 'white';
    

    ...您还可以覆盖 __get($name) 方法为您的属性添加一个不错的访问器:

    class Product
    {
        public function __get(string $name)
        {
            if ($name === 'attrs') {
                return (object) $this->attributeValues->mapWithKeys(function ($attributeValue) {
                    return [$attributeValue->productAttribute->name => $attributeValue->value];
                });
            }
    
            return parent::__get($name);
        }
    }
    

    这样做之后,您应该能够像这样访问您的属性:

    $product = $products->first();
    $color   = $product->attrs->color;
    
    // or if you need to retrieve an attribute by name stored in a variable
    $name = 'color';
    $attr = $product->attrs->$name;
    

    当然,您也可以省略 __get($name) 访问器中的 (object) 转换以返回一个数组。然后您会收到以下语法:$product->attrs['color']。无论哪种方式,如果属性未设置/不在数组中,这将返回错误。一定要抓住这个。您可能还想添加一些缓存以避免一遍又一遍地构建 attrs 对象/数组。

    请注意:$attributes 属性在 Eloquent 基础模型内部用于存储模型的所有属性。所以这个名字是保留的,你应该改用attrs之类的东西。


    编辑:getter 的另外两个选项如下:

    class Product
    {
        public function getColorAttribute(string $default = 'white'): string
        {
            return optional($this->attributeValues
                                 ->where('productAttribute.name', 'color')
                                 ->first())->value ?? $default;
        }
    
        public function getAttr(string $name, $default = null)
        {
            return optional($this->attributeValues
                                 ->where('productAttribute.name', $name)
                                 ->first())->value ?? $default;
        }
    }
    

    同样,你可以设计一个 setter:

    class Product
    {
        public function setAttr(string $name, $value): void
        {
            if ($this->hasAttr($name)) {
                $attr = $this->attributeValues
                             ->where('productAttribute.name', $name)
                             ->first();
                $attr->value = $value;
                $attr->save();
            } else {
                throw new \Exception(sprintf('This product may not have the attribute [%s].', $name));
            }
        }
    
        public function hasAttr(string $name): bool
        {
            return $this->attributeValues
                        ->contains('productAttribute.name', $name);
        }
    }
    

    【讨论】:

    • 谢谢,我想这就是我需要的。当我从覆盖的 __get() 函数返回对象时,我猜我不会覆盖并保存它,因为它不是模型的实例?
    • 是的,它只是一个吸气剂。恐怕写一个 setter 稍微复杂一些。
    • 你也可以查看现有的包,比如github.com/rinvex/laravel-attributes
    • 谢谢你的回答引起了我的大部分担忧,并且会调查那个包裹谢谢不知道他们存在
    • 我添加了有关其他 getter 类型和 setter 示例的更多详细信息。请注意,setter 相当复杂,因为您不仅需要了解AttributeValues,还需要了解ProductAttributes。因此,我只实现了更新属性,而不是添加新属性。
    猜你喜欢
    • 2019-03-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-03-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多