Lifecycle Hooks
Tapix exposes lifecycle hooks that let you run custom logic at specific points during the import process. These hooks are defined as methods on your importer class.
Hook Overview
The import lifecycle flows through these stages:
- Access check --
canImport()determines whether the user can access the importer. - Pre-import --
beforeImport()runs once before processing begins. - Pre-validation --
beforeValidation()runs before column validation starts. - Per-row processing -- For each row:
prepareForSave()transforms data,beforeSave()runs before persistence, thenafterSave()runs after persistence. - Post-import --
afterImport()runs once after all rows are processed.
Hook Reference
canImport(): bool
Controls access to the importer. Called before the import wizard is shown. Return false to prevent the user from accessing this importer.
Defaults to true.
public function canImport(): bool
{
return auth()->user()->hasPermissionTo('import contacts');
}
beforeImport(Import $import): void
Called once before the import begins processing rows. Use this to set up state, log the start of an import, or send notifications.
use Tapix\Core\Models\Import;
public function beforeImport(Import $import): void
{
logger()->info("Starting import #{$import->id} with {$import->total_rows} rows");
}
beforeValidation(Import $import): void
Called before column validation starts. Use this to prepare validation context or modify import settings before values are checked.
use Tapix\Core\Models\Import;
public function beforeValidation(Import $import): void
{
// Pre-load lookup tables for custom validation rules
cache()->put(
"import:{$import->id}:valid-codes",
ProductCode::pluck('code')->toArray(),
now()->addHour(),
);
}
prepareForSave(array $data, ?Model $existing, array &$context): array
Transforms the validated row data before it is saved to the database. This hook runs for every row.
Parameters:
$data-- The validated row data as a key-value array.$existing-- The matched Eloquent model instance when updating an existing record, ornullwhen creating a new record.$context-- A reference array containing import metadata. Includestenant_idandcreator_id.
The base implementation strips the id field from the data array. Always return the modified data array.
$context array contains tenant_id (the current tenant's ID when multi-tenancy is enabled) and creator_id (the authenticated user's ID who initiated the import). Use these to scope records and track ownership.use Illuminate\Database\Eloquent\Model;
public function prepareForSave(array $data, ?Model $existing, array &$context): array
{
// Set tenant scoping
$data['tenant_id'] = $context['tenant_id'];
// Set default values for new records
if (! $existing) {
$data['status'] = 'pending';
$data['created_by'] = $context['creator_id'];
}
// Normalize data
$data['email'] = strtolower($data['email'] ?? '');
return $data;
}
beforeSave(Model $model, array $data): void
Called before each row is persisted to the database. The model instance has already been populated with the prepared data but has not been saved yet.
use Illuminate\Database\Eloquent\Model;
public function beforeSave(Model $model, array $data): void
{
if (! $model->exists) {
$model->uuid = (string) Str::uuid();
}
}
afterSave(Model $model, array $data): void
Called after each row is persisted to the database. The model has been saved and has a primary key. Use this for post-save side effects like syncing related data or dispatching events.
use Illuminate\Database\Eloquent\Model;
public function afterSave(Model $model, array $data): void
{
// Sync tags if present in the import data
if (! empty($data['tags'])) {
$tagIds = Tag::whereIn('name', explode(',', $data['tags']))->pluck('id');
$model->tags()->sync($tagIds);
}
// Dispatch an event for downstream processing
ContactImported::dispatch($model);
}
afterImport(Import $import): void
Called once after all rows have been processed. Use this for summary actions like sending a completion notification, updating caches, or running aggregate calculations.
use Tapix\Core\Models\Import;
public function afterImport(Import $import): void
{
// Notify the user
$import->user->notify(new ImportCompleteNotification($import));
// Rebuild search index
Contact::where('import_id', $import->id)
->searchable();
logger()->info("Import #{$import->id} complete: {$import->processed_rows} rows processed");
}
matchableFields(): array
Defines which fields are used to match imported rows against existing records in the database. When a match is found, the existing record is updated rather than creating a duplicate.
Defaults to [MatchableField::id()].
use Tapix\Core\Matching\MatchableField;
public function matchableFields(): array
{
return [
MatchableField::id(),
MatchableField::email('email'),
MatchableField::name(),
];
}
Practical Example
Combining multiple hooks in a single importer:
namespace App\Importers;
use App\Events\ContactImported;
use App\Models\Contact;
use App\Notifications\ImportCompleteNotification;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Str;
use Tapix\Core\Fields\ImportField;
use Tapix\Core\Fields\ImportFieldCollection;
use Tapix\Core\Importing\BaseImporter;
use Tapix\Core\Models\Import;
final class ContactImporter extends BaseImporter
{
public function model(): string
{
return Contact::class;
}
public function fields(): ImportFieldCollection
{
return ImportFieldCollection::make([
ImportField::make('name')->required(),
ImportField::make('email')->required(),
]);
}
public function canImport(): bool
{
return auth()->user()->can('import contacts');
}
public function beforeImport(Import $import): void
{
logger()->info("Starting contact import #{$import->id}");
}
public function prepareForSave(array $data, ?Model $existing, array &$context): array
{
$data['tenant_id'] = $context['tenant_id'];
$data['email'] = strtolower($data['email'] ?? '');
return $data;
}
public function beforeSave(Model $model, array $data): void
{
if (! $model->exists) {
$model->uuid = (string) Str::uuid();
}
}
public function afterSave(Model $model, array $data): void
{
ContactImported::dispatch($model);
}
public function afterImport(Import $import): void
{
$import->user->notify(new ImportCompleteNotification($import));
}
}