我对您的问题有些困惑,但您首先要做的是创建所有模型和关系。您不一定需要为四个表中的每一个提供模型,但我强烈推荐它。
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);
}
}