NovinkaZverejnili sme 50+ Laravel balíkov ako open source
Custom AI apps, agents and automation — Roundly ConsultingRoundly
Všetky balíky

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ódaPovinná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): Modelpre Updatevyhodí výnimkuJej prepísanie zapne režim Update.
key(): stringniená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|arrayniekľúč ako nadpis (Team Members)Prekladový kľúč, text alebo ['en' => …, 'sk' => …].
naturalKey(): ?NaturalKeynienullBez kľúča nie je detekcia duplicít.
modes(): list<DuplicateMode>nieSkip, Update (ak je prepísané update()), CreateNew—
defaultMode(): DuplicateModenieSkipMusí byť jedným z modes().
rollback(Model, ImportContext): RollbackOutcomeniezmaže → DeletedVráťte Handled (vrátené inak) alebo Kept.
changeDetection(): ChangeDetectionnieChangeDetection::checksum()Alebo ::updatedAt(), prípadne ::checksum(['name', …]).
checksumAttributes(Model): arrayniesurové atribúty bez kľúča, časových pečiatok a deleted_atČo pokrýva kontrolný súčet.
diff(Model, ImportRow): list<FieldChange>nienamapované, neprázdne polia (okrem odkazov) oproti rovnomenným atribútomNáhľad aktualizácií — prázdna bunka nie je zmena.
formats(): list<SourceFormat>nieCSV, XLSX—
limits(): Limitsniez konfigurácieLimits::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, formats

Octane 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 kryptomien

Odoslaní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.