From fc20edbb07f7077be18d86359f08ad4eec5362c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9as=20Lundgren?= <1066486+adevade@users.noreply.github.com> Date: Wed, 11 Sep 2024 11:04:31 +0200 Subject: [PATCH 1/5] Preload base prices on Product Variant page (#1934) --- .../Resources/ProductResource/Widgets/ProductOptionsWidget.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/admin/src/Filament/Resources/ProductResource/Widgets/ProductOptionsWidget.php b/packages/admin/src/Filament/Resources/ProductResource/Widgets/ProductOptionsWidget.php index a082be186d..2a50f3e01f 100644 --- a/packages/admin/src/Filament/Resources/ProductResource/Widgets/ProductOptionsWidget.php +++ b/packages/admin/src/Filament/Resources/ProductResource/Widgets/ProductOptionsWidget.php @@ -245,7 +245,7 @@ public function mapVariantPermutations($fillMissing = true): void )] )->toArray(); - $variants = $this->record->variants->load('values.option')->map(function ($variant) { + $variants = $this->record->variants->load(['basePrices.currency', 'basePrices.priceable', 'values.option'])->map(function ($variant) { return [ 'id' => $variant->id, 'sku' => $variant->sku, From 9eac4c75f1374691abaa2f38edaa7154427115da Mon Sep 17 00:00:00 2001 From: wychoong <67364036+wychoong@users.noreply.github.com> Date: Tue, 17 Sep 2024 16:30:34 +0800 Subject: [PATCH 2/5] Add helper function to get model morph name (#1947) --- packages/admin/src/Filament/Resources/ProductResource.php | 2 +- .../Resources/ProductResource/Pages/ListProducts.php | 2 +- .../admin/src/Filament/Resources/ProductTypeResource.php | 4 ++-- .../Support/Actions/Collections/CreateChildCollection.php | 2 +- .../Support/Actions/Collections/CreateRootCollection.php | 4 ++-- .../Support/Actions/Traits/CreatesChildCollections.php | 2 +- .../src/Support/Forms/Components/AttributeSelector.php | 4 ++-- .../admin/src/Support/Forms/Components/Attributes.php | 6 +++--- packages/admin/src/Support/Resources/BaseResource.php | 2 +- packages/core/database/factories/CartLineFactory.php | 2 +- .../database/factories/DiscountPurchasableFactory.php | 2 +- packages/core/database/factories/OrderLineFactory.php | 2 +- packages/core/database/factories/UrlFactory.php | 2 +- .../state/ConvertProductTypeAttributesToProducts.php | 4 ++-- packages/core/src/Base/Traits/HasAttributes.php | 2 +- packages/core/src/Base/Traits/HasModelExtending.php | 7 +++++++ packages/core/src/Console/InstallLunar.php | 6 +++--- packages/core/src/DiscountTypes/BuyXGetY.php | 4 ++-- packages/core/src/Models/Discount.php | 4 ++-- packages/core/src/Models/ProductType.php | 4 ++-- .../database/factories/ShippingExclusionFactory.php | 2 +- .../src/Drivers/ShippingMethods/Collection.php | 2 +- .../src/Drivers/ShippingMethods/FlatRate.php | 2 +- .../src/Drivers/ShippingMethods/FreeShipping.php | 2 +- .../src/Drivers/ShippingMethods/ShipBy.php | 2 +- .../ProductTypeResource/Pages/CreateProductTypeTest.php | 2 +- .../ProductTypeResource/Pages/EditProductTypeTest.php | 8 ++++---- tests/core/Unit/Base/Traits/HasModelExtendingTest.php | 4 ++++ 28 files changed, 51 insertions(+), 40 deletions(-) diff --git a/packages/admin/src/Filament/Resources/ProductResource.php b/packages/admin/src/Filament/Resources/ProductResource.php index 14b2d5440f..724c09a1a9 100644 --- a/packages/admin/src/Filament/Resources/ProductResource.php +++ b/packages/admin/src/Filament/Resources/ProductResource.php @@ -177,7 +177,7 @@ public static function getBaseNameFormComponent(): Component { $nameType = Attribute::whereHandle('name') ->whereAttributeType( - (new (static::getModel()))->getMorphClass() + static::getModel()::morphName() ) ->first()?->type ?: TranslatedText::class; diff --git a/packages/admin/src/Filament/Resources/ProductResource/Pages/ListProducts.php b/packages/admin/src/Filament/Resources/ProductResource/Pages/ListProducts.php index 82d7c6bb3c..f02e59b67f 100644 --- a/packages/admin/src/Filament/Resources/ProductResource/Pages/ListProducts.php +++ b/packages/admin/src/Filament/Resources/ProductResource/Pages/ListProducts.php @@ -52,7 +52,7 @@ public static function createRecord(array $data, string $model): Model $currency = Currency::getDefault(); $nameAttribute = Attribute::whereAttributeType( - (new $model)->getMorphClass() + $model::morphName() ) ->whereHandle('name') ->first() diff --git a/packages/admin/src/Filament/Resources/ProductTypeResource.php b/packages/admin/src/Filament/Resources/ProductTypeResource.php index a56958ca9c..31648eeb64 100644 --- a/packages/admin/src/Filament/Resources/ProductTypeResource.php +++ b/packages/admin/src/Filament/Resources/ProductTypeResource.php @@ -54,7 +54,7 @@ public static function getDefaultForm(Forms\Form $form): Forms\Form Forms\Components\Tabs\Tab::make(__('lunarpanel::producttype.tabs.product_attributes.label')) ->schema([ AttributeSelector::make('mappedAttributes') - ->withType((new Product)->getMorphClass()) + ->withType(Product::morphName()) ->relationship(name: 'mappedAttributes') ->label('') ->columnSpan(2), @@ -62,7 +62,7 @@ public static function getDefaultForm(Forms\Form $form): Forms\Form Forms\Components\Tabs\Tab::make(__('lunarpanel::producttype.tabs.variant_attributes.label')) ->schema([ AttributeSelector::make('mappedAttributes') - ->withType((new ProductVariant)->getMorphClass()) + ->withType(ProductVariant::morphName()) ->relationship(name: 'mappedAttributes') ->label('') ->columnSpan(2), diff --git a/packages/admin/src/Support/Actions/Collections/CreateChildCollection.php b/packages/admin/src/Support/Actions/Collections/CreateChildCollection.php index 10482f94f1..30756048e9 100644 --- a/packages/admin/src/Support/Actions/Collections/CreateChildCollection.php +++ b/packages/admin/src/Support/Actions/Collections/CreateChildCollection.php @@ -26,7 +26,7 @@ public function setUp(): void $this->success(); }); - $attribute = Attribute::where('attribute_type', '=', (new Collection)->getMorphClass()) + $attribute = Attribute::where('attribute_type', '=', Collection::morphName()) ->where('handle', '=', 'name')->first(); $formInput = TextInput::class; diff --git a/packages/admin/src/Support/Actions/Collections/CreateRootCollection.php b/packages/admin/src/Support/Actions/Collections/CreateRootCollection.php index 0f84795d36..092aeca520 100644 --- a/packages/admin/src/Support/Actions/Collections/CreateRootCollection.php +++ b/packages/admin/src/Support/Actions/Collections/CreateRootCollection.php @@ -23,7 +23,7 @@ public function setUp(): void $record = $this->process(function (array $data) { $attribute = Attribute::whereHandle('name')->whereAttributeType( - (new Collection)->getMorphClass() + Collection::morphName() )->first()->type; return Collection::create([ @@ -58,7 +58,7 @@ public function setUp(): void $this->success(); }); - $attribute = Attribute::where('attribute_type', '=', (new Collection)->getMorphClass()) + $attribute = Attribute::where('attribute_type', '=', Collection::morphName()) ->where('handle', '=', 'name')->first(); $formInput = TextInput::class; diff --git a/packages/admin/src/Support/Actions/Traits/CreatesChildCollections.php b/packages/admin/src/Support/Actions/Traits/CreatesChildCollections.php index fc92b7fbff..f7df262119 100644 --- a/packages/admin/src/Support/Actions/Traits/CreatesChildCollections.php +++ b/packages/admin/src/Support/Actions/Traits/CreatesChildCollections.php @@ -13,7 +13,7 @@ public function createChildCollection(Collection $parent, array|string $name) DB::beginTransaction(); $attribute = Attribute::whereHandle('name')->whereAttributeType( - (new Collection)->getMorphClass() + Collection::morphName() )->first()->type; $parent->appendNode(Collection::create([ diff --git a/packages/admin/src/Support/Forms/Components/AttributeSelector.php b/packages/admin/src/Support/Forms/Components/AttributeSelector.php index f4c8eda52c..f6820986b3 100644 --- a/packages/admin/src/Support/Forms/Components/AttributeSelector.php +++ b/packages/admin/src/Support/Forms/Components/AttributeSelector.php @@ -57,8 +57,8 @@ public function getAttributeGroups() $this->getRelationship()->getParent() ); - if ($type === (new ProductType)->getMorphClass()) { - $type = (new Product)->getMorphClass(); + if ($type === ProductType::morphName()) { + $type = Product::morphName(); } if ($this->attributableType) { diff --git a/packages/admin/src/Support/Forms/Components/Attributes.php b/packages/admin/src/Support/Forms/Components/Attributes.php index 4af835e54f..8df4b49223 100644 --- a/packages/admin/src/Support/Forms/Components/Attributes.php +++ b/packages/admin/src/Support/Forms/Components/Attributes.php @@ -26,13 +26,13 @@ protected function setUp(): void $productTypeId = null; - $morphMap = (new $modelClass)->getMorphClass(); + $morphMap = $modelClass::morphName(); $attributeQuery = Attribute::where('attribute_type', $morphMap); // Products are unique in that they use product types to map attributes, so we need // to try and find the product type ID - if ($morphMap == (new Product)->getMorphClass()) { + if ($morphMap == Product::morphName()) { $productTypeId = $record?->product_type_id ?: ProductType::first()->id; // If we have a product type, the attributes should be based off that. @@ -41,7 +41,7 @@ protected function setUp(): void } } - if ($morphMap == (new ProductVariant)->getMorphClass()) { + if ($morphMap == ProductVariant::morphName()) { $productTypeId = $record?->product?->product_type_id ?: ProductType::first()->id; // If we have a product type, the attributes should be based off that. if ($productTypeId) { diff --git a/packages/admin/src/Support/Resources/BaseResource.php b/packages/admin/src/Support/Resources/BaseResource.php index 0eb1960be9..6277e40f6d 100644 --- a/packages/admin/src/Support/Resources/BaseResource.php +++ b/packages/admin/src/Support/Resources/BaseResource.php @@ -125,7 +125,7 @@ protected static function applyGlobalSearchAttributeConstraints(Builder $query, protected static function mapSearchableAttributes(array &$map) { $attributes = Attribute::whereAttributeType( - (new (static::getModel()))->getMorphClass() + static::getModel()::morphName() ) ->whereSearchable(true) ->get(); diff --git a/packages/core/database/factories/CartLineFactory.php b/packages/core/database/factories/CartLineFactory.php index 3dda127e31..2c2118b6ea 100644 --- a/packages/core/database/factories/CartLineFactory.php +++ b/packages/core/database/factories/CartLineFactory.php @@ -15,7 +15,7 @@ public function definition(): array return [ 'cart_id' => Cart::factory(), 'quantity' => $this->faker->numberBetween(0, 1000), - 'purchasable_type' => (new ProductVariant)->getMorphClass(), + 'purchasable_type' => ProductVariant::morphName(), 'purchasable_id' => ProductVariant::factory(), 'meta' => null, ]; diff --git a/packages/core/database/factories/DiscountPurchasableFactory.php b/packages/core/database/factories/DiscountPurchasableFactory.php index a67036c6f2..a39e1d93bf 100644 --- a/packages/core/database/factories/DiscountPurchasableFactory.php +++ b/packages/core/database/factories/DiscountPurchasableFactory.php @@ -13,7 +13,7 @@ public function definition(): array { return [ 'purchasable_id' => ProductVariant::factory(), - 'purchasable_type' => (new ProductVariant)->getMorphClass(), + 'purchasable_type' => ProductVariant::morphName(), ]; } } diff --git a/packages/core/database/factories/OrderLineFactory.php b/packages/core/database/factories/OrderLineFactory.php index 94adb8d4a0..198e4dfac1 100644 --- a/packages/core/database/factories/OrderLineFactory.php +++ b/packages/core/database/factories/OrderLineFactory.php @@ -16,7 +16,7 @@ public function definition(): array { return [ 'order_id' => Order::factory(), - 'purchasable_type' => (new ProductVariant)->getMorphClass(), + 'purchasable_type' => ProductVariant::morphName(), 'purchasable_id' => ProductVariant::factory(), 'type' => 'physical', 'description' => $this->faker->sentence, diff --git a/packages/core/database/factories/UrlFactory.php b/packages/core/database/factories/UrlFactory.php index 285b215fe2..00f8fcd740 100644 --- a/packages/core/database/factories/UrlFactory.php +++ b/packages/core/database/factories/UrlFactory.php @@ -16,7 +16,7 @@ public function definition(): array 'slug' => $this->faker->slug, 'default' => true, 'language_id' => Language::factory(), - 'element_type' => (new Product)->getMorphClass(), + 'element_type' => Product::morphName(), 'element_id' => 1, ]; } diff --git a/packages/core/database/state/ConvertProductTypeAttributesToProducts.php b/packages/core/database/state/ConvertProductTypeAttributesToProducts.php index 863d618c80..dcf9c144e6 100644 --- a/packages/core/database/state/ConvertProductTypeAttributesToProducts.php +++ b/packages/core/database/state/ConvertProductTypeAttributesToProducts.php @@ -23,7 +23,7 @@ public function run() DB::table("{$prefix}attributes") ->whereAttributeType( - (new ProductType)->getMorphClass() + ProductType::morphName() ) ->update([ 'attribute_type' => 'product', @@ -31,7 +31,7 @@ public function run() DB::table("{$prefix}attribute_groups") ->whereAttributableType( - (new ProductType)->getMorphClass() + ProductType::morphName() ) ->update([ 'attributable_type' => 'product', diff --git a/packages/core/src/Base/Traits/HasAttributes.php b/packages/core/src/Base/Traits/HasAttributes.php index da028fc439..2d3f7bbee0 100644 --- a/packages/core/src/Base/Traits/HasAttributes.php +++ b/packages/core/src/Base/Traits/HasAttributes.php @@ -19,7 +19,7 @@ public function getAttributableClassnameAttribute() public function getAttributableMorphMapAttribute() { - return (new self)->getMorphClass(); + return self::morphName(); } /** diff --git a/packages/core/src/Base/Traits/HasModelExtending.php b/packages/core/src/Base/Traits/HasModelExtending.php index c0a0412d94..7cafe6c00d 100644 --- a/packages/core/src/Base/Traits/HasModelExtending.php +++ b/packages/core/src/Base/Traits/HasModelExtending.php @@ -47,6 +47,13 @@ public static function modelClass(): string return ModelManifest::get($contractClass) ?? static::class; } + /** + * Returns the model alias registered in the model relation morph map. + */ + public static function morphName():string{ + return (new (static::modelClass()))->getMorphClass(); + } + public function getMorphClass(): string { $morphMap = Relation::morphMap(); diff --git a/packages/core/src/Console/InstallLunar.php b/packages/core/src/Console/InstallLunar.php index 25d62e0a71..ed9f889f74 100644 --- a/packages/core/src/Console/InstallLunar.php +++ b/packages/core/src/Console/InstallLunar.php @@ -140,7 +140,7 @@ public function handle(): void $this->components->info('Setting up initial attributes'); $group = AttributeGroup::create([ - 'attributable_type' => (new Product)->getMorphClass(), + 'attributable_type' => Product::morphName(), 'name' => collect([ 'en' => 'Details', ]), @@ -149,7 +149,7 @@ public function handle(): void ]); $collectionGroup = AttributeGroup::create([ - 'attributable_type' => (new Collection)->getMorphClass(), + 'attributable_type' => Collection::morphName(), 'name' => collect([ 'en' => 'Details', ]), @@ -251,7 +251,7 @@ public function handle(): void $type->mappedAttributes()->attach( Attribute::whereAttributeType( - (new Product)->getMorphClass() + Product::morphName() )->get()->pluck('id') ); } diff --git a/packages/core/src/DiscountTypes/BuyXGetY.php b/packages/core/src/DiscountTypes/BuyXGetY.php index cf938d282d..6329049ed0 100644 --- a/packages/core/src/DiscountTypes/BuyXGetY.php +++ b/packages/core/src/DiscountTypes/BuyXGetY.php @@ -58,7 +58,7 @@ public function apply(Cart $cart): Cart // Get all purchasables that are eligible. $conditions = $cart->lines->reject(function ($line) { return ! $this->discount->purchasableConditions->first(function ($item) use ($line) { - return $item->purchasable_type == (new Product)->getMorphClass() && + return $item->purchasable_type == Product::morphName() && $item->purchasable_id == $line->purchasable->product->id; }); }); @@ -89,7 +89,7 @@ public function apply(Cart $cart): Cart // Get the reward lines and sort by cheapest first. $rewardLines = $cart->lines->filter(function ($line) { return $this->discount->purchasableRewards->first(function ($item) use ($line) { - return $item->purchasable_type == (new Product)->getMorphClass() && + return $item->purchasable_type == Product::morphName() && $item->purchasable_id == $line->purchasable->product->id; }); })->sortBy('subTotal.value'); diff --git a/packages/core/src/Models/Discount.php b/packages/core/src/Models/Discount.php index d580ff56fe..4bcf8f7628 100644 --- a/packages/core/src/Models/Discount.php +++ b/packages/core/src/Models/Discount.php @@ -179,7 +179,7 @@ public function scopeProducts(Builder $query, iterable $productIds = [], array|s fn ($subQuery) => $subQuery->whereDoesntHave('purchasables', fn ($query) => $query->when($types, fn ($query) => $query->whereIn('type', $types))) ->orWhereHas('purchasables', fn ($relation) => $relation->whereIn('purchasable_id', $productIds) - ->wherePurchasableType((new Product)->getMorphClass()) + ->wherePurchasableType(Product::morphName()) ->when( $types, fn ($query) => $query->whereIn('type', $types) @@ -200,7 +200,7 @@ public function scopeProductVariants(Builder $query, iterable $variantIds = [], fn ($subQuery) => $subQuery->whereDoesntHave('purchasables', fn ($query) => $query->when($types, fn ($query) => $query->whereIn('type', $types))) ->orWhereHas('purchasables', fn ($relation) => $relation->whereIn('purchasable_id', $variantIds) - ->wherePurchasableType((new ProductVariant)->getMorphClass()) + ->wherePurchasableType(ProductVariant::morphName()) ->when( $types, fn ($query) => $query->whereIn('type', $types) diff --git a/packages/core/src/Models/ProductType.php b/packages/core/src/Models/ProductType.php index f3526e1fe0..5ae661cbd4 100644 --- a/packages/core/src/Models/ProductType.php +++ b/packages/core/src/Models/ProductType.php @@ -52,14 +52,14 @@ public function mappedAttributes(): MorphToMany public function productAttributes(): MorphToMany { return $this->mappedAttributes()->whereAttributeType( - (new Product)->getMorphClass() + Product::morphName() ); } public function variantAttributes(): MorphToMany { return $this->mappedAttributes()->whereAttributeType( - (new ProductVariant)->getMorphClass() + ProductVariant::morphName() ); } diff --git a/packages/table-rate-shipping/database/factories/ShippingExclusionFactory.php b/packages/table-rate-shipping/database/factories/ShippingExclusionFactory.php index ddd98ea247..69a84a0d10 100644 --- a/packages/table-rate-shipping/database/factories/ShippingExclusionFactory.php +++ b/packages/table-rate-shipping/database/factories/ShippingExclusionFactory.php @@ -14,7 +14,7 @@ public function definition(): array { return [ 'purchasable_id' => 1, - 'purchasable_type' => (new Product)->getMorphClass(), + 'purchasable_type' => Product::morphName(), ]; } } diff --git a/packages/table-rate-shipping/src/Drivers/ShippingMethods/Collection.php b/packages/table-rate-shipping/src/Drivers/ShippingMethods/Collection.php index 3ff6d86c7f..ed16b140d1 100644 --- a/packages/table-rate-shipping/src/Drivers/ShippingMethods/Collection.php +++ b/packages/table-rate-shipping/src/Drivers/ShippingMethods/Collection.php @@ -45,7 +45,7 @@ public function resolve(ShippingOptionRequest $shippingOptionRequest): ?Shipping $hasExclusions = $shippingZone->shippingExclusions() ->whereHas('exclusions', function ($query) use ($productIds) { - $query->wherePurchasableType((new Product)->getMorphClass()) + $query->wherePurchasableType(Product::morphName()) ->whereIn('purchasable_id', $productIds); })->exists(); diff --git a/packages/table-rate-shipping/src/Drivers/ShippingMethods/FlatRate.php b/packages/table-rate-shipping/src/Drivers/ShippingMethods/FlatRate.php index d00886319c..c25c445f56 100644 --- a/packages/table-rate-shipping/src/Drivers/ShippingMethods/FlatRate.php +++ b/packages/table-rate-shipping/src/Drivers/ShippingMethods/FlatRate.php @@ -45,7 +45,7 @@ public function resolve(ShippingOptionRequest $shippingOptionRequest): ?Shipping $hasExclusions = $shippingZone->shippingExclusions() ->whereHas('exclusions', function ($query) use ($productIds) { - $query->wherePurchasableType((new Product)->getMorphClass()) + $query->wherePurchasableType(Product::morphName()) ->whereIn('purchasable_id', $productIds); })->exists(); diff --git a/packages/table-rate-shipping/src/Drivers/ShippingMethods/FreeShipping.php b/packages/table-rate-shipping/src/Drivers/ShippingMethods/FreeShipping.php index 449c5dd36f..a6ef71e912 100644 --- a/packages/table-rate-shipping/src/Drivers/ShippingMethods/FreeShipping.php +++ b/packages/table-rate-shipping/src/Drivers/ShippingMethods/FreeShipping.php @@ -46,7 +46,7 @@ public function resolve(ShippingOptionRequest $shippingOptionRequest): ?Shipping $hasExclusions = $shippingZone->shippingExclusions() ->whereHas('exclusions', function ($query) use ($productIds) { - $query->wherePurchasableType((new Product)->getMorphClass()) + $query->wherePurchasableType(Product::morphName()) ->whereIn('purchasable_id', $productIds); })->exists(); diff --git a/packages/table-rate-shipping/src/Drivers/ShippingMethods/ShipBy.php b/packages/table-rate-shipping/src/Drivers/ShippingMethods/ShipBy.php index e11fea9875..cabcb98acc 100644 --- a/packages/table-rate-shipping/src/Drivers/ShippingMethods/ShipBy.php +++ b/packages/table-rate-shipping/src/Drivers/ShippingMethods/ShipBy.php @@ -53,7 +53,7 @@ public function resolve(ShippingOptionRequest $shippingOptionRequest): ?Shipping $hasExclusions = $shippingZone->shippingExclusions() ->whereHas('exclusions', function ($query) use ($productIds) { - $query->wherePurchasableType((new Product)->getMorphClass()) + $query->wherePurchasableType(Product::morphName()) ->whereIn('purchasable_id', $productIds); })->exists(); diff --git a/tests/admin/Feature/Filament/Resources/ProductTypeResource/Pages/CreateProductTypeTest.php b/tests/admin/Feature/Filament/Resources/ProductTypeResource/Pages/CreateProductTypeTest.php index ef494e59b7..903c514f2b 100644 --- a/tests/admin/Feature/Filament/Resources/ProductTypeResource/Pages/CreateProductTypeTest.php +++ b/tests/admin/Feature/Filament/Resources/ProductTypeResource/Pages/CreateProductTypeTest.php @@ -54,7 +54,7 @@ ->assertHasNoFormErrors(); $this->assertDatabaseHas((new ProductType)->mappedAttributes()->getTable(), [ - 'attributable_type' => (new ProductType)->getMorphClass(), + 'attributable_type' => ProductType::morphName(), 'attributable_id' => $component->get('record')->id, ]); }); diff --git a/tests/admin/Feature/Filament/Resources/ProductTypeResource/Pages/EditProductTypeTest.php b/tests/admin/Feature/Filament/Resources/ProductTypeResource/Pages/EditProductTypeTest.php index 44b04035fe..94b006a031 100644 --- a/tests/admin/Feature/Filament/Resources/ProductTypeResource/Pages/EditProductTypeTest.php +++ b/tests/admin/Feature/Filament/Resources/ProductTypeResource/Pages/EditProductTypeTest.php @@ -26,13 +26,13 @@ ->assertHasNoFormErrors(); $this->assertDatabaseHas((new ProductType)->mappedAttributes()->getTable(), [ - 'attributable_type' => (new ProductType)->getMorphClass(), + 'attributable_type' => ProductType::morphName(), 'attributable_id' => $component->get('record')->id, 'attribute_id' => $attributeA->id, ]); $this->assertDatabaseHas((new ProductType)->mappedAttributes()->getTable(), [ - 'attributable_type' => (new ProductType)->getMorphClass(), + 'attributable_type' => ProductType::morphName(), 'attributable_id' => $component->get('record')->id, 'attribute_id' => $attributeB->id, ]); @@ -46,13 +46,13 @@ ->assertHasNoFormErrors(); $this->assertDatabaseHas((new ProductType)->mappedAttributes()->getTable(), [ - 'attributable_type' => (new ProductType)->getMorphClass(), + 'attributable_type' => ProductType::morphName(), 'attributable_id' => $component->get('record')->id, 'attribute_id' => $attributeA->id, ]); $this->assertDatabaseMissing((new ProductType)->mappedAttributes()->getTable(), [ - 'attributable_type' => (new ProductType)->getMorphClass(), + 'attributable_type' => ProductType::morphName(), 'attributable_id' => $component->get('record')->id, 'attribute_id' => $attributeB->id, ]); diff --git a/tests/core/Unit/Base/Traits/HasModelExtendingTest.php b/tests/core/Unit/Base/Traits/HasModelExtendingTest.php index 413a276eb9..da758ad99d 100644 --- a/tests/core/Unit/Base/Traits/HasModelExtendingTest.php +++ b/tests/core/Unit/Base/Traits/HasModelExtendingTest.php @@ -53,7 +53,11 @@ function () { ); expect((new \Lunar\Tests\Core\Stubs\Models\CustomProduct)->getMorphClass()) + ->toBe('product') + ->and(\Lunar\Tests\Core\Stubs\Models\CustomProduct::morphName()) ->toBe('product') ->and((new Product)->getMorphClass()) + ->toBe('product') + ->and(Product::morphName()) ->toBe('product'); }); From 6b0e787e6c2fb02684cb68eb3e775d827f5f3c80 Mon Sep 17 00:00:00 2001 From: alecritson Date: Tue, 17 Sep 2024 08:33:26 +0000 Subject: [PATCH 3/5] chore: fix code style --- packages/core/src/Base/Traits/HasModelExtending.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/core/src/Base/Traits/HasModelExtending.php b/packages/core/src/Base/Traits/HasModelExtending.php index 7cafe6c00d..340c769e02 100644 --- a/packages/core/src/Base/Traits/HasModelExtending.php +++ b/packages/core/src/Base/Traits/HasModelExtending.php @@ -50,10 +50,11 @@ public static function modelClass(): string /** * Returns the model alias registered in the model relation morph map. */ - public static function morphName():string{ + public static function morphName(): string + { return (new (static::modelClass()))->getMorphClass(); } - + public function getMorphClass(): string { $morphMap = Relation::morphMap(); From f3db11375c7ee03ca94b273543d9ade3e6f7efbc Mon Sep 17 00:00:00 2001 From: Alec Ritson Date: Tue, 17 Sep 2024 09:41:29 +0100 Subject: [PATCH 4/5] Fix extended model resolving (#1949) This PR looks to solve some anomalies that have been discovered with model extending. ## Problem A When replacing a Lunar model, there are instances where the model provided by Lunar is still used for example, if you replace the model like so ```php \Lunar\Facades\ModelManifest::replace( \Lunar\Models\Contracts\Order::class, \App\Models\Order::class ); ``` Later in the Lunar core, we are still referencing `Lunar\Models\Order::first()` or via relationships `$orderLine->order()`. We attempted to resolve these queries to their concrete replacements by overriding the `newModelQuery` method, however there were issues with castable attributes which caused them to be cast multiple times, resulting in errors. ### Solution This has been rewritten so Lunar doesn't try to `fill` the extended instance of the model from the Lunar base class anymore and instead just populates the attributes as they are. Since this should just be a simple class swap it should no longer result in duplicate calls to attribute casters. There are some additional checks to see if we are actually working with a model that hasn't been extended to ensure we are not getting into a situation where we try and rehydrate with the same class. ### Affected issues This change should resolve #1942 #1930 ## Problem B If your own custom model was named something other than it's counterpart, for example: ```php \Lunar\Facades\ModelManifest::replace( \Lunar\Models\Contracts\Order::class, \App\Models\MyCustomOrder::class ); ``` This would result in the table name and subsequent foreign key naming to be incorrect i.e. `my_custom_order` and `my_custom_order_id`. This would mean developers would need to add their own methods to override this in order for the naming to resolve properly, which is a bit of a maintenance burden and easily missed when encountering errors. ### Solution The `HasModelExtending` trait now provides its own `getTable` and `getForeignKey` methods which will check which class within Lunar we are extending and return the appropriate table name and foreign key. ## How this slipped through testing Looks like there was an oversight and although there were tests for extending models, the tests themselves didn't use any extending when performing more complex tasks, like creating orders from carts. This PR has now added some model extending to tests which were affected by the issues referenced above to hopefully keep this in check and make for a more "real world" test environment. --------- Co-authored-by: alecritson --- .../src/Base/Traits/HasModelExtending.php | 52 +++++++++++++++++-- tests/core/Stubs/Models/CustomOrder.php | 5 ++ tests/core/Stubs/Models/Order.php | 5 ++ .../Unit/Actions/Carts/CreateOrderTest.php | 10 +++- .../Base/Traits/HasModelExtendingTest.php | 7 +++ 5 files changed, 74 insertions(+), 5 deletions(-) create mode 100644 tests/core/Stubs/Models/CustomOrder.php create mode 100644 tests/core/Stubs/Models/Order.php diff --git a/packages/core/src/Base/Traits/HasModelExtending.php b/packages/core/src/Base/Traits/HasModelExtending.php index 340c769e02..a8e0f2a689 100644 --- a/packages/core/src/Base/Traits/HasModelExtending.php +++ b/packages/core/src/Base/Traits/HasModelExtending.php @@ -3,25 +3,71 @@ namespace Lunar\Base\Traits; use Illuminate\Database\Eloquent\Builder; +use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\Relation; use Illuminate\Support\Arr; +use Illuminate\Support\Str; +use Lunar\Base\BaseModel; use Lunar\Facades\ModelManifest; trait HasModelExtending { public function newModelQuery(): Builder { - $realClass = static::modelClass(); + $concreteClass = static::modelClass(); + $parentClass = get_parent_class($concreteClass); // If they are both the same class i.e. they haven't changed // then just call the parent method. - if ($this instanceof $realClass) { + if ($parentClass == BaseModel::class || $this instanceof $concreteClass) { return parent::newModelQuery(); } return $this->newEloquentBuilder( $this->newBaseQueryBuilder() - )->setModel(new $realClass($this->toArray())); + )->setModel( + static::withoutEvents( + fn () => $this->replicateInto($concreteClass) + ) + ); + } + + public function replicateInto($newClass) + { + $defaults = array_values(array_filter([ + $this->getKeyName(), + $this->getCreatedAtColumn(), + $this->getUpdatedAtColumn(), + ...$this->uniqueIds(), + 'laravel_through_key', + ])); + + $attributes = Arr::except( + $this->getAttributes(), $defaults + ); + + return tap(new $newClass, function ($instance) use ($attributes): Model { + $instance->setRawAttributes($attributes); + + $instance->setRelations($this->relations); + + return $instance; + }); + } + + public function getForeignKey(): string + { + $parentClass = get_parent_class($this); + + return $parentClass == BaseModel::class ? parent::getForeignKey() : Str::snake(class_basename($parentClass)).'_'.$this->getKeyName(); + + } + + public function getTable() + { + $parentClass = get_parent_class($this); + + return $parentClass == BaseModel::class ? parent::getTable() : (new $parentClass)->table; } public static function __callStatic($method, $parameters) diff --git a/tests/core/Stubs/Models/CustomOrder.php b/tests/core/Stubs/Models/CustomOrder.php new file mode 100644 index 0000000000..7cf7db04ce --- /dev/null +++ b/tests/core/Stubs/Models/CustomOrder.php @@ -0,0 +1,5 @@ +create([ 'default' => true, ]); @@ -223,8 +229,8 @@ function can_update_draft_order() ->and($order->shippingAddress)->toBeInstanceOf(OrderAddress::class) ->and($order->billingAddress)->toBeInstanceOf(OrderAddress::class); - $this->assertDatabaseHas((new Order)->getTable(), $datacheck); - $this->assertDatabaseHas((new OrderLine)->getTable(), [ + assertDatabaseHas((new Order)->getTable(), $datacheck); + assertDatabaseHas((new OrderLine)->getTable(), [ 'identifier' => $shippingOption->getIdentifier(), ]); diff --git a/tests/core/Unit/Base/Traits/HasModelExtendingTest.php b/tests/core/Unit/Base/Traits/HasModelExtendingTest.php index da758ad99d..24f55d3e09 100644 --- a/tests/core/Unit/Base/Traits/HasModelExtendingTest.php +++ b/tests/core/Unit/Base/Traits/HasModelExtendingTest.php @@ -38,6 +38,13 @@ function () { expect($sizeOption->sizes)->toHaveCount(1); }); +test('extended model returns correct table name', function () { + expect((new \Lunar\Tests\Core\Stubs\Models\CustomOrder)->getTable()) + ->toBe( + (new \Lunar\Models\Order)->getTable() + ); +}); + test('can forward static method calls to extended model', function () { /** @see \Lunar\Tests\Core\Stubs\Models\ProductOption::getSizesStatic() */ $newStaticMethod = ProductOption::getSizesStatic(); From c4381dc732aaa3e43b6032afa1871438b74e1956 Mon Sep 17 00:00:00 2001 From: Alec Ritson Date: Tue, 17 Sep 2024 09:41:49 +0100 Subject: [PATCH 5/5] Manually map models on migration instead of loading via directory (#1928) Currently if you publish the migrations they can fail due to trying to read a directory relative to the the vendor folder. This PR instead opts to manually map the models to prevent this issue completely. --------- Co-authored-by: alecritson Co-authored-by: Glenn Jacobs --- ..._15_100000_remap_polymorphic_relations.php | 52 ++++++++++++++++--- 1 file changed, 44 insertions(+), 8 deletions(-) diff --git a/packages/core/database/migrations/2024_03_15_100000_remap_polymorphic_relations.php b/packages/core/database/migrations/2024_03_15_100000_remap_polymorphic_relations.php index 8e509cee2d..748ef41d27 100644 --- a/packages/core/database/migrations/2024_03_15_100000_remap_polymorphic_relations.php +++ b/packages/core/database/migrations/2024_03_15_100000_remap_polymorphic_relations.php @@ -1,20 +1,56 @@ classes() - ->extending(BaseModel::class) - ->get() - )->mapWithKeys( + $modelClasses = collect([ + \Lunar\Models\CartLine::class, + \Lunar\Models\ProductOption::class, + \Lunar\Models\Asset::class, + \Lunar\Models\Brand::class, + \Lunar\Models\TaxZone::class, + \Lunar\Models\TaxZoneCountry::class, + \Lunar\Models\TaxZoneCustomerGroup::class, + \Lunar\Models\DiscountCollection::class, + \Lunar\Models\TaxClass::class, + \Lunar\Models\ProductOptionValue::class, + \Lunar\Models\Channel::class, + \Lunar\Models\AttributeGroup::class, + \Lunar\Models\Tag::class, + \Lunar\Models\Cart::class, + \Lunar\Models\Collection::class, + \Lunar\Models\Discount::class, + \Lunar\Models\TaxRate::class, + \Lunar\Models\Price::class, + \Lunar\Models\DiscountPurchasable::class, + \Lunar\Models\State::class, + \Lunar\Models\UserPermission::class, + \Lunar\Models\OrderAddress::class, + \Lunar\Models\Country::class, + \Lunar\Models\Address::class, + \Lunar\Models\Url::class, + \Lunar\Models\ProductVariant::class, + \Lunar\Models\TaxZonePostcode::class, + \Lunar\Models\ProductAssociation::class, + \Lunar\Models\TaxRateAmount::class, + \Lunar\Models\Attribute::class, + \Lunar\Models\Order::class, + \Lunar\Models\Customer::class, + \Lunar\Models\OrderLine::class, + \Lunar\Models\CartAddress::class, + \Lunar\Models\Language::class, + \Lunar\Models\TaxZoneState::class, + \Lunar\Models\Currency::class, + \Lunar\Models\Product::class, + \Lunar\Models\Transaction::class, + \Lunar\Models\ProductType::class, + \Lunar\Models\CollectionGroup::class, + \Lunar\Models\CustomerGroup::class, + ])->mapWithKeys( fn ($class) => [ $class => \Lunar\Facades\ModelManifest::getMorphMapKey($class), ]