Importéry
Importér opisuje jeden typ záznamu: jeho model, polia, ako záznamy patria vlastníkovi, kto ich smie importovať, ako sa z riadka stane záznam a ako sa rozpozná existujúci. Rozšírte RoundlyConsulting\Imports\Importer a triedu zaregistrujte:
// config/imports.php
'importers' => [
App\Imports\AssetsImporter::class,
App\Imports\DocumentsImporter::class,
],
// or at runtime, e.g. in a service provider's boot()
use RoundlyConsulting\Imports\Facades\Imports;
Imports::register(AssetsImporter::class);Importéry sa vytvárajú cez kontajner — injektovanie do konštruktora funguje — a overia sa raz za proces, lenivo, pri prvom použití.
Metódy a predvolené hodnoty
| Metóda | Povinná | Predvolené | Poznámky |
|---|---|---|---|
model(): class-string<Model> | áno | — | Musí byť na spojení tabuliek balíka — jedna transakcia. |
fields(): list<Field> | áno | — | Aspoň jedno; názvy unikátne, ^[a-z][a-z0-9_]*$. |
ownership(): Ownership | áno | — | Bezpečné odmietnutie — pozrite Vlastníci a oprávnenia. |
authorize(?Authenticatable, Model $owner, ImportOperation): bool | áno | — | Pýta sa na View, Upload, Commit a Rollback. |
create(ImportRow, ImportContext): Model | áno | — | Vráťte uložený model v rámci vlastníka. Vrátiť existujúci záznam je v poriadku — v evidencii je ako linked a nikdy sa nevracia. |
update(Model, ImportRow, ImportContext): Model | pre Update | vyhodí výnimku | Jej prepísanie zapne režim Update. |
key(): string | nie | názov triedy bez Importer v snake case (AssetsImporter → assets, TeamMembersImporter → team_members) | ^[a-z][a-z0-9_.-]{0,63}$, unikátny. V produkcii ho uveďte — kľúčujú sa ním uložené mapovania aj vyhľadávanie cez HasImportSource. |
label(): string|array | nie | kľúč ako nadpis (Team Members) | Prekladový kľúč, text alebo ['en' => …, 'sk' => …]. |
naturalKey(): ?NaturalKey | nie | null | Bez kľúča nie je detekcia duplicít. |
modes(): list<DuplicateMode> | nie | Skip, Update (ak je prepísané update()), CreateNew | — |
defaultMode(): DuplicateMode | nie | Skip | Musí byť jedným z modes(). |
rollback(Model, ImportContext): RollbackOutcome | nie | zmaže → Deleted | Vráťte Handled (vrátené inak) alebo Kept. |
changeDetection(): ChangeDetection | nie | ChangeDetection::checksum() | Alebo ::updatedAt(), prípadne ::checksum(['name', …]). |
checksumAttributes(Model): array | nie | surové atribúty bez kľúča, časových pečiatok a deleted_at | Čo pokrýva kontrolný súčet. |
diff(Model, ImportRow): list<FieldChange> | nie | namapované, neprázdne polia (okrem odkazov) oproti rovnomenným atribútom | Náhľad aktualizácií — prázdna bunka nie je zmena. |
formats(): list<SourceFormat> | nie | CSV, XLSX | — |
limits(): Limits | nie | z konfigurácie | Limits::fromConfig()->withRows(50_000)->withColumns(200)->withFileSize(20_480)->withCellLength(255) — riadky najviac 100 000, stĺpce 16 384, dĺžka bunky orezaná na 1 … 1 048 576 a kontrolovaná pre každú bunku (too_long). |
Finálni pomocníci na každom importéri: kind() (ImportKind::Rows), supportsUpdate() (či je prepísané update()), field(string $name) a labelIn(?string $locale).
use RoundlyConsulting\Imports\Definition\{ChangeDetection, Limits};
use RoundlyConsulting\Imports\Enums\{DuplicateMode, SourceFormat};
public function key(): string { return 'assets'; } // declare it in production
public function label(): string|array { return ['en' => 'Assets', 'sk' => 'Majetok']; }
public function modes(): array { return [DuplicateMode::Skip, DuplicateMode::Update]; }
public function defaultMode(): DuplicateMode { return DuplicateMode::Update; }
public function formats(): array { return [SourceFormat::Xlsx]; }
public function limits(): Limits
{
return Limits::fromConfig()->withRows(50_000)->withColumns(200)->withFileSize(20_480)->withCellLength(255);
}
public function changeDetection(): ChangeDetection
{
return ChangeDetection::checksum(['name', 'serial_number']); // or ChangeDetection::updatedAt()
}V produkčných importéroch uveďte key(). Kľúčujú sa ním uložené mapovania aj vyhľadávanie cez HasImportSource, takže pri predvolenej hodnote premenovanie triedy premenuje aj kľúč a tie sa stratia. Názov triedy, z ktorého nevznikne platný kľúč — anonymná trieda, trieda s názvom len Importer — sa odmietne s uvedením triedy.
Prirodzené kľúče
use RoundlyConsulting\Imports\Definition\NaturalKey;
public function naturalKey(): ?NaturalKey
{
return NaturalKey::by('asset_tag')->then('serial_number'); // candidates, tried in order
}
NaturalKey::by('first_name', 'last_name')->caseInsensitive(); // one candidate, several fields- Najprv prebehne overenie: neplatný riadok sa nikdy nespáruje ani sa neráta ako duplicita.
- Zhodou riadka je prvý vyplnený kandidát, ktorý nájde záznam. Neskorší kandidát, ktorý nájde iný záznam, je conflicting_match; dva záznamy pre jeden kľúč sú ambiguous_match.
- Riadok, ktorý opakuje kľúč skoršieho platného riadka alebo sa spáruje s rovnakým záznamom, je duplicate_in_file.
- Kľúče sa porovnávajú aj so soft-deleted záznamami. Živý záznam má vždy prednosť, no kľúč, ktorý má len soft-deleted záznam, urobí riadok neplatným (matches_deleted_record, v každom režime) — váš unikátny index tento záznam stále pokrýva, takže vytvorenie by pri importe zlyhalo.
Callbacky create a update
public function create(ImportRow $row, ImportContext $context): Model
{
return Asset::create([...$row->attributes(), ...$context->ownerAttributes()]);
}
public function update(Model $asset, ImportRow $row, ImportContext $context): Model
{
return tap($asset)->update($row->changes());
}ImportRow ponúka dve mapy pripravené na zápis. attributes() obsahuje všetky hodnoty, pričom každé pole s odkazom sa zmení na svoj stĺpec storeAs() s kľúčom odkazovaného záznamu (['asset_tag' => 'AST-1', 'vendor_id' => 5]) — použite ju v create(). changes() je attributes() obmedzené na namapované polia s neprázdnou bunkou — použite ju v update().
Prázdne bunky v režime Update ponechajú uloženú hodnotu: exporty z iných nástrojov bežne nechávajú stĺpce prázdne a aktualizácie sa pri vrátení importu nikdy nevracajú. update(), ktoré zapisuje $row->changes(), sa týmto pravidlom riadi a predvolené diff() pri prázdnej bunke neukáže zmenu. Ak chcete hodnoty zámerne mazať, zapisujte v update() $row->attributes() a diff() prepíšte rovnako.
Čo dostanú callbacky
use RoundlyConsulting\Imports\Exceptions\ImportRowException;
public function create(ImportRow $row, ImportContext $context): Model
{
$row->number(); // the spreadsheet row number
$row->get('name'); // the coerced value
$row->has('purchased_on'); // the field is mapped
$row->filledIn('serial_number'); // mapped and not blank
$row->reference('vendor'); // the resolved Vendor model, or null
$row->raw('name'); // the cell as text
$row->cell('name'); // the Cell (type, value)
$row->all(); $row->filled(); $row->only('name'); $row->except('vendor');
$row->attributes(); // ['asset_tag' => 'AST-1', …, 'vendor_id' => 5]
$row->changes(); // attributes() of mapped, non-blank cells only
$context->owner; $context->actor; $context->mode; $context->locale; $context->timezone;
$context->sessionId; $context->batchId;
$context->query(Vendor::class); // a query constrained to the owner
$context->ownerAttributes(); // ['team_id' => 7] for Ownership::column('team_id')
if ($row->get('status') === 'retired') {
throw ImportRowException::because(__('app.imports.retired')); // fails this row only
}
return Asset::create([...$row->attributes(), ...$context->ownerAttributes()]);
}ImportContext sprístupňuje owner, actor, importer, sessionId, batchId, mode, locale a timezone (časové pásmo relácie — v ňom sa čítali časy zo súboru) ako verejné vlastnosti, k tomu query(string $model, ?Ownership $rule = null) — dopyt obmedzený na vlastníka pravidlom importéra alebo tým, ktoré odovzdáte — a ownerAttributes(). Výnimkou ImportRowException::because('…') necháte zlyhať jeden riadok s vlastnou preloženou správou.
Háčik rollback
Vrátenie importu zmaže vytvorené záznamy, ak sa odvtedy nezmenili. Ak chcete záznam vrátiť inak, prepíšte rollback():
use RoundlyConsulting\Imports\Enums\RollbackOutcome;
public function rollback(Model $record, ImportContext $context): RollbackOutcome
{
if ($record->accepted_at !== null) {
return RollbackOutcome::Kept; // leave it alone
}
$record->revoke(); // undone another way
return RollbackOutcome::Handled; // the default deletes it: RollbackOutcome::Deleted
}Pravidlá registrácie
Registrácia odmietne výnimkou InvalidImporterDefinitionException s názvom importéra a pravidla:
- chybný kľúč, dva importéry s rovnakým kľúčom, chýbajúce ownership, model na inom spojení;
- žiadne polia, chybné alebo duplicitné názvy polí, choice bez options, reference bez to() alebo by();
- polia prirodzeného kľúča, ktoré chýbajú alebo sú odkazmi;
- režim Update bez update(), predvolený režim mimo modes(), žiadne formáty;
- storeAs() na poli, ktoré nie je odkazom, alebo stĺpec storeAs(), ktorý nie je názvom stĺpca či je názvom iného poľa;
- pravidlá prítomnosti v databáze — reťazce exists: / unique:, Rule::exists() / Rule::unique() — pretože obchádzajú scope vlastníka a mohli by prezradiť dáta iného vlastníka. Namiesto nich použite Field::reference() a naturalKey().
Odkaz, ktorý zdedí pravidlo, aké jeho model nemôže niesť (team_id na modeli bez neho), vyhodí UnscopedQueryException — na poli uveďte ->ownedBy().
Zisťovanie pre UI
Imports::importers()->available($user, $team); // list<ImporterDescriptor> the user may view
Imports::importers()->describe('assets', 'sk'); // fields: label, type, required, options, example, help, aliases, format
Imports::importers()->all(); Imports::importers()->has('assets'); Imports::importers()->find('assets');
Imports::importer('assets')->modes();
Imports::importer('assets')->descriptor('sk');available() vráti len importéry, ktorých authorize() povolí View. Register vypíše:
php artisan imports:importers # key, label, kind, fields, modes, formatsOctane a dlho bežiace workery
Inštancie importérov sú v registri singletony kontajnera počas celej životnosti procesu — queue workera aj Octane workera. Do konštruktora importéra neinjektujte nič, čo patrí k požiadavke (aktuálneho používateľa, request, nájomcu): prenieslo by sa to do ďalšej požiadavky alebo úlohy. Všetko, čo sa týka importu, prichádza cez ImportContext a ImportRow.
Prejavte lásku k open source
Tento balík je zadarmo pod licenciou MIT. Ak vám šetrí čas, jednorazový príspevok alebo členstvo na Patreone nám pomôže ho ďalej udržiavať, testovať a dokumentovať.
Ďalšie spôsoby podpory vrátane kryptomienOdoslaním daru súhlasíte s našimi podmienkami prijímania darov.
Chcete to zabudovať do svojho produktu?
Naše balíky integrujeme do zákazkových Laravel a AI riešení. Napíšte nám, na čom pracujete, a ozveme sa do 48 hodín.