diff --git a/.github/workflows/npm-build.yml b/.github/workflows/npm-build.yml index 60c9b3b71a..25bd772489 100644 --- a/.github/workflows/npm-build.yml +++ b/.github/workflows/npm-build.yml @@ -1,33 +1,33 @@ name: npm-build on: - pull_request: - branches: - - 2.x - types: [closed] + pull_request: + branches: + - 2.x + types: [closed] jobs: - npm-build: - runs-on: ubuntu-latest - if: github.event.pull_request.merged == true - steps: - - name: Checkout code - uses: actions/checkout@v2 - with: - ref: 2.x + npm-build: + runs-on: ubuntu-latest + if: github.event.pull_request.merged == true + steps: + - name: Checkout code + uses: actions/checkout@v2 + with: + ref: 2.x - - name: Setup Node - uses: actions/setup-node@v2 + - name: Setup Node + uses: actions/setup-node@v2 - - name: Install dependencies - run: npm ci + - name: Install dependencies + run: npm ci - - name: Build assets - run: npm run build + - name: Build assets + run: npm run build - - name: Commit changes - uses: stefanzweifel/git-auto-commit-action@v4 - with: - branch: 2.x - commit_message: > - chore: build assets + - name: Commit changes + uses: stefanzweifel/git-auto-commit-action@v4 + with: + branch: 2.x + commit_message: > + chore: build assets diff --git a/.github/workflows/phpstan.yml b/.github/workflows/phpstan.yml new file mode 100644 index 0000000000..6ffe802287 --- /dev/null +++ b/.github/workflows/phpstan.yml @@ -0,0 +1,24 @@ +name: phpstan + +on: + push: + pull_request: + +jobs: + phpstan: + name: phpstan + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '8.0' + coverage: none + + - name: Install composer dependencies + uses: ramsey/composer-install@v1 + + - name: Run PHPStan + run: ./vendor/bin/phpstan --error-format=github diff --git a/composer.json b/composer.json index 77e1875079..40f15d15c4 100644 --- a/composer.json +++ b/composer.json @@ -44,7 +44,7 @@ "sort-packages": true }, "scripts": { - "test": "./vendor/bin/pest --parallel" + "test": "./vendor/bin/pest --parallel && ./vendor/bin/phpstan analyse" }, "minimum-stability": "dev", "prefer-stable": true diff --git a/packages/admin/src/Commands/MakeUserCommand.php b/packages/admin/src/Commands/MakeUserCommand.php index c788a6cf8a..a94c9b3c76 100644 --- a/packages/admin/src/Commands/MakeUserCommand.php +++ b/packages/admin/src/Commands/MakeUserCommand.php @@ -3,6 +3,8 @@ namespace Filament\Commands; use Filament\Facades\Filament; +use Illuminate\Auth\EloquentUserProvider; +use Illuminate\Auth\SessionGuard; use Illuminate\Console\Command; use Illuminate\Support\Facades\Hash; @@ -16,9 +18,13 @@ class MakeUserCommand extends Command public function handle(): int { + /** @var SessionGuard $auth */ $auth = Filament::auth(); - $userModel = $auth->getProvider()->getModel(); + /** @var EloquentUserProvider $userProvider */ + $userProvider = $auth->getProvider(); + + $userModel = $userProvider->getModel(); $user = $userModel::create([ 'name' => $this->validateInput(fn () => $this->ask('Name'), 'name', ['required']), @@ -29,7 +35,7 @@ class MakeUserCommand extends Command $loginUrl = route('filament.auth.login'); $this->info("Success! {$user->email} may now log in at {$loginUrl}."); - if ($auth->getProvider()->getModel()::count() === 1 && $this->confirm('Would you like to show some love by starring the repo?', true)) { + if ($userProvider->getModel()::count() === 1 && $this->confirm('Would you like to show some love by starring the repo?', true)) { if (PHP_OS_FAMILY === 'Darwin') { exec('open https://github.com/laravel-filament/filament'); } diff --git a/packages/admin/src/FilamentManager.php b/packages/admin/src/FilamentManager.php index 528c14a981..53c5444c0c 100644 --- a/packages/admin/src/FilamentManager.php +++ b/packages/admin/src/FilamentManager.php @@ -7,7 +7,9 @@ use Filament\Events\ServingFilament; use Filament\Models\Contracts\HasAvatar; use Filament\Models\Contracts\HasName; use Illuminate\Contracts\Auth\Authenticatable; +use Illuminate\Contracts\Auth\Guard; use Illuminate\Contracts\Auth\StatefulGuard; +use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Arr; use Illuminate\Support\Facades\Event; @@ -33,7 +35,7 @@ class FilamentManager protected array $widgets = []; - public function auth(): StatefulGuard + public function auth(): Guard { return auth()->guard(config('filament.auth.guard')); } @@ -190,7 +192,7 @@ class FilamentManager return $firstItem->getUrl(); } - public function getUserAvatarUrl(Authenticatable $user): string + public function getUserAvatarUrl(Model $user): string { $avatar = null; @@ -207,13 +209,13 @@ class FilamentManager return (new $provider())->get($user); } - public function getUserName(Authenticatable $user): string + public function getUserName(Model $user): string { if ($user instanceof HasName) { return $user->getFilamentName(); } - return $user->name; + return $user->getAttributeValue('name'); } public function getWidgets(): array diff --git a/packages/admin/src/FilamentServiceProvider.php b/packages/admin/src/FilamentServiceProvider.php index 8c809e3b93..0313e9eda7 100644 --- a/packages/admin/src/FilamentServiceProvider.php +++ b/packages/admin/src/FilamentServiceProvider.php @@ -19,7 +19,7 @@ use Livewire\Livewire; use ReflectionClass; use Spatie\LaravelPackageTools\Package; use Spatie\LaravelPackageTools\PackageServiceProvider; -use SplFileInfo; +use Symfony\Component\Finder\SplFileInfo; class FilamentServiceProvider extends PackageServiceProvider { diff --git a/packages/admin/src/Http/Livewire/Auth/Login.php b/packages/admin/src/Http/Livewire/Auth/Login.php index 7ff7a5ea01..59a86423da 100644 --- a/packages/admin/src/Http/Livewire/Auth/Login.php +++ b/packages/admin/src/Http/Livewire/Auth/Login.php @@ -5,13 +5,18 @@ namespace Filament\Http\Livewire\Auth; use DanHarrin\LivewireRateLimiting\Exceptions\TooManyRequestsException; use DanHarrin\LivewireRateLimiting\WithRateLimiting; use Filament\Facades\Filament; +use Filament\Forms\ComponentContainer; use Filament\Forms\Components\Checkbox; use Filament\Forms\Components\TextInput; use Filament\Forms\Concerns\InteractsWithForms; use Filament\Forms\Contracts\HasForms; use Illuminate\Contracts\View\View; use Livewire\Component; +use Livewire\Macros\ViewMacros; +/** + * @property ComponentContainer $form + */ class Login extends Component implements HasForms { use InteractsWithForms; @@ -76,9 +81,17 @@ class Login extends Component implements HasForms public function render(): View { - return view('filament::login') - ->layout('filament::components.layouts.base', [ - 'title' => __('filament::login.title'), - ]); + $view = view('filament::login'); + + /* + * Livewire uses a macro for the `layout()` method. + * + * @phpstan-ignore-next-line + */ + $view->layout('filament::components.layouts.base', [ + 'title' => __('filament::login.title'), + ]); + + return $view; } } diff --git a/packages/admin/src/Http/Middleware/Authenticate.php b/packages/admin/src/Http/Middleware/Authenticate.php index 6c16c73599..82189ad6e1 100644 --- a/packages/admin/src/Http/Middleware/Authenticate.php +++ b/packages/admin/src/Http/Middleware/Authenticate.php @@ -20,7 +20,7 @@ class Authenticate extends Middleware $this->auth->shouldUse($guardName); - $user = $this->auth->user(); + $user = $guard->user(); if (config('app.env') === 'local') { return; diff --git a/packages/admin/src/Pages/Page.php b/packages/admin/src/Pages/Page.php index 30044cad88..1e5cd71605 100644 --- a/packages/admin/src/Pages/Page.php +++ b/packages/admin/src/Pages/Page.php @@ -9,6 +9,7 @@ use Illuminate\Contracts\View\View; use Illuminate\Support\Facades\Route; use Illuminate\Support\Str; use Livewire\Component; +use Livewire\Macros\ViewMacros; class Page extends Component { @@ -86,8 +87,16 @@ class Page extends Component public function render(): View { - return view(static::$view, $this->getViewData()) - ->layout(static::$layout, $this->getLayoutData()); + $view = view(static::$view, $this->getViewData()); + + /* + * Livewire uses a macro for the `layout()` method. + * + * @phpstan-ignore-next-line + */ + $view->layout(static::$layout, $this->getLayoutData()); + + return $view; } protected function getBreadcrumbs(): array diff --git a/packages/admin/src/Resources/Pages/CreateRecord.php b/packages/admin/src/Resources/Pages/CreateRecord.php index ff311b9b43..99ded99260 100644 --- a/packages/admin/src/Resources/Pages/CreateRecord.php +++ b/packages/admin/src/Resources/Pages/CreateRecord.php @@ -3,9 +3,13 @@ namespace Filament\Resources\Pages; use Filament\Forms; +use Filament\Forms\ComponentContainer; use Filament\Pages\Actions\ButtonAction; use Illuminate\Support\Str; +/** + * @property ComponentContainer $form + */ class CreateRecord extends Page implements Forms\Contracts\HasForms { use Concerns\UsesResourceForm; diff --git a/packages/admin/src/Resources/Pages/EditRecord.php b/packages/admin/src/Resources/Pages/EditRecord.php index 969b876889..7af3d85097 100644 --- a/packages/admin/src/Resources/Pages/EditRecord.php +++ b/packages/admin/src/Resources/Pages/EditRecord.php @@ -3,9 +3,13 @@ namespace Filament\Resources\Pages; use Filament\Forms; +use Filament\Forms\ComponentContainer; use Filament\Pages\Actions\ButtonAction; use Illuminate\Support\Str; +/** + * @property ComponentContainer $form + */ class EditRecord extends Page implements Forms\Contracts\HasForms { use Concerns\HasRecordBreadcrumb; diff --git a/packages/admin/src/Resources/Pages/ListRecords.php b/packages/admin/src/Resources/Pages/ListRecords.php index 41f4480bec..6c3702e7c6 100644 --- a/packages/admin/src/Resources/Pages/ListRecords.php +++ b/packages/admin/src/Resources/Pages/ListRecords.php @@ -83,7 +83,7 @@ class ListRecords extends Page implements Tables\Contracts\HasTable { return Tables\Actions\BulkAction::make('delete') ->label(__('filament::resources/pages/list-records.table.bulk_actions.delete.label')) - ->action(fn (Collection $records) => $records->each->delete()) + ->action(fn (Collection $records) => $records->each(fn (Model $record) => $record->delete())) ->requiresConfirmation() ->deselectRecordsAfterCompletion() ->color('danger') diff --git a/packages/admin/src/Resources/Pages/ViewRecord.php b/packages/admin/src/Resources/Pages/ViewRecord.php index 3ebb78782c..25a8841b73 100644 --- a/packages/admin/src/Resources/Pages/ViewRecord.php +++ b/packages/admin/src/Resources/Pages/ViewRecord.php @@ -3,9 +3,13 @@ namespace Filament\Resources\Pages; use Filament\Forms; +use Filament\Forms\ComponentContainer; use Filament\Pages\Actions\ButtonAction; use Illuminate\Support\Str; +/** + * @property ComponentContainer $form + */ class ViewRecord extends Page implements Forms\Contracts\HasForms { use Concerns\HasRecordBreadcrumb; diff --git a/packages/admin/src/Resources/RelationManagers/BelongsToManyRelationManager.php b/packages/admin/src/Resources/RelationManagers/BelongsToManyRelationManager.php index 289a9941a6..755497b87d 100644 --- a/packages/admin/src/Resources/RelationManagers/BelongsToManyRelationManager.php +++ b/packages/admin/src/Resources/RelationManagers/BelongsToManyRelationManager.php @@ -4,6 +4,8 @@ namespace Filament\Resources\RelationManagers; use Filament\Resources\Table; use Illuminate\Database\Eloquent\Builder; +use Illuminate\Database\Eloquent\Relations\BelongsToMany; +use Illuminate\Database\Eloquent\Relations\Relation; use Illuminate\Support\Arr; class BelongsToManyRelationManager extends RelationManager @@ -55,7 +57,9 @@ class BelongsToManyRelationManager extends RelationManager $this->callHook('beforeCreate'); + /** @var BelongsToMany $relationship */ $relationship = $this->getRelationship(); + $pivotColumns = $relationship->getPivotColumns(); $pivotData = Arr::only($data, $pivotColumns); $data = Arr::except($data, $pivotColumns); @@ -79,7 +83,9 @@ class BelongsToManyRelationManager extends RelationManager $this->callHook('beforeSave'); + /** @var BelongsToMany $relationship */ $relationship = $this->getRelationship(); + $pivotColumns = $relationship->getPivotColumns(); $pivotData = Arr::only($data, $pivotColumns); $data = Arr::except($data, $pivotColumns); @@ -96,6 +102,15 @@ class BelongsToManyRelationManager extends RelationManager { $query = parent::getTableQuery(); - return $query->select($this->getRelationship()->getTable().'.*', $query->getModel()->getTable().'.*'); + /** @var BelongsToMany $relationship */ + $relationship = $this->getRelationship(); + + /** @var Builder $query */ + $query->select( + $relationship->getTable().'.*', + $query->getModel()->getTable().'.*', + ); + + return $query; } } diff --git a/packages/admin/src/Resources/RelationManagers/Concerns/CanAttachRecords.php b/packages/admin/src/Resources/RelationManagers/Concerns/CanAttachRecords.php index 705621f510..e93d84a987 100644 --- a/packages/admin/src/Resources/RelationManagers/Concerns/CanAttachRecords.php +++ b/packages/admin/src/Resources/RelationManagers/Concerns/CanAttachRecords.php @@ -7,7 +7,9 @@ use Filament\Resources\Form; use Filament\Resources\RelationManagers\RelationManager; use Filament\Tables; use Filament\Tables\Actions\Modal\Actions\ButtonAction; +use Illuminate\Database\Connection; use Illuminate\Database\Eloquent\Builder; +use Illuminate\Database\Eloquent\Relations\BelongsToMany; use Illuminate\Support\Arr; trait CanAttachRecords @@ -45,10 +47,15 @@ trait CanAttachRecords $displayColumnName = static::getRecordTitleAttribute(); - $relationshipQuery = $relationship->getRelated()->orderBy($displayColumnName); + /** @var Builder $relationshipQuery */ + $relationshipQuery = $relationship->getRelated()->query()->orderBy($displayColumnName); $query = strtolower($query); - $searchOperator = match ($relationshipQuery->getConnection()->getDriverName()) { + + /** @var Connection $databaseConnection */ + $databaseConnection = $relationshipQuery->getConnection(); + + $searchOperator = match ($databaseConnection->getDriverName()) { 'pgsql' => 'ilike', default => 'like', }; @@ -61,7 +68,7 @@ trait CanAttachRecords ->pluck($displayColumnName, $relationship->getRelated()->getKeyName()) ->toArray(); }) - ->getOptionLabelUsing(fn (RelationManager $livewire, $value): ?string => static::getRecordTitle($livewire->getRelationship()->getRelated()->find($value))) + ->getOptionLabelUsing(fn (RelationManager $livewire, $value): ?string => static::getRecordTitle($livewire->getRelationship()->getRelated()->query()->find($value))) ->disableLabel(); } @@ -95,10 +102,12 @@ trait CanAttachRecords $this->callHook('beforeCreate'); + /** @var BelongsToMany $relationship */ $relationship = $this->getRelationship(); + $pivotColumns = $relationship->getPivotColumns(); - $record = $relationship->getRelated()->find($data['recordId']); + $record = $relationship->getRelated()->query()->find($data['recordId']); $relationship->attach($record, Arr::only($data, $pivotColumns)); $this->callHook('afterCreate'); diff --git a/packages/admin/src/Resources/RelationManagers/Concerns/CanDeleteRecords.php b/packages/admin/src/Resources/RelationManagers/Concerns/CanDeleteRecords.php index f595a84b49..d50a2f0d63 100644 --- a/packages/admin/src/Resources/RelationManagers/Concerns/CanDeleteRecords.php +++ b/packages/admin/src/Resources/RelationManagers/Concerns/CanDeleteRecords.php @@ -43,7 +43,7 @@ trait CanDeleteRecords { return Tables\Actions\BulkAction::make('delete') ->label(__('filament::resources/relation-managers/delete.bulk_action.label')) - ->action(fn (Collection $records) => $records->each->delete()) + ->action(fn (Collection $records) => $records->each(fn (Model $record) => $record->delete())) ->requiresConfirmation() ->modalHeading(__('filament::resources/relation-managers/delete.bulk_action.modal.heading', ['label' => static::getPluralRecordLabel()])) ->deselectRecordsAfterCompletion() diff --git a/packages/admin/src/Resources/RelationManagers/Concerns/CanDetachRecords.php b/packages/admin/src/Resources/RelationManagers/Concerns/CanDetachRecords.php index bf9dc2d689..d3cfd7d883 100644 --- a/packages/admin/src/Resources/RelationManagers/Concerns/CanDetachRecords.php +++ b/packages/admin/src/Resources/RelationManagers/Concerns/CanDetachRecords.php @@ -5,6 +5,7 @@ namespace Filament\Resources\RelationManagers\Concerns; use Filament\Tables; use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Eloquent\Relations\BelongsToMany; trait CanDetachRecords { @@ -22,7 +23,10 @@ trait CanDetachRecords { $this->callHook('beforeDetach'); - $this->getRelationship()->detach($this->getMountedTableActionRecord()); + /** @var BelongsToMany $relationship */ + $relationship = $this->getRelationship(); + + $relationship->detach($this->getMountedTableActionRecord()); $this->callHook('afterDetach'); } @@ -43,7 +47,12 @@ trait CanDetachRecords { return Tables\Actions\BulkAction::make('detach') ->label(__('filament::resources/relation-managers/detach.bulk_action.label')) - ->action(fn (Collection $records) => $this->getRelationship()->detach($records)) + ->action(function (Collection $records) { + /** @var BelongsToMany $relationship */ + $relationship = $this->getRelationship(); + + $relationship->detach($records); + }) ->requiresConfirmation() ->modalHeading(__('filament::resources/relation-managers/detach.bulk_action.modal.heading', ['label' => static::getPluralRecordLabel()])) ->deselectRecordsAfterCompletion() diff --git a/packages/admin/src/Resources/RelationManagers/HasManyThroughRelationManager.php b/packages/admin/src/Resources/RelationManagers/HasManyThroughRelationManager.php index 10785f50d7..ab87769065 100644 --- a/packages/admin/src/Resources/RelationManagers/HasManyThroughRelationManager.php +++ b/packages/admin/src/Resources/RelationManagers/HasManyThroughRelationManager.php @@ -11,6 +11,9 @@ class HasManyThroughRelationManager extends HasManyRelationManager { $query = parent::getTableQuery(); - return $query->select($query->getModel()->getTable().'.*'); + /** @var Builder $query */ + $query->select($query->getModel()->getTable().'.*'); + + return $query; } } diff --git a/packages/admin/src/Resources/RelationManagers/RelationManager.php b/packages/admin/src/Resources/RelationManagers/RelationManager.php index 4234e9970f..7aeb1867ea 100644 --- a/packages/admin/src/Resources/RelationManagers/RelationManager.php +++ b/packages/admin/src/Resources/RelationManagers/RelationManager.php @@ -143,6 +143,15 @@ class RelationManager extends Component implements Tables\Contracts\HasTable return $this->ownerRecord->{static::getRelationshipName()}(); } + protected function getResourceTable(): Table + { + if (! $this->resourceTable) { + $this->resourceTable = Table::make(); + } + + return $this->resourceTable; + } + protected function getDefaultTableSortColumn(): ?string { return $this->getResourceTable()->getDefaultSortColumn(); diff --git a/packages/admin/src/Resources/Resource.php b/packages/admin/src/Resources/Resource.php index 260f4d3060..ea070525a9 100644 --- a/packages/admin/src/Resources/Resource.php +++ b/packages/admin/src/Resources/Resource.php @@ -5,6 +5,7 @@ namespace Filament\Resources; use Closure; use Filament\Facades\Filament; use Filament\Navigation\NavigationItem; +use Illuminate\Database\Connection; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Arr; @@ -250,7 +251,7 @@ class Resource return function () { $slug = static::getSlug(); - Route::name("{$slug}.")->prefix($slug)->group(function () use ($slug) { + Route::name("{$slug}.")->prefix($slug)->group(function () { foreach (static::getPages() as $name => $page) { Route::get($page['route'], $page['class'])->name($name); } @@ -284,7 +285,10 @@ class Resource protected static function applyGlobalSearchAttributeConstraint(Builder $query, array $searchAttributes, string $searchQuery, bool &$isFirst): Builder { - $searchOperator = match ($query->getConnection()->getDriverName()) { + /** @var Connection $databaseConnection */ + $databaseConnection = $query->getConnection(); + + $searchOperator = match ($databaseConnection->getDriverName()) { 'pgsql' => 'ilike', default => 'like', }; diff --git a/packages/forms/composer.json b/packages/forms/composer.json index 179b97dc79..e1cc4363fe 100644 --- a/packages/forms/composer.json +++ b/packages/forms/composer.json @@ -22,6 +22,9 @@ "spatie/laravel-package-tools": "^1.9" }, "autoload": { + "files": [ + "src/helpers.php" + ], "psr-4": { "Filament\\Forms\\": "src" } diff --git a/packages/forms/src/Components/BaseFileUpload.php b/packages/forms/src/Components/BaseFileUpload.php index b6a28227ec..6243999c03 100644 --- a/packages/forms/src/Components/BaseFileUpload.php +++ b/packages/forms/src/Components/BaseFileUpload.php @@ -3,6 +3,7 @@ namespace Filament\Forms\Components; use Illuminate\Contracts\Filesystem\Filesystem; +use Illuminate\Filesystem\FilesystemAdapter; use Illuminate\Support\Facades\Storage; use League\Flysystem\AwsS3v3\AwsS3Adapter; use SplFileInfo; @@ -46,7 +47,10 @@ class BaseFileUpload extends Field return; } - $component->getContainer()->getParentComponent()->appendNewUploadField(); + /** @var MultipleFileUpload $parentComponent */ + $parentComponent = $component->getContainer()->getParentComponent(); + + $parentComponent->appendNewUploadField(); }); $this->dehydrated(fn (BaseFileUpload $component): bool => ! $component->isMultiple()); @@ -222,10 +226,14 @@ class BaseFileUpload extends Field protected function handleUploadedFileUrlRetrieval($file): ?string { + /** @var FilesystemAdapter $storage */ $storage = $this->getDisk(); + /** @var \League\Flysystem\Filesystem $storageDriver */ + $storageDriver = $storage->getDriver(); + if ( - $storage->getDriver()->getAdapter() instanceof AwsS3Adapter && + $storageDriver->getAdapter() instanceof AwsS3Adapter && $storage->getVisibility($file) === 'private' ) { return $storage->temporaryUrl( @@ -266,4 +274,44 @@ class BaseFileUpload extends Field return $file->{$storeMethod}($this->getDirectory(), $this->getDiskName()); } + + public function isMultiple(): bool + { + return $this->getContainer()->getParentComponent() instanceof MultipleFileUpload; + } + + protected function handleUploadedFileRemoval($file): void + { + $this->state(null); + } + + protected function handleUploadedFileDeletion($file): void + { + } + + public function removeUploadedFile(): static + { + $file = $this->getState(); + + if ($callback = $this->removeUploadedFileUsing) { + $this->evaluate($callback, [ + 'file' => $file, + ]); + } else { + $this->handleUploadedFileRemoval($file); + } + + if ($this->isMultiple()) { + $container = $this->getContainer(); + + /** @var MultipleFileUpload $parentComponent */ + $parentComponent = $container->getParentComponent(); + + $parentComponent->removeUploadedFile( + $container->getStatePath(isAbsolute: false), + ); + } + + return $this; + } } diff --git a/packages/forms/src/Components/BelongsToManyMultiSelect.php b/packages/forms/src/Components/BelongsToManyMultiSelect.php index 181b585ae4..d6c5f59654 100644 --- a/packages/forms/src/Components/BelongsToManyMultiSelect.php +++ b/packages/forms/src/Components/BelongsToManyMultiSelect.php @@ -2,6 +2,7 @@ namespace Filament\Forms\Components; +use Illuminate\Database\Connection; use Illuminate\Database\Eloquent\Relations\BelongsToMany; use Illuminate\Support\Str; @@ -18,7 +19,7 @@ class BelongsToManyMultiSelect extends MultiSelect parent::setUp(); $this->afterStateHydrated(function (BelongsToManyMultiSelect $component): void { - if (count($this->getState() ?? [])) { + if (count($component->getState())) { return; } @@ -55,7 +56,7 @@ class BelongsToManyMultiSelect extends MultiSelect $relationship = $component->getRelationship(); $relatedKeyName = $relationship->getRelatedKeyName(); - return $relationship->getRelated() + return $relationship->getRelated()->query() ->whereIn($relatedKeyName, $values) ->pluck($component->getDisplayColumnName(), $relatedKeyName) ->toArray(); @@ -64,7 +65,7 @@ class BelongsToManyMultiSelect extends MultiSelect $this->getSearchResultsUsing(function (BelongsToManyMultiSelect $component, ?string $query) use ($callback): array { $relationship = $component->getRelationship(); - $relationshipQuery = $relationship->getRelated()->orderBy($component->getDisplayColumnName()); + $relationshipQuery = $relationship->getRelated()->query()->orderBy($component->getDisplayColumnName()); if ($callback) { $relationshipQuery = $this->evaluate($callback, [ @@ -73,7 +74,11 @@ class BelongsToManyMultiSelect extends MultiSelect } $query = strtolower($query); - $searchOperator = match ($relationshipQuery->getConnection()->getDriverName()) { + + /** @var Connection $databaseConnection */ + $databaseConnection = $relationshipQuery->getConnection(); + + $searchOperator = match ($databaseConnection->getDriverName()) { 'pgsql' => 'ilike', default => 'like', }; @@ -91,7 +96,7 @@ class BelongsToManyMultiSelect extends MultiSelect $relationship = $component->getRelationship(); - $relationshipQuery = $relationship->getRelated()->orderBy($component->getDisplayColumnName()); + $relationshipQuery = $relationship->getRelated()->query()->orderBy($component->getDisplayColumnName()); if ($callback) { $relationshipQuery = $this->evaluate($callback, [ diff --git a/packages/forms/src/Components/BelongsToSelect.php b/packages/forms/src/Components/BelongsToSelect.php index ca7cbc2f12..49b84f7597 100644 --- a/packages/forms/src/Components/BelongsToSelect.php +++ b/packages/forms/src/Components/BelongsToSelect.php @@ -2,6 +2,7 @@ namespace Filament\Forms\Components; +use Illuminate\Database\Connection; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Support\Str; @@ -52,15 +53,15 @@ class BelongsToSelect extends Select $this->getOptionLabelUsing(function (BelongsToSelect $component, $value) { $relationship = $component->getRelationship(); - $record = $relationship->getRelated()->where($relationship->getOwnerKeyName(), $value)->first(); + $record = $relationship->getRelated()->query()->where($relationship->getOwnerKeyName(), $value)->first(); - return $record ? $record->getAttributeValue($component->getDisplayColumnName()) : null; + return $record?->getAttributeValue($component->getDisplayColumnName()); }); $this->getSearchResultsUsing(function (BelongsToSelect $component, ?string $query) use ($callback): array { $relationship = $component->getRelationship(); - $relationshipQuery = $relationship->getRelated()->orderBy($component->getDisplayColumnName()); + $relationshipQuery = $relationship->getRelated()->query()->orderBy($component->getDisplayColumnName()); if ($callback) { $relationshipQuery = $this->evaluate($callback, [ @@ -69,7 +70,11 @@ class BelongsToSelect extends Select } $query = strtolower($query); - $searchOperator = match ($relationshipQuery->getConnection()->getDriverName()) { + + /** @var Connection $databaseConnection */ + $databaseConnection = $relationshipQuery->getConnection(); + + $searchOperator = match ($databaseConnection->getDriverName()) { 'pgsql' => 'ilike', default => 'like', }; @@ -87,7 +92,7 @@ class BelongsToSelect extends Select $relationship = $component->getRelationship(); - $relationshipQuery = $relationship->getRelated()->orderBy($component->getDisplayColumnName()); + $relationshipQuery = $relationship->getRelated()->query()->orderBy($component->getDisplayColumnName()); if ($callback) { $relationshipQuery = $this->evaluate($callback, [ diff --git a/packages/forms/src/Components/Builder.php b/packages/forms/src/Components/Builder.php index df47f90ca5..71f9416151 100644 --- a/packages/forms/src/Components/Builder.php +++ b/packages/forms/src/Components/Builder.php @@ -6,6 +6,8 @@ use Filament\Forms\ComponentContainer; use Filament\Forms\Components\Builder\Block; use Illuminate\Support\Arr; use Illuminate\Support\Str; +use function Filament\Forms\array_move_after; +use function Filament\Forms\array_move_before; class Builder extends Field { @@ -93,7 +95,7 @@ class Builder extends Field return; } - $items = Arr::moveElementAfter($component->getNormalisedState(), $uuidToMoveDown); + $items = array_move_after($component->getNormalisedState(), $uuidToMoveDown); $livewire = $component->getLivewire(); data_set($livewire, $statePath, $items); @@ -113,7 +115,7 @@ class Builder extends Field return; } - $items = Arr::moveElementBefore($component->getNormalisedState(), $uuidToMoveUp); + $items = array_move_before($component->getNormalisedState(), $uuidToMoveUp); $livewire = $component->getLivewire(); data_set($livewire, $statePath, $items); diff --git a/packages/forms/src/Components/Concerns/CanBeValidated.php b/packages/forms/src/Components/Concerns/CanBeValidated.php index 5cbcff3feb..f467c6c97d 100644 --- a/packages/forms/src/Components/Concerns/CanBeValidated.php +++ b/packages/forms/src/Components/Concerns/CanBeValidated.php @@ -3,7 +3,7 @@ namespace Filament\Forms\Components\Concerns; use Closure; -use Filament\Forms\Components\Component; +use Filament\Forms\Components\Field; use Illuminate\Database\Eloquent\Model; use Illuminate\Validation\Rule; use Illuminate\Validation\Rules\Unique; @@ -18,7 +18,7 @@ trait CanBeValidated public function exists(string | Closure | null $table = null, string | Closure | null $column = null, ?Closure $callback = null): static { - $this->rule(function (Component $component) use ($callback, $column, $table) { + $this->rule(function (Field $component) use ($callback, $column, $table) { $table = $component->evaluate($table) ?? $component->getModelClass(); $column = $component->evaluate($column) ?? $component->getName(); @@ -31,14 +31,14 @@ trait CanBeValidated } return $rule; - }, fn (Component $component): bool => (bool) ($component->evaluate($table) ?? $component->getModelClass())); + }, fn (Field $component): bool => (bool) ($component->evaluate($table) ?? $component->getModelClass())); return $this; } public function nullable(bool | Closure $condition = true): static { - $this->required(function (Component $component) use ($condition): bool { + $this->required(function (Field $component) use ($condition): bool { return ! $component->evaluate($condition); }); @@ -124,7 +124,7 @@ trait CanBeValidated public function unique(string | Closure | null $table = null, string | Closure | null $column = null, Model | Closure $ignorable = null, ?Closure $callback = null): static { - $this->rule(function (Component $component) use ($callback, $column, $ignorable, $table) { + $this->rule(function (Field $component) use ($callback, $column, $ignorable, $table) { $table = $component->evaluate($table) ?? $component->getModelClass(); $column = $component->evaluate($column) ?? $component->getName(); $ignorable = $component->evaluate($ignorable); @@ -145,7 +145,7 @@ trait CanBeValidated } return $rule; - }, fn (Component $component): bool => (bool) ($component->evaluate($table) ?? $component->getModelClass())); + }, fn (Field $component): bool => (bool) ($component->evaluate($table) ?? $component->getModelClass())); return $this; } @@ -191,7 +191,7 @@ trait CanBeValidated protected function dateComparisonRule(string $rule, string | Closure $date, bool $isStatePathAbsolute = false): static { - $this->rule(function (Component $component) use ($date, $isStatePathAbsolute, $rule): string { + $this->rule(function (Field $component) use ($date, $isStatePathAbsolute, $rule): string { $date = $component->evaluate($date); if (! (strtotime($date) && $isStatePathAbsolute)) { @@ -203,14 +203,14 @@ trait CanBeValidated } return "{$rule}:{$date}"; - }, fn (Component $component): bool => (bool) $component->evaluate($date)); + }, fn (Field $component): bool => (bool) $component->evaluate($date)); return $this; } protected function fieldComparisonRule(string $rule, string | Closure $statePath, bool $isStatePathAbsolute = false): static { - $this->rule(function (Component $component) use ($isStatePathAbsolute, $rule, $statePath): string { + $this->rule(function (Field $component) use ($isStatePathAbsolute, $rule, $statePath): string { $statePath = $component->evaluate($statePath); if (! $isStatePathAbsolute) { @@ -222,7 +222,7 @@ trait CanBeValidated } return "{$rule}:{$statePath}"; - }, fn (Component $component): bool => (bool) $component->evaluate($statePath)); + }, fn (Field $component): bool => (bool) $component->evaluate($statePath)); return $this; } diff --git a/packages/forms/src/Components/Concerns/CanLimitItemsLength.php b/packages/forms/src/Components/Concerns/CanLimitItemsLength.php index 8f63ddf38b..80d0474fdd 100644 --- a/packages/forms/src/Components/Concerns/CanLimitItemsLength.php +++ b/packages/forms/src/Components/Concerns/CanLimitItemsLength.php @@ -17,6 +17,8 @@ trait CanLimitItemsLength $this->rule('array'); $this->rule(function (Component $component): string { + /** @var static $component */ + $count = $component->getMaxItems(); return "max:{$count}"; @@ -31,6 +33,8 @@ trait CanLimitItemsLength $this->rule('array'); $this->rule(function (Component $component): string { + /** @var static $component */ + $count = $component->getMinItems(); return "min:{$count}"; diff --git a/packages/forms/src/Components/Concerns/HasFileAttachments.php b/packages/forms/src/Components/Concerns/HasFileAttachments.php index a8e19f14bd..1af9ae35e6 100644 --- a/packages/forms/src/Components/Concerns/HasFileAttachments.php +++ b/packages/forms/src/Components/Concerns/HasFileAttachments.php @@ -4,6 +4,7 @@ namespace Filament\Forms\Components\Concerns; use Closure; use Illuminate\Contracts\Filesystem\Filesystem; +use Illuminate\Filesystem\FilesystemAdapter; use Illuminate\Support\Facades\Storage; use League\Flysystem\AwsS3v3\AwsS3Adapter; use SplFileInfo; @@ -103,10 +104,14 @@ trait HasFileAttachments protected function handleUploadedAttachmentUrlRetrieval($file): ?string { + /** @var FilesystemAdapter $storage */ $storage = $this->getFileAttachmentsDisk(); + /** @var \League\Flysystem\Filesystem $storageDriver */ + $storageDriver = $storage->getDriver(); + if ( - $storage->getDriver()->getAdapter() instanceof AwsS3Adapter && + $storageDriver->getAdapter() instanceof AwsS3Adapter && $storage->getVisibility($file) === 'private' ) { return $storage->temporaryUrl( diff --git a/packages/forms/src/Components/FileUpload.php b/packages/forms/src/Components/FileUpload.php index c9c932e30d..32b3a9d3a6 100644 --- a/packages/forms/src/Components/FileUpload.php +++ b/packages/forms/src/Components/FileUpload.php @@ -188,40 +188,4 @@ class FileUpload extends BaseFileUpload { return (bool) $this->evaluate($this->isAvatar); } - - public function isMultiple(): bool - { - return $this->getContainer()->getParentComponent() instanceof MultipleFileUpload; - } - - protected function handleUploadedFileRemoval($file): void - { - $this->state(null); - } - - protected function handleUploadedFileDeletion($file): void - { - } - - public function removeUploadedFile(): static - { - $file = $this->getState(); - - if ($callback = $this->removeUploadedFileUsing) { - $this->evaluate($callback, [ - 'file' => $file, - ]); - } else { - $this->handleUploadedFileRemoval($file); - } - - if ($this->isMultiple()) { - $container = $this->getContainer(); - $container->getParentComponent()->removeUploadedFile( - $container->getStatePath(isAbsolute: false), - ); - } - - return $this; - } } diff --git a/packages/forms/src/Components/Repeater.php b/packages/forms/src/Components/Repeater.php index c3962c7ee8..9f33151009 100644 --- a/packages/forms/src/Components/Repeater.php +++ b/packages/forms/src/Components/Repeater.php @@ -5,6 +5,8 @@ namespace Filament\Forms\Components; use Filament\Forms\ComponentContainer; use Illuminate\Support\Arr; use Illuminate\Support\Str; +use function Filament\Forms\array_move_after; +use function Filament\Forms\array_move_before; class Repeater extends Field { @@ -73,7 +75,7 @@ class Repeater extends Field return; } - $items = Arr::moveElementAfter($component->getNormalisedState(), $uuidToMoveDown); + $items = array_move_after($component->getNormalisedState(), $uuidToMoveDown); $livewire = $component->getLivewire(); data_set($livewire, $statePath, $items); @@ -93,7 +95,7 @@ class Repeater extends Field return; } - $items = Arr::moveElementBefore($component->getNormalisedState(), $uuidToMoveUp); + $items = array_move_before($component->getNormalisedState(), $uuidToMoveUp); $livewire = $component->getLivewire(); data_set($livewire, $statePath, $items); diff --git a/packages/forms/src/Concerns/SupportsComponentFileAttachments.php b/packages/forms/src/Concerns/SupportsComponentFileAttachments.php index f8cc7aaada..d8c4f12816 100644 --- a/packages/forms/src/Concerns/SupportsComponentFileAttachments.php +++ b/packages/forms/src/Concerns/SupportsComponentFileAttachments.php @@ -3,13 +3,14 @@ namespace Filament\Forms\Concerns; use Filament\Forms\Components\Contracts\HasFileAttachments; +use Filament\Forms\Components\Field; trait SupportsComponentFileAttachments { public function getComponentFileAttachmentUrl(string $statePath): ?string { foreach ($this->getComponents() as $component) { - if ($component instanceof HasFileAttachments && $component->getStatePath() === $statePath) { + if ($component instanceof HasFileAttachments && $component instanceof Field && $component->getStatePath() === $statePath) { $attachment = $this->getLivewire()->getComponentFileAttachment($statePath); if (! $attachment) { diff --git a/packages/forms/src/FormsServiceProvider.php b/packages/forms/src/FormsServiceProvider.php index 5bb801b3ca..418d1485b9 100644 --- a/packages/forms/src/FormsServiceProvider.php +++ b/packages/forms/src/FormsServiceProvider.php @@ -40,43 +40,4 @@ class FormsServiceProvider extends PackageServiceProvider return array_merge($commands, $aliases); } - - public function packageBooted(): void - { - Arr::macro('moveElementAfter', function (array $array, $keyToMoveAfter): array { - $keys = array_keys($array); - - $indexToMoveAfter = array_search($keyToMoveAfter, $keys); - $keyToMoveBefore = $keys[$indexToMoveAfter + 1]; - - $keys[$indexToMoveAfter + 1] = $keyToMoveAfter; - $keys[$indexToMoveAfter] = $keyToMoveBefore; - - $newArray = []; - - foreach ($keys as $key) { - $newArray[$key] = $array[$key]; - } - - return $newArray; - }); - - Arr::macro('moveElementBefore', function (array $array, $keyToMoveBefore): array { - $keys = array_keys($array); - - $indexToMoveBefore = array_search($keyToMoveBefore, $keys); - $keyToMoveAfter = $keys[$indexToMoveBefore - 1]; - - $keys[$indexToMoveBefore - 1] = $keyToMoveBefore; - $keys[$indexToMoveBefore] = $keyToMoveAfter; - - $newArray = []; - - foreach ($keys as $key) { - $newArray[$key] = $array[$key]; - } - - return $newArray; - }); - } } diff --git a/packages/forms/src/helpers.php b/packages/forms/src/helpers.php new file mode 100644 index 0000000000..34b8ffe12f --- /dev/null +++ b/packages/forms/src/helpers.php @@ -0,0 +1,44 @@ +getModel())) { + $model = $this->getModel(); + + if (! $model) { return $file; } - $media = $model - ->addMediaFromString($file->get()) + if (! method_exists($model, 'addMediaFromString')) { + return $file; + } + + /** @var FileAdder $mediaAdder */ + $mediaAdder = $model->addMediaFromString($file->get()); + + $media = $mediaAdder ->usingFileName($file->getFilename()) ->toMediaCollection($this->getCollection()); - return $media->uuid; + return $media->getAttributeValue('uuid'); } protected function handleUploadedFileDeletion($file): void @@ -119,8 +131,12 @@ class SpatieMediaLibraryFileUpload extends FileUpload protected function handleUploadedFileUrlRetrieval($file): ?string { + /** @var FilesystemAdapter $storage */ $storage = $this->getDisk(); + /** @var \League\Flysystem\Filesystem $storageDriver */ + $storageDriver = $storage->getDriver(); + if (! $this->getModel()) { return null; } @@ -129,13 +145,16 @@ class SpatieMediaLibraryFileUpload extends FileUpload return null; } + /** @var ?Media $media */ + $media = Media::findByUuid($file); + if ( - $storage->getDriver()->getAdapter() instanceof AwsS3Adapter && + $storageDriver->getAdapter() instanceof AwsS3Adapter && $this->getVisibility() === 'private' ) { - return Media::findByUuid($file)?->getTemporaryUrl(now()->addMinutes(5)); + return $media?->getTemporaryUrl(now()->addMinutes(5)); } - return Media::findByUuid($file)?->getUrl(); + return $media?->getUrl(); } } diff --git a/packages/spatie-laravel-media-library-plugin/src/Tables/Columns/SpatieMediaLibraryImageColumn.php b/packages/spatie-laravel-media-library-plugin/src/Tables/Columns/SpatieMediaLibraryImageColumn.php index 642953bf02..87c655e968 100644 --- a/packages/spatie-laravel-media-library-plugin/src/Tables/Columns/SpatieMediaLibraryImageColumn.php +++ b/packages/spatie-laravel-media-library-plugin/src/Tables/Columns/SpatieMediaLibraryImageColumn.php @@ -42,8 +42,13 @@ class SpatieMediaLibraryImageColumn extends ImageColumn return $state; } - return $this->getRecord() - ->getFirstMediaUrl($this->getCollection(), $this->getConversion()); + $record = $this->getRecord(); + + if (! method_exists($record, 'getFirstMediaUrl')) { + return $state; + } + + return $record->getFirstMediaUrl($this->getCollection(), $this->getConversion()); } public function applyEagreLoading(Builder $query): Builder diff --git a/packages/spatie-laravel-settings-plugin/src/Pages/SettingsPage.php b/packages/spatie-laravel-settings-plugin/src/Pages/SettingsPage.php index d60b8c1c57..d2b75a677a 100644 --- a/packages/spatie-laravel-settings-plugin/src/Pages/SettingsPage.php +++ b/packages/spatie-laravel-settings-plugin/src/Pages/SettingsPage.php @@ -3,8 +3,12 @@ namespace Filament\Pages; use Filament\Forms; +use Filament\Forms\ComponentContainer; use Filament\Pages\Actions\ButtonAction; +/** + * @property ComponentContainer $form + */ class SettingsPage extends Page implements Forms\Contracts\HasForms { use Forms\Concerns\InteractsWithForms; diff --git a/packages/spatie-laravel-tags-plugin/src/Forms/Components/SpatieTagsInput.php b/packages/spatie-laravel-tags-plugin/src/Forms/Components/SpatieTagsInput.php index 43362a6826..f6102bdb75 100644 --- a/packages/spatie-laravel-tags-plugin/src/Forms/Components/SpatieTagsInput.php +++ b/packages/spatie-laravel-tags-plugin/src/Forms/Components/SpatieTagsInput.php @@ -5,6 +5,7 @@ namespace Filament\Forms\Components; use Closure; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Model; +use Spatie\Tags\HasTags; use Spatie\Tags\Tag; class SpatieTagsInput extends TagsInput @@ -22,6 +23,10 @@ class SpatieTagsInput extends TagsInput return; } + if (! method_exists($model, 'tagsWithType')) { + return; + } + $type = $component->getType(); $tags = $model->tagsWithType($type); @@ -42,6 +47,10 @@ class SpatieTagsInput extends TagsInput $model = $this->getModel(); $tags = $this->getState(); + if (! (method_exists($model, 'syncTagsWithType') && method_exists($model, 'syncTags'))) { + return; + } + if ($type = $this->getType()) { $model->syncTagsWithType($tags, $type); @@ -69,7 +78,7 @@ class SpatieTagsInput extends TagsInput return Tag::query() ->when( filled($type), - fn (Builder $query) => $query->withType($type), + fn (Builder $query) => $query->where('type', $type), fn (Builder $query) => $query->whereNull('type'), ) ->pluck('name') diff --git a/packages/spatie-laravel-tags-plugin/src/Tables/Columns/SpatieTagsColumn.php b/packages/spatie-laravel-tags-plugin/src/Tables/Columns/SpatieTagsColumn.php index 3c19f91d20..d1efcfa30e 100644 --- a/packages/spatie-laravel-tags-plugin/src/Tables/Columns/SpatieTagsColumn.php +++ b/packages/spatie-laravel-tags-plugin/src/Tables/Columns/SpatieTagsColumn.php @@ -8,8 +8,14 @@ class SpatieTagsColumn extends TagsColumn public function getTags(): array { + $record = $this->getRecord(); + + if (! method_exists($record, 'tagsWithType')) { + return []; + } + $type = $this->getType(); - $tags = $this->getRecord()->tagsWithType($type); + $tags = $record->tagsWithType($type); return $tags->pluck('name')->toArray(); } diff --git a/packages/tables/src/Columns/Concerns/InteractsWithTableQuery.php b/packages/tables/src/Columns/Concerns/InteractsWithTableQuery.php index afb589ccdc..b527bdb460 100644 --- a/packages/tables/src/Columns/Concerns/InteractsWithTableQuery.php +++ b/packages/tables/src/Columns/Concerns/InteractsWithTableQuery.php @@ -2,6 +2,7 @@ namespace Filament\Tables\Columns\Concerns; +use Illuminate\Database\Connection; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\Relation; @@ -32,7 +33,10 @@ trait InteractsWithTableQuery return $query; } - $searchOperator = match ($query->getConnection()->getDriverName()) { + /** @var Connection $databaseConnection */ + $databaseConnection = $query->getConnection(); + + $searchOperator = match ($databaseConnection->getDriverName()) { 'pgsql' => 'ilike', default => 'like', }; diff --git a/packages/tables/src/Columns/ImageColumn.php b/packages/tables/src/Columns/ImageColumn.php index ef59dee364..66a49816d9 100644 --- a/packages/tables/src/Columns/ImageColumn.php +++ b/packages/tables/src/Columns/ImageColumn.php @@ -3,6 +3,7 @@ namespace Filament\Tables\Columns; use Illuminate\Contracts\Filesystem\Filesystem; +use Illuminate\Filesystem\FilesystemAdapter; use Illuminate\Support\Facades\Storage; use League\Flysystem\AwsS3v3\AwsS3Adapter; @@ -94,10 +95,14 @@ class ImageColumn extends Column return $state; } + /** @var FilesystemAdapter $storage */ $storage = $this->getDisk(); + /** @var \League\Flysystem\Filesystem $storageDriver */ + $storageDriver = $storage->getDriver(); + if ( - $storage->getDriver()->getAdapter() instanceof AwsS3Adapter && + $storageDriver->getAdapter() instanceof AwsS3Adapter && $storage->getVisibility($state) === 'private' ) { return $storage->temporaryUrl( diff --git a/packages/tables/src/Concerns/HasActions.php b/packages/tables/src/Concerns/HasActions.php index 2b0970e91e..eda11b265b 100644 --- a/packages/tables/src/Concerns/HasActions.php +++ b/packages/tables/src/Concerns/HasActions.php @@ -6,6 +6,9 @@ use Filament\Forms\ComponentContainer; use Filament\Tables\Actions\Action; use Illuminate\Database\Eloquent\Model; +/** + * @property ComponentContainer $mountedTableActionForm + */ trait HasActions { public $mountedTableAction = null; diff --git a/packages/tables/src/Concerns/HasBulkActions.php b/packages/tables/src/Concerns/HasBulkActions.php index dba8a9de12..b4b4fbf1ee 100644 --- a/packages/tables/src/Concerns/HasBulkActions.php +++ b/packages/tables/src/Concerns/HasBulkActions.php @@ -5,6 +5,9 @@ namespace Filament\Tables\Concerns; use Filament\Forms\ComponentContainer; use Filament\Tables\Actions\BulkAction; +/** + * @property ComponentContainer $mountedTableBulkActionForm + */ trait HasBulkActions { public $mountedTableBulkAction = null; diff --git a/packages/tables/src/Concerns/HasFilters.php b/packages/tables/src/Concerns/HasFilters.php index dd7c5a99a1..3857991363 100644 --- a/packages/tables/src/Concerns/HasFilters.php +++ b/packages/tables/src/Concerns/HasFilters.php @@ -3,9 +3,13 @@ namespace Filament\Tables\Concerns; use Filament\Forms; +use Filament\Forms\ComponentContainer; use Filament\Tables\Filters\Filter; use Illuminate\Database\Eloquent\Builder; +/** + * @property ComponentContainer $tableFiltersForm + */ trait HasFilters { protected array $cachedTableFilters; diff --git a/packages/tables/src/Concerns/HasRecords.php b/packages/tables/src/Concerns/HasRecords.php index 07e89bb814..756d00912b 100644 --- a/packages/tables/src/Concerns/HasRecords.php +++ b/packages/tables/src/Concerns/HasRecords.php @@ -2,14 +2,15 @@ namespace Filament\Tables\Concerns; -use Illuminate\Contracts\Pagination\LengthAwarePaginator; +use Illuminate\Contracts\Pagination\Paginator; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Model; +use Illuminate\Pagination\LengthAwarePaginator; trait HasRecords { - protected Collection | LengthAwarePaginator | null $records = null; + protected Collection | Paginator | null $records = null; protected function getFilteredTableQuery(): Builder { @@ -22,7 +23,7 @@ trait HasRecords return $query; } - public function getTableRecords(): Collection | LengthAwarePaginator + public function getTableRecords(): Collection | Paginator { if ($this->records) { return $this->records; @@ -37,14 +38,21 @@ trait HasRecords $this->applySortingToTableQuery($query); if ($this->isTablePaginationEnabled()) { - return $this->records = $query->paginate( + /** @var LengthAwarePaginator $records */ + $records = $query->paginate( $this->getTableRecordsPerPage(), ['*'], $this->getTablePaginationPageName(), - )->onEachSide(1); + ); + + $records->onEachSide(1); + + $this->records = $records; } else { - return $this->records = $query->get(); + $this->records = $query->get(); } + + return $this->records; } protected function resolveTableRecord(?string $key): ?Model diff --git a/packages/tables/src/Concerns/InteractsWithTable.php b/packages/tables/src/Concerns/InteractsWithTable.php index 58a57cbd8b..8e7571eb3a 100644 --- a/packages/tables/src/Concerns/InteractsWithTable.php +++ b/packages/tables/src/Concerns/InteractsWithTable.php @@ -4,7 +4,11 @@ namespace Filament\Tables\Concerns; use Filament\Forms; use Filament\Tables\Table; +use Illuminate\Database\Eloquent\Builder; +/** + * @method Builder getTableQuery() + */ trait InteractsWithTable { use CanPaginateRecords; diff --git a/packages/tables/src/Contracts/HasTable.php b/packages/tables/src/Contracts/HasTable.php index 08f42dad3c..560115480b 100644 --- a/packages/tables/src/Contracts/HasTable.php +++ b/packages/tables/src/Contracts/HasTable.php @@ -7,6 +7,8 @@ use Filament\Forms\Contracts\HasForms; use Filament\Tables\Actions\Action; use Filament\Tables\Actions\BulkAction; use Illuminate\Contracts\Pagination\LengthAwarePaginator; +use Illuminate\Contracts\Pagination\Paginator; +use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Collection; interface HasTable extends HasForms @@ -43,7 +45,7 @@ interface HasTable extends HasForms public function getTableFiltersForm(): ComponentContainer; - public function getTableRecords(): Collection | LengthAwarePaginator; + public function getTableRecords(): Collection | Paginator; public function getTableSortColumn(): ?string; diff --git a/packages/tables/src/Filters/MultiSelectFilter.php b/packages/tables/src/Filters/MultiSelectFilter.php index 4722efec62..f2851cbf6b 100644 --- a/packages/tables/src/Filters/MultiSelectFilter.php +++ b/packages/tables/src/Filters/MultiSelectFilter.php @@ -5,6 +5,7 @@ namespace Filament\Tables\Filters; use Filament\Forms\Components\MultiSelect; use Illuminate\Contracts\Support\Arrayable; use Illuminate\Database\Eloquent\Builder; +use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\Relation; use Illuminate\Support\Str; @@ -31,16 +32,22 @@ class MultiSelectFilter extends Filter } if ($this->queriesRelationships()) { + /** @var BelongsTo $relationship */ + $relationship = $this->getRelationship(); + return $query->whereHas( $this->getRelationshipName(), fn (Builder $query) => $query->whereIn( - $this->getRelationship()->getOwnerKeyName(), + $relationship->getOwnerKeyName(), $data['values'], ), ); } - return $query->whereIn($this->getColumn(), $data['values']); + /** @var Builder $query */ + $query = $query->whereIn($this->getColumn(), $data['values']); + + return $query; } public function column(string $name): static @@ -93,10 +100,12 @@ class MultiSelectFilter extends Filter protected function getRelationshipOptions(): array { + /** @var BelongsTo $relationship */ $relationship = $this->getRelationship(); + $displayColumnName = $this->getRelationshipDisplayColumnName(); - $relationshipQuery = $relationship->getRelated()->orderBy($displayColumnName); + $relationshipQuery = $relationship->getRelated()->query()->orderBy($displayColumnName); return $relationshipQuery ->pluck($displayColumnName, $relationship->getOwnerKeyName()) diff --git a/packages/tables/src/Filters/SelectFilter.php b/packages/tables/src/Filters/SelectFilter.php index 3d1bf11d1f..d766fe8324 100644 --- a/packages/tables/src/Filters/SelectFilter.php +++ b/packages/tables/src/Filters/SelectFilter.php @@ -5,6 +5,7 @@ namespace Filament\Tables\Filters; use Filament\Forms\Components\Select; use Illuminate\Contracts\Support\Arrayable; use Illuminate\Database\Eloquent\Builder; +use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\Relation; use Illuminate\Support\Str; @@ -31,9 +32,12 @@ class SelectFilter extends Filter } if ($this->queriesRelationships()) { + /** @var BelongsTo $relationship */ + $relationship = $this->getRelationship(); + return $query->whereRelation( $this->getRelationshipName(), - $this->getRelationship()->getOwnerKeyName(), + $relationship->getOwnerKeyName(), $data['value'], ); } @@ -91,10 +95,12 @@ class SelectFilter extends Filter protected function getRelationshipOptions(): array { + /** @var BelongsTo $relationship */ $relationship = $this->getRelationship(); + $displayColumnName = $this->getRelationshipDisplayColumnName(); - $relationshipQuery = $relationship->getRelated()->orderBy($displayColumnName); + $relationshipQuery = $relationship->getRelated()->query()->orderBy($displayColumnName); return $relationshipQuery ->pluck($displayColumnName, $relationship->getOwnerKeyName()) diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 9d76729f92..0a986fe5b2 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -7,4 +7,4 @@ parameters: - packages/spatie-laravel-tags-plugin/src - packages/spatie-laravel-translatable-plugin/src - packages/tables/src - level: 0 + level: 5