diff --git a/composer.json b/composer.json index 338cabb50..0cf3a27af 100644 --- a/composer.json +++ b/composer.json @@ -31,7 +31,7 @@ "matchish/laravel-scout-elasticsearch": "^7.11", "rapidez/blade-components": "^1.10", "rapidez/blade-directives": "^1.1", - "rapidez/laravel-multi-cache": "^2.0", + "rapidez/laravel-multi-cache": "^2.1", "tormjens/eventy": "^0.8" }, "require-dev": { diff --git a/src/Commands/IndexCommand.php b/src/Commands/IndexCommand.php index 5d6d52fba..6b4b9fb9c 100644 --- a/src/Commands/IndexCommand.php +++ b/src/Commands/IndexCommand.php @@ -3,6 +3,7 @@ namespace Rapidez\Core\Commands; use Illuminate\Console\Command; +use Illuminate\Support\Facades\Cache; use Rapidez\Core\Events\IndexAfterEvent; use Rapidez\Core\Events\IndexBeforeEvent; use Rapidez\Core\Facades\Rapidez; @@ -27,7 +28,7 @@ public function handle() ? Rapidez::getStores(explode(',', $this->option('store'))) : Rapidez::getStores(); - $this->call('cache:clear'); + Cache::driver('rapidez:multi')->tags(['attributes', 'swatches'])->flush(); IndexBeforeEvent::dispatch($this); 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/Attribute.php b/src/Models/Attribute.php index ed2056e57..6e525256a 100644 --- a/src/Models/Attribute.php +++ b/src/Models/Attribute.php @@ -46,7 +46,7 @@ protected function prefix(): CastsAttribute public static function getCachedWhere(callable $callback): array { - $attributes = Cache::store('rapidez:multi')->rememberForever('attributes.' . config('rapidez.store'), function () { + $attributes = Cache::store('rapidez:multi')->tags('attributes')->rememberForever('attributes.' . config('rapidez.store'), function () { return self::all()->toArray(); }); diff --git a/src/Models/Config.php b/src/Models/Config.php index b16d9ee08..79c931be3 100644 --- a/src/Models/Config.php +++ b/src/Models/Config.php @@ -86,7 +86,7 @@ public static function getValue( } if ($options['cache'] ?? true) { - $configCache = Cache::driver('rapidez:multi')->get('magento.config', []); + $configCache = Cache::driver('rapidez:multi')->tags('config')->get('magento.config', []); $cacheKey = implode( '.', [ @@ -126,7 +126,7 @@ public static function getValue( if (($options['cache'] ?? true) && isset($cacheKey)) { Arr::set($configCache, $cacheKey, $resultObject ? $result : false); - Cache::driver('rapidez:multi')->set('magento.config', $configCache); + Cache::driver('rapidez:multi')->tags('config')->set('magento.config', $configCache); } return (bool) $options['decrypt'] && is_string($result) ? static::decrypt($result) : $result; diff --git a/src/Models/OptionSwatch.php b/src/Models/OptionSwatch.php index 2196c367a..326afa443 100644 --- a/src/Models/OptionSwatch.php +++ b/src/Models/OptionSwatch.php @@ -22,7 +22,7 @@ public static function getCachedSwatchValues(): array return $attribute['text_swatch'] || $attribute['visual_swatch']; }), 'id', 'code'); - return Cache::rememberForever('swatchvalues', function () use ($swatchAttributes) { + return Cache::store('rapidez:multi')->tags('swatches')->rememberForever('swatchvalues', function () use ($swatchAttributes) { return self::select('eav_attribute.attribute_code') ->selectRaw('JSON_OBJECTAGG(`eav_attribute_option_value`.`option_id`, JSON_OBJECT( "label", COALESCE(`eav_attribute_option_value_store`.`value`, `eav_attribute_option_value`.`value`), diff --git a/src/Models/OptionValue.php b/src/Models/OptionValue.php index faadc4b89..69538b895 100644 --- a/src/Models/OptionValue.php +++ b/src/Models/OptionValue.php @@ -10,18 +10,20 @@ 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, []); + $cache = Cache::store('rapidez:multi')->tags('attributes')->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); - Cache::store('rapidez:multi')->forever($cacheKey, $cache); + ->value ?? $default); + Cache::store('rapidez:multi')->tags('attributes')->forever($cacheKey, $cache); } return $cache[$optionId]; diff --git a/src/Models/Product.php b/src/Models/Product.php index b15cb8acb..eab8e21f7 100644 --- a/src/Models/Product.php +++ b/src/Models/Product.php @@ -233,6 +233,62 @@ 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, $attribute['id'], $optionValueId); + } + $this->setAttribute($key, $values); + + return $values; + } + + if ($attribute['input'] == 'select' && $attribute['type'] == 'int' && ! ($attribute['system'] ?? false)) { + $value = OptionValue::getCachedByOptionId($value, $attribute['id'], $value); + } + + if ($key == 'url_key') { + return '/' . ($value ? $value . Rapidez::config('catalog/seo/product_url_suffix') : 'catalog/product/view/id/' . $this->entity_id); + } + + $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..28b6d35f7 --- /dev/null +++ b/src/Models/ProductAttribute.php @@ -0,0 +1,35 @@ +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..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['productpage'] || in_array($attribute['code'], [ + return ($attribute['flat'] || $attribute['type'] === 'select') && $attribute['productpage'] || in_array($attribute['code'], [ 'name', 'meta_title', 'meta_description',