diff --git a/packages/forms/src/Components/CheckboxList.php b/packages/forms/src/Components/CheckboxList.php index 9a801363aaf..f86cf5415e9 100644 --- a/packages/forms/src/Components/CheckboxList.php +++ b/packages/forms/src/Components/CheckboxList.php @@ -3,6 +3,7 @@ namespace Filament\Forms\Components; use Closure; +use Exception; use Filament\Actions\Action; use Filament\Schemas\Components\StateCasts\Contracts\StateCast; use Filament\Schemas\Components\StateCasts\EnumArrayStateCast; @@ -296,7 +297,13 @@ public function getRelationship(): ?BelongsToMany return null; } - return $this->getModelInstance()->{$name}(); + $record = $this->getModelInstance(); + + if (! $record->isRelation($name)) { + throw new Exception("The relationship [{$name}] does not exist on the model [{$this->getModel()}]."); + } + + return $record->{$name}(); } public function getRelationshipName(): ?string diff --git a/packages/forms/src/Components/MorphToSelect.php b/packages/forms/src/Components/MorphToSelect.php index 8536d8e0c99..be192a0898a 100644 --- a/packages/forms/src/Components/MorphToSelect.php +++ b/packages/forms/src/Components/MorphToSelect.php @@ -145,7 +145,15 @@ public function types(array | Closure $types): static public function getRelationship(): MorphTo { - return $this->getModelInstance()->{$this->getName()}(); + $record = $this->getModelInstance(); + + $relationshipName = $this->getName(); + + if (! $record->isRelation($relationshipName)) { + throw new Exception("The relationship [{$relationshipName}] does not exist on the model [{$this->getModel()}]."); + } + + return $record->{$relationshipName}(); } /** diff --git a/packages/forms/src/Components/Repeater.php b/packages/forms/src/Components/Repeater.php index aa7b66dfb24..79834c3cb11 100644 --- a/packages/forms/src/Components/Repeater.php +++ b/packages/forms/src/Components/Repeater.php @@ -3,9 +3,11 @@ namespace Filament\Forms\Components; use Closure; +use Exception; use Filament\Actions\Action; use Filament\Forms\Contracts\HasForms; use Filament\Schemas\Components\Concerns\CanBeCollapsed; +use Filament\Schemas\Components\Concerns\HasContainerGridLayout; use Filament\Schemas\Components\Contracts\CanConcealComponents; use Filament\Schemas\Components\Contracts\HasExtraItemActions; use Filament\Schemas\Schema; @@ -30,7 +32,7 @@ class Repeater extends Field implements CanConcealComponents, HasExtraItemAction use Concerns\CanGenerateUuids; use Concerns\CanLimitItemsLength; use Concerns\HasExtraItemActions; - use \Filament\Schemas\Components\Concerns\HasContainerGridLayout; + use HasContainerGridLayout; use HasReorderAnimationDuration; protected string | Closure | null $addActionLabel = null; @@ -1086,7 +1088,15 @@ public function getRelationship(): HasOneOrMany | BelongsToMany | null return null; } - return $this->getModelInstance()->{$this->getRelationshipName()}(); + $record = $this->getModelInstance(); + + $relationshipName = $this->getRelationshipName(); + + if (! $record->isRelation($relationshipName)) { + throw new Exception("The relationship [{$relationshipName}] does not exist on the model [{$this->getModel()}]."); + } + + return $this->getModelInstance()->{$relationshipName}(); } public function getRelationshipName(): ?string diff --git a/packages/forms/src/Components/Select.php b/packages/forms/src/Components/Select.php index 2553c627ebb..f5cd0ca8548 100644 --- a/packages/forms/src/Components/Select.php +++ b/packages/forms/src/Components/Select.php @@ -1159,7 +1159,7 @@ public function getLabel(): string | Htmlable | null public function getRelationship(): BelongsTo | BelongsToMany | HasOneOrMany | HasManyThrough | BelongsToThrough | null { - if (blank($this->getRelationshipName())) { + if (! $this->hasRelationship()) { return null; } @@ -1167,7 +1167,9 @@ public function getRelationship(): BelongsTo | BelongsToMany | HasOneOrMany | Ha $relationship = null; - foreach (explode('.', $this->getRelationshipName()) as $nestedRelationshipName) { + $relationshipName = $this->getRelationshipName(); + + foreach (explode('.', $relationshipName) as $nestedRelationshipName) { if (! $record->isRelation($nestedRelationshipName)) { $relationship = null; @@ -1178,6 +1180,10 @@ public function getRelationship(): BelongsTo | BelongsToMany | HasOneOrMany | Ha $record = $relationship->getRelated(); } + if (! $relationship) { + throw new Exception("The relationship [{$relationshipName}] does not exist on the model [{$this->getModel()}]."); + } + return $relationship; } diff --git a/packages/tables/src/Columns/Column.php b/packages/tables/src/Columns/Column.php index fecda6cea9e..9c92a4ff89a 100644 --- a/packages/tables/src/Columns/Column.php +++ b/packages/tables/src/Columns/Column.php @@ -7,6 +7,7 @@ use Filament\Support\Components\ViewComponent; use Filament\Support\Concerns\CanAggregateRelatedModels; use Filament\Support\Concerns\CanGrow; +use Filament\Support\Concerns\CanSpanColumns; use Filament\Support\Concerns\HasAlignment; use Filament\Support\Concerns\HasCellState; use Filament\Support\Concerns\HasExtraAttributes; @@ -25,6 +26,7 @@ class Column extends ViewComponent { use CanAggregateRelatedModels; use CanGrow; + use CanSpanColumns; use Concerns\BelongsToGroup; use Concerns\BelongsToLayout; use Concerns\BelongsToTable; @@ -46,7 +48,6 @@ class Column extends ViewComponent use Concerns\HasRowLoopObject; use Concerns\HasWidth; use Concerns\InteractsWithTableQuery; - use \Filament\Support\Concerns\CanSpanColumns; use HasAlignment; use HasCellState; use HasExtraAttributes; diff --git a/packages/tables/src/Filters/Concerns/HasRelationship.php b/packages/tables/src/Filters/Concerns/HasRelationship.php index b37d534ecec..96e9bfc7f8d 100644 --- a/packages/tables/src/Filters/Concerns/HasRelationship.php +++ b/packages/tables/src/Filters/Concerns/HasRelationship.php @@ -3,6 +3,7 @@ namespace Filament\Tables\Filters\Concerns; use Closure; +use Exception; use Filament\Support\Services\RelationshipJoiner; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Relations\BelongsTo; @@ -49,11 +50,15 @@ public function queriesRelationships(): bool public function getRelationship(): Relation | Builder { - $record = app($this->getTable()->getModel()); + $model = $this->getTable()->getModel(); + + $record = app($model); $relationship = null; - foreach (explode('.', $this->getRelationshipName()) as $nestedRelationshipName) { + $relationshipName = $this->getRelationshipName(); + + foreach (explode('.', $relationshipName) as $nestedRelationshipName) { if (! $record->isRelation($nestedRelationshipName)) { $relationship = null; @@ -64,6 +69,10 @@ public function getRelationship(): Relation | Builder $record = $relationship->getRelated(); } + if (! $relationship) { + throw new Exception("The relationship [{$relationshipName}] does not exist on the model [{$model}]."); + } + return $relationship; } diff --git a/tests/src/Fixtures/Resources/PostCategories/PostCategoryResource.php b/tests/src/Fixtures/Resources/PostCategories/PostCategoryResource.php index 88d39bec24f..150b4eeeaa1 100644 --- a/tests/src/Fixtures/Resources/PostCategories/PostCategoryResource.php +++ b/tests/src/Fixtures/Resources/PostCategories/PostCategoryResource.php @@ -4,6 +4,10 @@ use Filament\Resources\Resource; use Filament\Tests\Fixtures\Models\PostCategory; +use Filament\Tests\Fixtures\Resources\PostCategories\Pages\CreatePostCategory; +use Filament\Tests\Fixtures\Resources\PostCategories\Pages\EditPostCategory; +use Filament\Tests\Fixtures\Resources\PostCategories\Pages\ListPostCategories; +use Filament\Tests\Fixtures\Resources\PostCategories\Pages\ViewPostCategory; class PostCategoryResource extends Resource { @@ -16,10 +20,10 @@ class PostCategoryResource extends Resource public static function getPages(): array { return [ - 'index' => \Filament\Tests\Fixtures\Resources\PostCategories\Pages\ListPostCategories::route('/'), - 'create' => \Filament\Tests\Fixtures\Resources\PostCategories\Pages\CreatePostCategory::route('/create'), - 'view' => \Filament\Tests\Fixtures\Resources\PostCategories\Pages\ViewPostCategory::route('/{record}'), - 'edit' => \Filament\Tests\Fixtures\Resources\PostCategories\Pages\EditPostCategory::route('/{record}/edit'), + 'index' => ListPostCategories::route('/'), + 'create' => CreatePostCategory::route('/create'), + 'view' => ViewPostCategory::route('/{record}'), + 'edit' => EditPostCategory::route('/{record}/edit'), ]; } } diff --git a/tests/src/Fixtures/Resources/Posts/PostResource.php b/tests/src/Fixtures/Resources/Posts/PostResource.php index 3eefae937cf..0205179d3ca 100644 --- a/tests/src/Fixtures/Resources/Posts/PostResource.php +++ b/tests/src/Fixtures/Resources/Posts/PostResource.php @@ -12,6 +12,10 @@ use Filament\Tables; use Filament\Tables\Table; use Filament\Tests\Fixtures\Models\Post; +use Filament\Tests\Fixtures\Resources\Posts\Pages\CreatePost; +use Filament\Tests\Fixtures\Resources\Posts\Pages\EditPost; +use Filament\Tests\Fixtures\Resources\Posts\Pages\ListPosts; +use Filament\Tests\Fixtures\Resources\Posts\Pages\ViewPost; use Illuminate\Database\Eloquent\Builder; class PostResource extends Resource @@ -70,10 +74,10 @@ public static function table(Table $table): Table public static function getPages(): array { return [ - 'index' => \Filament\Tests\Fixtures\Resources\Posts\Pages\ListPosts::route('/'), - 'create' => \Filament\Tests\Fixtures\Resources\Posts\Pages\CreatePost::route('/create'), - 'view' => \Filament\Tests\Fixtures\Resources\Posts\Pages\ViewPost::route('/{record}'), - 'edit' => \Filament\Tests\Fixtures\Resources\Posts\Pages\EditPost::route('/{record}/edit'), + 'index' => ListPosts::route('/'), + 'create' => CreatePost::route('/create'), + 'view' => ViewPost::route('/{record}'), + 'edit' => EditPost::route('/{record}/edit'), ]; } } diff --git a/tests/src/Fixtures/Resources/Shop/Products/ProductResource.php b/tests/src/Fixtures/Resources/Shop/Products/ProductResource.php index bd32984d4a2..2b27809ad38 100644 --- a/tests/src/Fixtures/Resources/Shop/Products/ProductResource.php +++ b/tests/src/Fixtures/Resources/Shop/Products/ProductResource.php @@ -4,6 +4,10 @@ use Filament\Resources\Resource; use Filament\Tests\Fixtures\Models\Product; +use Filament\Tests\Fixtures\Resources\Shop\Products\Pages\CreateProduct; +use Filament\Tests\Fixtures\Resources\Shop\Products\Pages\EditProduct; +use Filament\Tests\Fixtures\Resources\Shop\Products\Pages\ListProducts; +use Filament\Tests\Fixtures\Resources\Shop\Products\Pages\ViewProduct; class ProductResource extends Resource { @@ -16,10 +20,10 @@ class ProductResource extends Resource public static function getPages(): array { return [ - 'index' => \Filament\Tests\Fixtures\Resources\Shop\Products\Pages\ListProducts::route('/'), - 'create' => \Filament\Tests\Fixtures\Resources\Shop\Products\Pages\CreateProduct::route('/create'), - 'view' => \Filament\Tests\Fixtures\Resources\Shop\Products\Pages\ViewProduct::route('/{record}'), - 'edit' => \Filament\Tests\Fixtures\Resources\Shop\Products\Pages\EditProduct::route('/{record}/edit'), + 'index' => ListProducts::route('/'), + 'create' => CreateProduct::route('/create'), + 'view' => ViewProduct::route('/{record}'), + 'edit' => EditProduct::route('/{record}/edit'), ]; } } diff --git a/tests/src/Fixtures/Resources/Users/UserResource.php b/tests/src/Fixtures/Resources/Users/UserResource.php index 934ad88cc59..820b555f938 100644 --- a/tests/src/Fixtures/Resources/Users/UserResource.php +++ b/tests/src/Fixtures/Resources/Users/UserResource.php @@ -11,6 +11,10 @@ use Filament\Tables; use Filament\Tables\Table; use Filament\Tests\Fixtures\Models\User; +use Filament\Tests\Fixtures\Resources\Users\Pages\CreateUser; +use Filament\Tests\Fixtures\Resources\Users\Pages\EditUser; +use Filament\Tests\Fixtures\Resources\Users\Pages\ListUsers; +use Filament\Tests\Fixtures\Resources\Users\Pages\ViewUser; class UserResource extends Resource { @@ -68,10 +72,10 @@ public static function table(Table $table): Table public static function getPages(): array { return [ - 'index' => \Filament\Tests\Fixtures\Resources\Users\Pages\ListUsers::route('/'), - 'create' => \Filament\Tests\Fixtures\Resources\Users\Pages\CreateUser::route('/create'), - 'view' => \Filament\Tests\Fixtures\Resources\Users\Pages\ViewUser::route('/{record}'), - 'edit' => \Filament\Tests\Fixtures\Resources\Users\Pages\EditUser::route('/{record}/edit'), + 'index' => ListUsers::route('/'), + 'create' => CreateUser::route('/create'), + 'view' => ViewUser::route('/{record}'), + 'edit' => EditUser::route('/{record}/edit'), ]; } } diff --git a/tests/src/Forms/Components/RepeaterTest.php b/tests/src/Forms/Components/RepeaterTest.php index 2abb4dc7adc..f6cc05fb2f0 100644 --- a/tests/src/Forms/Components/RepeaterTest.php +++ b/tests/src/Forms/Components/RepeaterTest.php @@ -4,6 +4,8 @@ use Filament\Forms\Components\TextInput; use Filament\Schemas\Schema; use Filament\Tests\Fixtures\Livewire\Livewire; +use Filament\Tests\Fixtures\Models\Post; +use Filament\Tests\Fixtures\Models\User; use Filament\Tests\TestCase; use Illuminate\Support\Arr; use Illuminate\Support\Str; @@ -144,6 +146,40 @@ $undoRepeaterFake(); }); +it('loads a relationship', function () { + $user = User::factory() + ->has(Post::factory()->count(3)) + ->create(); + + $componentContainer = Schema::make(Livewire::make()) + ->statePath('data') + ->components([ + (new Repeater('repeater')) + ->relationship('posts'), + ]) + ->model($user); + + $componentContainer->loadStateFromRelationships(); + + $componentContainer->saveRelationships(); + + expect($user->posts()->count()) + ->toBe(3); +}); + +it('throws an exception for a missing relationship', function () { + $componentContainer = Schema::make(Livewire::make()) + ->statePath('data') + ->components([ + (new Repeater(Str::random())) + ->relationship('missing'), + ]) + ->model(Post::factory()->create()); + + $componentContainer + ->saveRelationships(); +})->throws(Exception::class, 'The relationship [missing] does not exist on the model [Filament\Tests\Models\Post].'); + class TestComponentWithRepeater extends Livewire { public function form(Schema $form): Schema