From c54d912647b402d046ab42264f5c1052509c030a Mon Sep 17 00:00:00 2001 From: indy koning Date: Fri, 20 Jun 2025 13:34:39 +0200 Subject: [PATCH 1/9] Get non-flat table attributes using relations instead of join --- src/Http/Controllers/ProductController.php | 2 +- src/Models/Product.php | 54 +++++++++++++++++++ src/Models/ProductAttribute.php | 37 +++++++++++++ .../Traits/Product/SelectAttributeScopes.php | 2 +- 4 files changed, 93 insertions(+), 2 deletions(-) create mode 100644 src/Models/ProductAttribute.php diff --git a/src/Http/Controllers/ProductController.php b/src/Http/Controllers/ProductController.php index cc11e2460..9876023fa 100644 --- a/src/Http/Controllers/ProductController.php +++ b/src/Http/Controllers/ProductController.php @@ -12,7 +12,7 @@ public function show(int $productId) $productModel = config('rapidez.models.product'); $product = $productModel::selectForProductPage() ->withEventyGlobalScopes('productpage.scopes') - ->with('options') + ->with('options', 'attrs') ->findOrFail($productId); $attributes = [ diff --git a/src/Models/Product.php b/src/Models/Product.php index b15cb8acb..a2216a9d9 100644 --- a/src/Models/Product.php +++ b/src/Models/Product.php @@ -233,6 +233,60 @@ public function getThumbnailAttribute($image): ?string return $this->getImageAttribute($image); } + public function attrs(): HasMany + { + return $this->hasMany( + ProductAttribute::class, + 'entity_id', + 'entity_id', + ); + } + + public function getAttribute($key) + { + if (($value = parent::getAttribute($key)) !== null || $this->hasAttribute($key)) { + return $value; + } + + // TOOD: Not sure if this is very efficient, first we're + // searching for the attribute by code for the id and + // after that we're searching for the attribute id + // between the product attributes for the value. + $attributeModel = config('rapidez.models.attribute'); + $attributes = $attributeModel::getCachedWhere(function ($attribute) use ($key) { + return $attribute['code'] == $key; + }); + + if (!count($attributes) || !$attribute = reset($attributes)) { + return null; + } + + $this->loadMissing('attrs'); + // TODO: Check for a custom value for a store. So if store 1 overwrites store 0. + if (!$value = optional($this->attrs->firstWhere('attribute_id', $attribute['id']))->value) { + return null; + } + + if ($attribute['input'] == 'multiselect') { + foreach (explode(',', $value) as $optionValueId) { + $values[] = OptionValue::getCachedByOptionId($optionValueId); + } + $this->setAttribute($key, $values); + return $values; + } + + if ($attribute['input'] == 'select' && $attribute['type'] == 'int' && !$attribute['system']) { + $value = OptionValue::getCachedByOptionId($value); + } + + if ($key == 'url_key') { + return '/' . $value . Config::getValue('catalog/seo/product_url_suffix', options: ['default' => '.html']); + } + + $this->setAttribute($key, $value); + return $value; + } + protected function breadcrumbCategories(): Attribute { return Attribute::make( diff --git a/src/Models/ProductAttribute.php b/src/Models/ProductAttribute.php new file mode 100644 index 000000000..f3785c25c --- /dev/null +++ b/src/Models/ProductAttribute.php @@ -0,0 +1,37 @@ +select([ + 'entity_id', + 'attribute_id', + 'store_id', + 'value', + ]) + ->whereIn('store_id', [config('rapidez.store'), 0]) + ->whereNotNull('value'); + + $baseQuery = clone $builder->getQuery(); + foreach (['int', 'text', 'decimal'] as $type) { + $typeTable = 'catalog_product_entity_'.$type; + $typeQuery = (clone $baseQuery)->from($typeTable); + $typeQuery->wheres[0]['column'] = $typeTable.'.entity_id'; + $builder->unionAll($typeQuery); + } + }); + } +} diff --git a/src/Models/Traits/Product/SelectAttributeScopes.php b/src/Models/Traits/Product/SelectAttributeScopes.php index eadbaab5d..febbda8ea 100644 --- a/src/Models/Traits/Product/SelectAttributeScopes.php +++ b/src/Models/Traits/Product/SelectAttributeScopes.php @@ -19,7 +19,7 @@ public function scopeSelectForProductPage(Builder $query): Builder { $attributeModel = config('rapidez.models.attribute'); $this->attributesToSelect = Arr::pluck($attributeModel::getCachedWhere(function ($attribute) { - return $attribute['productpage'] || in_array($attribute['code'], [ + return $attribute['flat'] && $attribute['productpage'] || in_array($attribute['code'], [ 'name', 'meta_title', 'meta_description', From 8d43426b5820b1a6711cede887002ade1305b894 Mon Sep 17 00:00:00 2001 From: indykoning Date: Fri, 20 Jun 2025 11:35:10 +0000 Subject: [PATCH 2/9] Apply fixes from Duster --- src/Models/Product.php | 8 +++++--- src/Models/ProductAttribute.php | 6 ++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Models/Product.php b/src/Models/Product.php index a2216a9d9..76e6602f5 100644 --- a/src/Models/Product.php +++ b/src/Models/Product.php @@ -257,13 +257,13 @@ public function getAttribute($key) return $attribute['code'] == $key; }); - if (!count($attributes) || !$attribute = reset($attributes)) { + if (! count($attributes) || ! $attribute = reset($attributes)) { return null; } $this->loadMissing('attrs'); // TODO: Check for a custom value for a store. So if store 1 overwrites store 0. - if (!$value = optional($this->attrs->firstWhere('attribute_id', $attribute['id']))->value) { + if (! $value = optional($this->attrs->firstWhere('attribute_id', $attribute['id']))->value) { return null; } @@ -272,10 +272,11 @@ public function getAttribute($key) $values[] = OptionValue::getCachedByOptionId($optionValueId); } $this->setAttribute($key, $values); + return $values; } - if ($attribute['input'] == 'select' && $attribute['type'] == 'int' && !$attribute['system']) { + if ($attribute['input'] == 'select' && $attribute['type'] == 'int' && ! $attribute['system']) { $value = OptionValue::getCachedByOptionId($value); } @@ -284,6 +285,7 @@ public function getAttribute($key) } $this->setAttribute($key, $value); + return $value; } diff --git a/src/Models/ProductAttribute.php b/src/Models/ProductAttribute.php index f3785c25c..28b6d35f7 100644 --- a/src/Models/ProductAttribute.php +++ b/src/Models/ProductAttribute.php @@ -3,8 +3,6 @@ namespace Rapidez\Core\Models; use Illuminate\Database\Eloquent\Builder; -use Illuminate\Support\Facades\DB; -use Rapidez\Core\Models\Model; class ProductAttribute extends Model { @@ -27,9 +25,9 @@ protected static function boot(): void $baseQuery = clone $builder->getQuery(); foreach (['int', 'text', 'decimal'] as $type) { - $typeTable = 'catalog_product_entity_'.$type; + $typeTable = 'catalog_product_entity_' . $type; $typeQuery = (clone $baseQuery)->from($typeTable); - $typeQuery->wheres[0]['column'] = $typeTable.'.entity_id'; + $typeQuery->wheres[0]['column'] = $typeTable . '.entity_id'; $builder->unionAll($typeQuery); } }); From 46a59694066b29584f4634b045bfe5d10cd04b4f Mon Sep 17 00:00:00 2001 From: indy koning Date: Fri, 20 Jun 2025 13:39:34 +0200 Subject: [PATCH 3/9] Still allow select type being joined --- src/Models/Traits/Product/SelectAttributeScopes.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Models/Traits/Product/SelectAttributeScopes.php b/src/Models/Traits/Product/SelectAttributeScopes.php index febbda8ea..c3c085d60 100644 --- a/src/Models/Traits/Product/SelectAttributeScopes.php +++ b/src/Models/Traits/Product/SelectAttributeScopes.php @@ -19,7 +19,7 @@ public function scopeSelectForProductPage(Builder $query): Builder { $attributeModel = config('rapidez.models.attribute'); $this->attributesToSelect = Arr::pluck($attributeModel::getCachedWhere(function ($attribute) { - return $attribute['flat'] && $attribute['productpage'] || in_array($attribute['code'], [ + return ($attribute['flat'] || $attribute['type'] === 'select') && $attribute['productpage'] || in_array($attribute['code'], [ 'name', 'meta_title', 'meta_description', From 0dd2d78f86cc18a48ab2cf5b89cee8c48a6506d9 Mon Sep 17 00:00:00 2001 From: indykoning <15870933+indykoning@users.noreply.github.com> Date: Mon, 23 Jun 2025 14:20:18 +0200 Subject: [PATCH 4/9] Fixed error for some attributes --- src/Models/Product.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Models/Product.php b/src/Models/Product.php index 76e6602f5..790c2eac0 100644 --- a/src/Models/Product.php +++ b/src/Models/Product.php @@ -276,7 +276,7 @@ public function getAttribute($key) return $values; } - if ($attribute['input'] == 'select' && $attribute['type'] == 'int' && ! $attribute['system']) { + if ($attribute['input'] == 'select' && $attribute['type'] == 'int' && ! ($attribute['system'] ?? false)) { $value = OptionValue::getCachedByOptionId($value); } From fe9da860a5d9b54141e30b3b0bea5f03d7786bb6 Mon Sep 17 00:00:00 2001 From: Jade Date: Wed, 20 Aug 2025 10:26:29 +0200 Subject: [PATCH 5/9] Fix options being taken from the wrong attribute ID --- src/Models/OptionValue.php | 8 +++++--- src/Models/Product.php | 6 +++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/Models/OptionValue.php b/src/Models/OptionValue.php index faadc4b89..d270856cd 100644 --- a/src/Models/OptionValue.php +++ b/src/Models/OptionValue.php @@ -10,17 +10,19 @@ class OptionValue extends Model protected $primaryKey = 'value_id'; - public static function getCachedByOptionId(int $optionId): string + public static function getCachedByOptionId(int $optionId, ?int $attributeId = null, mixed $default = false): string { $cacheKey = 'optionvalues.' . config('rapidez.store'); $cache = Cache::store('rapidez:multi')->get($cacheKey, []); if (! isset($cache[$optionId])) { - $cache[$optionId] = html_entity_decode(self::where('option_id', $optionId) + $cache[$optionId] = html_entity_decode(self::where('eav_attribute_option_value.option_id', $optionId) ->whereIn('store_id', [config('rapidez.store'), 0]) + ->join('eav_attribute_option', 'eav_attribute_option.option_id', '=', 'eav_attribute_option_value.option_id') ->orderByDesc('store_id') + ->when($attributeId, fn($query) => $query->where('attribute_id', $attributeId)) ->first('value') - ->value ?? false); + ->value ?? $default); Cache::store('rapidez:multi')->forever($cacheKey, $cache); } diff --git a/src/Models/Product.php b/src/Models/Product.php index 790c2eac0..f3893d4b0 100644 --- a/src/Models/Product.php +++ b/src/Models/Product.php @@ -269,7 +269,7 @@ public function getAttribute($key) if ($attribute['input'] == 'multiselect') { foreach (explode(',', $value) as $optionValueId) { - $values[] = OptionValue::getCachedByOptionId($optionValueId); + $values[] = OptionValue::getCachedByOptionId($optionValueId, $attribute['id'], $optionValueId); } $this->setAttribute($key, $values); @@ -277,7 +277,7 @@ public function getAttribute($key) } if ($attribute['input'] == 'select' && $attribute['type'] == 'int' && ! ($attribute['system'] ?? false)) { - $value = OptionValue::getCachedByOptionId($value); + $value = OptionValue::getCachedByOptionId($value, $attribute['id'], $value); } if ($key == 'url_key') { @@ -292,7 +292,7 @@ public function getAttribute($key) protected function breadcrumbCategories(): Attribute { return Attribute::make( - get: function () { + get: function (): iterable { if (! $path = session('latest_category_path')) { return []; } From 8d69a0bb966bcfe20a7e6bc3b28c3570e7279ee8 Mon Sep 17 00:00:00 2001 From: Jade-GG Date: Wed, 20 Aug 2025 08:26:56 +0000 Subject: [PATCH 6/9] Apply fixes from Duster --- src/Models/OptionValue.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Models/OptionValue.php b/src/Models/OptionValue.php index d270856cd..3c27bf86d 100644 --- a/src/Models/OptionValue.php +++ b/src/Models/OptionValue.php @@ -20,7 +20,7 @@ public static function getCachedByOptionId(int $optionId, ?int $attributeId = nu ->whereIn('store_id', [config('rapidez.store'), 0]) ->join('eav_attribute_option', 'eav_attribute_option.option_id', '=', 'eav_attribute_option_value.option_id') ->orderByDesc('store_id') - ->when($attributeId, fn($query) => $query->where('attribute_id', $attributeId)) + ->when($attributeId, fn ($query) => $query->where('attribute_id', $attributeId)) ->first('value') ->value ?? $default); Cache::store('rapidez:multi')->forever($cacheKey, $cache); From 7903b10a2dd73b7f5db22cb1191df10d5e2093d6 Mon Sep 17 00:00:00 2001 From: Jade Date: Wed, 20 Aug 2025 10:29:04 +0200 Subject: [PATCH 7/9] Remove extra change --- src/Models/Product.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Models/Product.php b/src/Models/Product.php index f3893d4b0..8bbcdf273 100644 --- a/src/Models/Product.php +++ b/src/Models/Product.php @@ -292,7 +292,7 @@ public function getAttribute($key) protected function breadcrumbCategories(): Attribute { return Attribute::make( - get: function (): iterable { + get: function () { if (! $path = session('latest_category_path')) { return []; } From c11cd16cce74a47dbe574a5d7041c837ef93f4a9 Mon Sep 17 00:00:00 2001 From: indykoning <15870933+indykoning@users.noreply.github.com> Date: Tue, 14 Oct 2025 11:28:14 +0200 Subject: [PATCH 8/9] Update url_key code --- src/Models/Product.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Models/Product.php b/src/Models/Product.php index 8bbcdf273..46b0175fc 100644 --- a/src/Models/Product.php +++ b/src/Models/Product.php @@ -281,7 +281,7 @@ public function getAttribute($key) } if ($key == 'url_key') { - return '/' . $value . Config::getValue('catalog/seo/product_url_suffix', options: ['default' => '.html']); + return '/' . ($value ? $value . Rapidez::config('catalog/seo/product_url_suffix') : 'catalog/product/view/id/' . $this->entity_id) } $this->setAttribute($key, $value); From a285298bfd648122cba1e41ca499b9bb6017feeb Mon Sep 17 00:00:00 2001 From: indykoning <15870933+indykoning@users.noreply.github.com> Date: Tue, 14 Oct 2025 11:29:29 +0200 Subject: [PATCH 9/9] Fixed typo --- src/Models/Product.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Models/Product.php b/src/Models/Product.php index 46b0175fc..eab8e21f7 100644 --- a/src/Models/Product.php +++ b/src/Models/Product.php @@ -281,7 +281,7 @@ public function getAttribute($key) } if ($key == 'url_key') { - return '/' . ($value ? $value . Rapidez::config('catalog/seo/product_url_suffix') : 'catalog/product/view/id/' . $this->entity_id) + return '/' . ($value ? $value . Rapidez::config('catalog/seo/product_url_suffix') : 'catalog/product/view/id/' . $this->entity_id); } $this->setAttribute($key, $value);