Feature: Adding Custom Fields to Products
This commit is contained in:
@@ -5,12 +5,15 @@ namespace Modules\Core\Providers;
|
||||
use Illuminate\Console\Scheduling\Schedule;
|
||||
use Illuminate\Support\Facades\Event;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use Lunar\Models\Product;
|
||||
use Lunar\Facades\ModelManifest;
|
||||
use Lunar\Models\Contracts\Product as ProductContract;
|
||||
use Lunar\Models\Product as LunarProduct;
|
||||
use Lunar\Models\ProductOption;
|
||||
use Lunar\Models\ProductOptionValue;
|
||||
use Modules\Core\Catalog\Events\ProductDeleted;
|
||||
use Modules\Core\Catalog\Events\ProductSaved;
|
||||
use Modules\Core\Catalog\Listeners\ReindexProductsRecommendingProduct;
|
||||
use Modules\Core\Catalog\Models\Product;
|
||||
use Modules\Core\Catalog\Observers\ProductOptionReindexObserver;
|
||||
use Modules\Core\Catalog\OptionTypes\ColorOptionType;
|
||||
use Modules\Core\Catalog\Services\ProductOptionTypeManager;
|
||||
@@ -40,13 +43,55 @@ class CatalogServiceProvider extends ServiceProvider
|
||||
ProductOptionValue::saved(fn (ProductOptionValue $value) => $observer->valueSaved($value));
|
||||
ProductOptionValue::deleted(fn (ProductOptionValue $value) => $observer->valueDeleted($value));
|
||||
|
||||
Product::saved(fn (Product $product) => Event::dispatch(new ProductSaved($product)));
|
||||
Product::deleted(fn (Product $product) => Event::dispatch(new ProductDeleted($product->id)));
|
||||
// Registered on BOTH classes — Eloquent model events are keyed by
|
||||
// the literal class ::saved()/::deleted() was called on
|
||||
// (registerModelEvent() uses static::class at registration time),
|
||||
// not by inheritance, so a listener registered only on one class
|
||||
// never fires for an instance of the other. Code resolving Product
|
||||
// through the contract (Filament's own ProductResource, anything
|
||||
// using app(Contracts\Product::class)) gets the subclass once
|
||||
// ModelManifest::replace() below takes effect; code that still
|
||||
// hardcodes `Lunar\Models\Product` directly (e.g. MigrateImport\
|
||||
// Shopify\ShopifyExportImporter — importing has no reason to need
|
||||
// the subclass's own custom_fields cast) keeps creating base-class
|
||||
// instances. Both must dispatch ProductSaved/ProductDeleted, since
|
||||
// ReindexProductsRecommendingProduct listens to those regardless
|
||||
// of which path created/updated the product.
|
||||
$dispatchSaved = fn (LunarProduct $product) => Event::dispatch(new ProductSaved($product));
|
||||
$dispatchDeleted = fn (LunarProduct $product) => Event::dispatch(new ProductDeleted($product->id));
|
||||
|
||||
LunarProduct::saved($dispatchSaved);
|
||||
LunarProduct::deleted($dispatchDeleted);
|
||||
Product::saved($dispatchSaved);
|
||||
Product::deleted($dispatchDeleted);
|
||||
|
||||
Event::listen(ProductSaved::class, [ReindexProductsRecommendingProduct::class, 'handleSaved']);
|
||||
Event::listen(ProductDeleted::class, [ReindexProductsRecommendingProduct::class, 'handleDeleted']);
|
||||
|
||||
$this->app->booted(function () {
|
||||
// Deferred to booted() to run after every provider (Lunar's
|
||||
// own included) has finished its own boot() — matches
|
||||
// 3dealer's own AppServiceProvider, which registers Customer
|
||||
// the same way for the same reason.
|
||||
//
|
||||
// The first argument MUST be the CONTRACT
|
||||
// (Lunar\Models\Contracts\Product), not the concrete
|
||||
// Lunar\Models\Product — HasModelExtending::modelClass()
|
||||
// (which every Lunar model's __callStatic()/newModelQuery()
|
||||
// consults to decide "is there a registered replacement for
|
||||
// me") looks itself up by
|
||||
// ModelManifest::guessContractClass(static::class), which
|
||||
// resolves to the CONTRACT interface, then does
|
||||
// ModelManifest::get($thatContract) — so the manifest must be
|
||||
// keyed by the contract, or the lookup simply misses and
|
||||
// silently falls back to the base class. Caught in practice —
|
||||
// passing the concrete LunarProduct::class here (mirroring
|
||||
// Modules\Core\Customer\Providers\CustomerServiceProvider's
|
||||
// own replace() call, which has this exact same bug) left
|
||||
// Product::modelClass() resolving to Lunar\Models\Product no
|
||||
// matter what, until this was corrected.
|
||||
ModelManifest::replace(ProductContract::class, Product::class);
|
||||
|
||||
// A full nightly reindex, on top of the per-event reindexing
|
||||
// above — catches everything event-driven reindexing
|
||||
// deliberately doesn't cover: a newly-created product not yet
|
||||
@@ -58,8 +103,14 @@ class CatalogServiceProvider extends ServiceProvider
|
||||
// documents, so a deploy that changed ProductIndexer's field
|
||||
// list self-heals here even if `lunar:meilisearch:setup`
|
||||
// wasn't run manually after that deploy.
|
||||
// References the subclass, not 'Lunar\Models\Product' — Scout's
|
||||
// own reindex loop (Searchable::makeAllSearchable()) queries
|
||||
// via whatever class name is passed here, so this determines
|
||||
// which class's casts (custom_fields included) are actually
|
||||
// applied to $model in ProductIndexer::toSearchableArray()
|
||||
// during this nightly full reindex.
|
||||
$this->app->make(Schedule::class)
|
||||
->command('lunar:search:index', ['Lunar\\Models\\Product', '--refresh'])
|
||||
->command('lunar:search:index', [Product::class, '--refresh'])
|
||||
->dailyAt('03:00');
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user