*/ protected array $cachedColumns; /** * @var array */ protected array $originalData; /** * @var array */ protected array $data; protected ?Model $record; protected static ?string $model = null; /** * @param array $columnMap * @param array $options */ public function __construct( protected Import $import, protected array $columnMap, protected array $options, ) {} /** * @param array $data */ public function __invoke(array $data): void { $this->originalData = $this->data = $data; $this->record = null; $this->remapData(); $this->castData(); $this->record = $this->resolveRecord(); if (! $this->record) { return; } $recordExists = $this->record->exists; if (! $recordExists) { $this->checkColumnMappingRequirementsForNewRecords(); } $this->callHook('beforeValidate'); $this->validateData(); $this->callHook('afterValidate'); $this->callHook('beforeFill'); $this->fillRecord(); $this->callHook('afterFill'); $this->callHook('beforeSave'); $this->callHook($recordExists ? 'beforeUpdate' : 'beforeCreate'); $this->saveRecord(); $this->callHook('afterSave'); $this->callHook($recordExists ? 'afterUpdate' : 'afterCreate'); } public function remapData(): void { $data = $this->data; foreach ($this->getCachedColumns() as $column) { $columnName = $column->getName(); if (blank($this->columnMap[$columnName] ?? null)) { continue; } $rowColumnName = $this->columnMap[$columnName]; if (! array_key_exists($rowColumnName, $this->data)) { continue; } $data[$columnName] = $this->data[$rowColumnName]; } $this->data = $data; } /** * @throws ValidationException */ public function checkColumnMappingRequirementsForNewRecords(): void { foreach ($this->getCachedColumns() as $column) { $columnName = $column->getName(); if (filled($this->columnMap[$columnName] ?? null)) { continue; } if (! $column->isMappingRequiredForNewRecordsOnly()) { continue; } Validator::validate( data: [$columnName => null], rules: [$columnName => ['required']], messages: ["{$columnName}.required" => __('filament-actions::import.failure_csv.column_mapping_required_for_new_record')], attributes: [$columnName => $column->getLabel()], ); } } public function castData(): void { foreach ($this->getCachedColumns() as $column) { $columnName = $column->getName(); if (! array_key_exists($columnName, $this->data)) { continue; } $this->data[$columnName] = $column->castState( $this->data[$columnName], $this->options, ); } } public function resolveRecord(): ?Model { $keyName = app(static::getModel())->getKeyName(); $keyColumnName = $this->columnMap[$keyName] ?? $keyName; return static::getModel()::find($this->data[$keyColumnName]); } /** * @throws ValidationException */ public function validateData(): void { Validator::validate( $this->data, $this->getValidationRules(), $this->getValidationMessages(), $this->getValidationAttributes(), ); } /** * @return array> */ public function getValidationRules(): array { $rules = []; foreach ($this->getCachedColumns() as $column) { $columnName = $column->getName(); if (blank($this->columnMap[$columnName] ?? null)) { continue; } $rules[$columnName] = $column->getDataValidationRules(); if ( $column->isArray() && count($nestedRecursiveRules = $column->getNestedRecursiveDataValidationRules()) ) { $rules["{$columnName}.*"] = $nestedRecursiveRules; } } return $rules; } /** * @return array */ public function getValidationMessages(): array { return []; } /** * @return array */ public function getValidationAttributes(): array { $attributes = []; foreach ($this->getCachedColumns() as $column) { $columnName = $column->getName(); if (blank($this->columnMap[$columnName] ?? null)) { continue; } $validationAttribute = $column->getValidationAttribute(); if (blank($validationAttribute)) { continue; } $attributes[$columnName] = $validationAttribute; } return $attributes; } public function fillRecord(): void { foreach ($this->getCachedColumns() as $column) { $columnName = $column->getName(); if (blank($this->columnMap[$columnName] ?? null)) { continue; } if (! array_key_exists($columnName, $this->data)) { continue; } $state = $this->data[$columnName]; if (blank($state) && $column->isBlankStateIgnored()) { continue; } $column->fillRecord($state); } } public function saveRecord(): void { $this->record->save(); } /** * @return array */ abstract public static function getColumns(): array; /** * @return array */ public static function getOptionsFormComponents(): array { return []; } /** * @return class-string */ public static function getModel(): string { return static::$model ?? (string) str(class_basename(static::class)) ->beforeLast('Importer') ->prepend('App\\Models\\'); } abstract public static function getCompletedNotificationBody(Import $import): string; public static function getCompletedNotificationTitle(Import $import): string { return __('filament-actions::import.notifications.completed.title'); } /** * @return array */ public function getJobMiddleware(): array { return [ (new WithoutOverlapping("import{$this->import->getKey()}"))->expireAfter(600), ]; } public function getJobRetryUntil(): ?CarbonInterface { return now()->addDay(); } /** * @return array */ public function getJobTags(): array { return ["import{$this->import->getKey()}"]; } public function getJobQueue(): ?string { return null; } public function getJobConnection(): ?string { return null; } public function getJobBatchName(): ?string { return null; } /** * @return array */ public function getCachedColumns(): array { return $this->cachedColumns ??= array_map( fn (ImportColumn $column) => $column->importer($this), static::getColumns(), ); } public function getRecord(): ?Model { return $this->record; } /** * @return array */ public function getOriginalData(): array { return $this->originalData; } /** * @return array */ public function getData(): array { return $this->data; } /** * @return array */ public function getOptions(): array { return $this->options; } protected function callHook(string $hook): void { if (! method_exists($this, $hook)) { return; } $this->{$hook}(); } public function getImport(): Import { return $this->import; } }