Hello all,
I made filament custom page with a form and some logic. Please find the code of the page below.
The form is working properly.
However, when I set some test (code below), I have an error from FilamentPHP and I have no idea how to debug it (seems it comes from FilamentPHP itselft). Any idea?
I use Filament V3.
The error:
FAILED Tests\Feature\ArtistEntityTest > can edit entity TypeError
App\Filament\Artist\Pages\EditEntity::form(): Argument #1 ($form) must be of type Filament\Forms\Form, Filament\Infolists\Infolist given, called in /var/www/html/vendor/filament/infolists/src/Concerns/InteractsWithInfolists.php on line 54
at vendor/filament/forms/src/Concerns/InteractsWithForms.php:358
354▕ 'form',
355▕ ];
356▕ }
357▕
➜ 358▕ public function form(Form $form): Form
359▕ {
360▕ return $form
361▕ ->schema($this->getFormSchema())
362▕ ->model($this->getFormModel())
+10 vendor frames
11 tests/Feature/ArtistEntityTest.php:34
Tests: 1 failed, 2 passed (2 assertions)
Duration: 1.92s
My custom page code:
<?php
namespace App\Filament\Artist\Pages;
use App\Filament\Common\Components;
use App\Models\Artist;
use App\Models\Entity;
use Exception;
use Filament\Forms\Form;
use Filament\Pages\Page;
use Filament\Actions\Action;
use Filament\Facades\Filament;
use Filament\Forms\Components\Section;
use Illuminate\Database\Eloquent\Model;
use Filament\Forms\Get;
use Filament\Notifications\Notification;
use Illuminate\Contracts\Support\Htmlable;
use Filament\Forms\Contracts\HasForms;
use Filament\Forms\Concerns\InteractsWithForms;
class EditEntity extends Page implements HasForms
{
use InteractsWithForms;
protected static ?string $navigationIcon = 'heroicon-o-currency-euro';
protected static string $view = 'filament.artist.pages.edit-entity';
public static function getNavigationGroup(): string
{
return __('admin.parameters');
}
public function getTitle(): string | Htmlable
{
return __('admin.my_billing');
}
public static function getNavigationLabel(): string
{
return __('admin.my_billing');
}
public ?array $entityData = [];
public Entity $entity;
public Artist $artist;
public function mount($artist = null): void
{
$this->artist = $artist ?? Filament::getTenant();
$this->entity = $this->getEntity();
$this->fillForms();
}
protected function getForms(): array
{
return [
'editEntityForm'
];
}
public function editEntityForm(Form $form): Form
{
return $form
->schema([
Section::make(__('admin.profile_information'))
->aside()
->description(__('admin.profile_information_description'))
->schema([
Components::getEntityLegalForm('legal_form')->live()->required(),
Components::getEntityAssociationFields()->visible(fn (Get $get): string => $get('legal_form') == 'association'),
Components::getEntityCompanyFields()->visible(fn (Get $get): string => $get('legal_form') == 'company'),
Components::getEntitySelfEmployedFields()->visible(fn (Get $get): string => $get('legal_form') == 'self_employed'),
Components::getEntityTemporaryWorkerFields()->visible(fn (Get $get): string => $get('legal_form') == 'temporary_worker'),
Components::getEntityTemporaryWorkersGroupFields()->visible(fn (Get $get): string => $get('legal_form') == 'temporary_workers_group')
]),
])
->model($this->entity)
->statePath('entityData');
}
protected function fillForms(): void
{
$data = $this->entity ? $this->entity->attributesToArray() : [];
$this->editEntityForm->fill($data);
}
protected function getUpdateOrCreateEntityFormActions(): array
{
return [
Action::make('updateEntityAction')
->label(__('filament-panels::pages/auth/edit-profile.form.actions.save.label'))
->submit('updateOrCreateEntity')
];
}
public function updateOrCreateEntity(): void
{
try {
$data = $this->editEntityForm->getState();
$this->handleRecordCreateOrUpdate($this->entity, $data);
} catch (Halt $exception) {
return;
}
$this->sendSuccessNotification();
}
protected function getEntity(): ?Entity
{
$entity = Entity::find($this->artist->entity_id) ?? Entity::make();
if ($entity) {
$this->authorize('view', $entity);
}
if ($entity && !$entity instanceof Model) {
throw new Exception('The entity object must be an Eloquent model to allow the entity page to update it.');
}
return $entity;
}
protected function handleRecordCreateOrUpdate(Model $record, array $data): Model
{
if (!$record->exists) {
$this->authorize('create', Entity::class);
$this->authorize('update', $this->artist);
$record->fill($data);
$record->save();
$this->artist->entity()->associate($record);
$this->artist->save();
return $record;
}
$this->authorize('update', $record);
$record->update($data);
return $record;
}
private function sendSuccessNotification(): void
{
Notification::make()
->success()
->title(__('filament-panels::pages/auth/edit-profile.notifications.saved.title'))
->send();
}
}
My php unit test code:
/** @test */
public function can_edit_entity(): void
{
$user = User::where('email', '[email protected]')->first();
$artist = $user->artists->first();
Livewire::actingAs($user)
->test(EditEntity::class, ['artist' => $artist])
->fillForm([
'legal_form' => 'association',
'name' => 'Association name',
'identification_number' => 'W123456789'
])
->call('updateOrCreateEntity')
->assertHasNoFormErrors();
$this->assertDatabaseHas(Entity::class, [
'legal_form' => 'association',
'name' => 'Association name',
'identification_number' => 'W123456789'
]);
}
My components code:
<?php
namespace App\Filament\Common;
use Filament\Forms\Components\Component;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\Hidden;
use Illuminate\Validation\Rules\Unique;
use Filament\Forms\Components\Wizard\Step;
use Filament\Forms\Components\Actions\Action;
use Filament\Support\Enums\IconPosition;
use App\Models\Address;
use App\Models\Entity;
use Filament\Forms\Get;
use Filament\Forms\Components\Fieldset;
use Filament\Forms\Components\FileUpload;
use Filament\Forms\Components\Grid;
use Filament\Forms\Components\Repeater;
use Filament\Forms\Components\Section;
use Livewire\Features\SupportFileUploads\TemporaryUploadedFile;
class Components
{
public static function getFirstName($field = null)
{
return TextInput::make($field)->minLength(1)->maxLength(100)->label(__('admin.first_name'));
}
public static function getLastName($field = null)
{
return TextInput::make($field)->minLength(1)->maxLength(100)->label(__('admin.last_name'));
}
public static function getEntitySiret($field = null)
{
return TextInput::make($field)->regex('/^\d{14}$/i')->helperText(__('admin.company_identification_number_helper'))->label(__('admin.company_identification_number'));
}
public static function getEntityRna($field = null)
{
return TextInput::make($field)->regex('/^W\d{9}$/i')->helperText(__('admin.association_identification_number_helper'))->label(__('admin.association_identification_number'));
}
public static function getEntityVat($field = null)
{
return TextInput::make($field)->regex('/^(AT)?U\d{8}|(BE)?0\d{9}|(BG)?\d{9,10}|(CY)?\d{8}L|(CZ)?\d{8,10}|(DE)?\d{9}|(DK)?\d{8}|(EE)?\d{9}|(EL|GR)?\d{9}|(ES)?[A-Z]\d{7}[A-Z]|(FI)?\d{8}|(FR)?\d{11}|(GB)?\d{12}|(GB)?GD\d{3}|(GB)?HA\d{3}|(HR)?\d{11}|(HU)?\d{8}|(IE)?\d{7}[A-Z]{1,2}|(IE)?[A-Z]\d{5}[A-Z]|(IT)?\d{11}|(LT)?(\d{9}|\d{12})|(LU)?\d{8}|(LV)?\d{11}|(MT)?\d{8}|(NL)?\d{9}B\d{2}|(PL)?\d{10}|(PT)?\d{9}|(RO)?\d{2,10}|(SE)?\d{12}|(SI)?\d{8}|(SK)?\d{10}$/i')->label(__('admin.vat_number'));
}
public static function getEntityName($field = null)
{
return TextInput::make($field)->label(__('admin.entity_name'));
}
public static function getEmail($field = null)
{
return TextInput::make($field)->email()->label(__('admin.email'));
}
public static function getEntityLegalForm($field = null)
{
return Select::make($field)
->options([
'association' => __('admin.association'),
'company' => __('admin.company'),
'temporary_worker' => __('admin.temporary_worker'),
'temporary_workers_group' => __('admin.temporary_workers_group'),
'self_employed' => __('admin.self_employed')
])
->default('company')
->label(__('admin.entity_type'));
}
public static function getEntityTemporaryWorkers($field = null)
{
return Repeater::make($field)
->label(__('admin.temporary_workers'))
->schema([
self::getFirstName('first_name')->required(),
self::getLastName('last_name')->required(),
self::getEmail('email')->required()
])
->reorderableWithDragAndDrop(false)
->maxItems(15)
->addAction(
fn (Action $action) => $action->label(__('admin.add_temporary_worker')),
)
->deleteAction(
fn (Action $action) => $action->requiresConfirmation(),
)
->columns(3);
}
public static function getEntityAssociationFields()
{
return Grid::make(1)
->schema([
self::getEntityName('name')->required(),
self::getEntityRna('identification_number')->required(),
]);
}
public static function getEntityCompanyFields()
{
return Grid::make(1)
->schema([
self::getEntityName('name')->required(),
self::getEntitySiret('identification_number')->required(),
self::getEntityVat('vat')->nullable(),
]);
}
public static function getEntitySelfEmployedFields()
{
return Grid::make(1)
->schema([
self::getFirstName('first_name')->required(),
self::getLastName('last_name')->required(),
self::getEntitySiret('identification_number')->required(),
self::getEntityVat('vat')->nullable(),
]);
}
public static function getEntityTemporaryWorkerFields()
{
return Grid::make(1)
->schema([
self::getFirstName('first_name')->required(),
self::getLastName('last_name')->required(),
self::getEmail('email')->required(),
]);
}
public static function getEntityTemporaryWorkersGroupFields()
{
return Grid::make(1)
->schema([
Fieldset::make('Label')
->label(__('admin.main_temporary_worker'))
->schema([
self::getFirstName('first_name')->required(),
self::getLastName('last_name')->required(),
self::getEmail('email')->required(),
])->columns(3),
Fieldset::make('Label')
->label(__('admin.co_temporary_workers'))
->schema([
self::getEntityTemporaryWorkers('extra_data.temporary_workers')->visible(fn (Get $get): string => $get('legal_form') == 'temporary_workers_group')->required()
])->columns(1),
]);
}
}