Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
64.71% covered (warning)
64.71%
11 / 17
33.33% covered (danger)
33.33%
3 / 9
CRAP
0.00% covered (danger)
0.00%
0 / 1
Definition
64.71% covered (warning)
64.71%
11 / 17
33.33% covered (danger)
33.33%
3 / 9
14.40
0.00% covered (danger)
0.00%
0 / 1
 model
n/a
0 / 0
n/a
0 / 0
0
 columns
n/a
0 / 0
n/a
0 / 0
0
 label
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 query
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 applyFilters
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 uniqueBy
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 defaults
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 beforeSave
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 resolvedColumns
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
2
 headings
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 sampleRow
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3namespace App\Services\ImportExport;
4
5use Illuminate\Database\Eloquent\Builder;
6use Illuminate\Database\Eloquent\Model;
7use Illuminate\Support\Str;
8
9/**
10 * Describes how one entity is exported to / imported from a spreadsheet.
11 * One subclass per entity, registered in config/importexport.php.
12 *
13 * A column is an array:
14 *   'key'       => spreadsheet header + model attribute
15 *   'label'     => header text (default: Title Case of key)
16 *   'required'  => bool (import)
17 *   'rules'     => Laravel validation rules for the cell (import)
18 *   'export'    => fn(Model $row) => string   (override the exported value)
19 *   'import'    => fn(mixed $value, array $row) => mixed   (override the stored value;
20 *                  return Definition::SKIP_COLUMN to leave the attribute untouched)
21 *   'sample'    => example value for the downloadable template
22 */
23abstract class Definition
24{
25    public const SKIP_COLUMN = "\0__skip__\0";
26
27    /** @return class-string<Model> */
28    abstract public function model(): string;
29
30    /** @return array<int, array<string, mixed>> */
31    abstract public function columns(): array;
32
33    /** Human label for menus / headings. */
34    public function label(): string
35    {
36        return Str::headline(class_basename(static::class));
37    }
38
39    /** Base query for export. Override to eager-load or scope. */
40    public function query(): Builder
41    {
42        return $this->model()::query()->latest('id');
43    }
44
45    /**
46     * Apply the same filters the list page uses so an export matches what the
47     * operator sees. $filters is typically the request query bag.
48     */
49    public function applyFilters(Builder $query, array $filters): Builder
50    {
51        return $query;
52    }
53
54    /** Column(s) that identify an existing row on import (upsert). Empty = always insert. */
55    public function uniqueBy(): array
56    {
57        return ['id'];
58    }
59
60    /** Attributes always set on an imported row (e.g. created_by). */
61    public function defaults(): array
62    {
63        return [];
64    }
65
66    /** Last-chance hook before a row is saved on import. */
67    public function beforeSave(Model $model, array $row): void
68    {
69        //
70    }
71
72    /* ---- resolved helpers ---------------------------------------------- */
73
74    /** @return array<string, array<string, mixed>> key => normalised column def */
75    public function resolvedColumns(): array
76    {
77        $out = [];
78        foreach ($this->columns() as $col) {
79            $key = $col['key'];
80            $out[$key] = $col + [
81                'label' => Str::headline($key),
82                'required' => false,
83                'rules' => null,
84            ];
85        }
86
87        return $out;
88    }
89
90    public function headings(): array
91    {
92        return array_column($this->resolvedColumns(), 'label');
93    }
94
95    public function sampleRow(): array
96    {
97        return array_map(fn ($c) => $c['sample'] ?? '', $this->resolvedColumns());
98    }
99}