Add more type safety

This commit is contained in:
Dan Harrin
2021-12-23 13:09:17 +00:00
parent 69e68b2ae8
commit be1461d0e3
49 changed files with 430 additions and 180 deletions
+24 -24
View File
@@ -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
+24
View File
@@ -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
+1 -1
View File
@@ -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
@@ -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');
}
+6 -4
View File
@@ -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
@@ -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
{
@@ -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;
}
}
@@ -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;
+11 -2
View File
@@ -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
@@ -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;
@@ -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;
@@ -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')
@@ -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;
@@ -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;
}
}
@@ -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');
@@ -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()
@@ -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()
@@ -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;
}
}
@@ -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();
+6 -2
View File
@@ -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',
};
+3
View File
@@ -22,6 +22,9 @@
"spatie/laravel-package-tools": "^1.9"
},
"autoload": {
"files": [
"src/helpers.php"
],
"psr-4": {
"Filament\\Forms\\": "src"
}
@@ -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;
}
}
@@ -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, [
@@ -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, [
+4 -2
View File
@@ -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);
@@ -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;
}
@@ -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}";
@@ -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(
@@ -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;
}
}
+4 -2
View File
@@ -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);
@@ -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) {
@@ -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;
});
}
}
+44
View File
@@ -0,0 +1,44 @@
<?php
namespace Filament\Forms;
if (! function_exists('Filament\Forms\array_move_after')) {
function array_move_after(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;
}
}
if (! function_exists('Filament\Forms\array_move_before')) {
function array_move_before(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;
}
}
@@ -3,8 +3,12 @@
namespace Filament\Forms\Components;
use Closure;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Filesystem\FilesystemAdapter;
use League\Flysystem\AwsS3v3\AwsS3Adapter;
use Spatie\MediaLibrary\HasMedia;
use Spatie\MediaLibrary\InteractsWithMedia;
use Spatie\MediaLibrary\MediaCollections\FileAdder;
use Spatie\MediaLibrary\MediaCollections\Models\Media;
use SplFileInfo;
@@ -89,16 +93,24 @@ class SpatieMediaLibraryFileUpload extends FileUpload
protected function handleUpload($file)
{
if (! ($model = $this->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();
}
}
@@ -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
@@ -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;
@@ -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')
@@ -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();
}
@@ -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',
};
+6 -1
View File
@@ -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(
@@ -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;
@@ -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;
@@ -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;
+14 -6
View File
@@ -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
@@ -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;
+3 -1
View File
@@ -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;
@@ -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())
+8 -2
View File
@@ -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())
+1 -1
View File
@@ -7,4 +7,4 @@ parameters:
- packages/spatie-laravel-tags-plugin/src
- packages/spatie-laravel-translatable-plugin/src
- packages/tables/src
level: 0
level: 5