From 7116b89da3df3ef1635478284771ae7d2b4b5999 Mon Sep 17 00:00:00 2001 From: Daniel Weaver Date: Wed, 27 Aug 2025 14:01:55 -0400 Subject: [PATCH] Initial setup for lazy collections --- src/Actions/Export.php | 2 +- src/Exports/EntriesExport.php | 77 +++++++++++------------ src/Http/Controllers/ExportController.php | 4 +- 3 files changed, 38 insertions(+), 45 deletions(-) diff --git a/src/Actions/Export.php b/src/Actions/Export.php index bd644b0..6d9dd4b 100644 --- a/src/Actions/Export.php +++ b/src/Actions/Export.php @@ -44,7 +44,7 @@ public function download($items, $values): BinaryFileResponse|bool { $fileType = Arr::get($values, 'file_type', 'xlsx'); $firstItem = $items->first(); - $exporter = new EntriesExport($items, $values); + $exporter = new EntriesExport(collect($items)->lazy(), $values); if ($firstItem instanceof User) { $filename = 'users'; diff --git a/src/Exports/EntriesExport.php b/src/Exports/EntriesExport.php index 80a1290..11baf1e 100644 --- a/src/Exports/EntriesExport.php +++ b/src/Exports/EntriesExport.php @@ -2,9 +2,10 @@ namespace Doefom\StatamicExport\Exports; +use Generator; use Illuminate\Support\Arr; -use Illuminate\Support\Collection; -use Maatwebsite\Excel\Concerns\FromCollection; +use Illuminate\Support\LazyCollection; +use Maatwebsite\Excel\Concerns\FromGenerator; use Maatwebsite\Excel\Concerns\WithStyles; use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet; use Statamic\Contracts\Assets\Asset as AssetContract; @@ -14,9 +15,9 @@ use Statamic\Entries\Entry; use Statamic\Fields\Field; -class EntriesExport implements FromCollection, WithStyles +class EntriesExport implements FromGenerator, WithStyles { - public function __construct(public Collection $items, public array $config = []) {} + public function __construct(public Generator|LazyCollection $items, public array $config = []) {} /** * Get all keys from all items combined (unique). Then go through all keys and check for each item if the key @@ -24,25 +25,40 @@ public function __construct(public Collection $items, public array $config = []) * * If headers should be included, prepend those to the result array. */ - public function collection(): Collection + public function generator(): Generator { - // Get all unique keys from all items - $keys = $this->getAllKeysCombined($this->items); - - $result = []; - foreach ($keys as $key => $label) { - // Add the key to the collection if it doesn't exist - foreach ($this->items as $index => $item) { - $result[$index][$key] = $this->getItemValue($item, $key); + Sheet::class; + $excludedFields = Arr::get($this->config, 'excluded_fields', []); + $keysForBlueprint = []; + + foreach ($this->items as $index => $item) { + $blueprint = $item->blueprint(); + + if (!in_array($blueprint->handle(), $keysForBlueprint)) { + $keysForBlueprint[$blueprint->handle()] = $blueprint + ->fields() + ->all() + ->unique(fn (Field $field) => $field->handle()) + ->filter(fn (Field $field) => ! in_array($field->handle(), $excludedFields)) + ->filter(fn(Field $field) => ! $field->fieldtype() instanceof \Statamic\Fieldtypes\Hidden + && ! $field->fieldtype() instanceof \Statamic\Fieldtypes\Revealer + && ! $field->fieldtype() instanceof \Statamic\Fieldtypes\Html + && ! $field->fieldtype() instanceof \Statamic\Fieldtypes\Spacer) + ->mapWithKeys(fn ($field) => [$field->handle() => $field->display()]); } - } - // Add the headers to the collection - if (Arr::get($this->config, 'headers', true)) { - $result = Arr::prepend($result, $keys->toArray()); - } + // First row with headers + if ($index === 0 && Arr::get($this->config, 'headers', true)) { + yield $keysForBlueprint[$blueprint->handle()]->all(); + } + + $row = []; + foreach ($keysForBlueprint[$blueprint->handle()] as $key => $label) { + $row[$label] = $this->getItemValue($item, $key); + } - return collect($result); + yield $row; + } } private function getItemValue($item, $key): mixed @@ -230,27 +246,4 @@ private function toString(mixed $value): string return ''; } - - protected function getAllKeysCombined(Collection $items): Collection - { - $excludedFields = Arr::get($this->config, 'excluded_fields', []); - - return $items - // Map each item to its blueprint fields, handling both Entry and User types - ->map(fn (mixed $item) => $item->blueprint()->fields()->all()) - // Flatten the resulting collection to remove nested structures - ->flatten() - // Remove duplicate fields - ->unique(fn (Field $field) => $field->handle()) - // Remove fields that are excluded by the user. If there are no excluded fields, this will have no effect. - ->filter(fn (Field $field) => ! in_array($field->handle(), $excludedFields)) - // Filter out fields that are instances of certain field types - ->filter(function (Field $field) { - return ! $field->fieldtype() instanceof \Statamic\Fieldtypes\Hidden - && ! $field->fieldtype() instanceof \Statamic\Fieldtypes\Revealer - && ! $field->fieldtype() instanceof \Statamic\Fieldtypes\Html - && ! $field->fieldtype() instanceof \Statamic\Fieldtypes\Spacer; - }) - ->mapWithKeys(fn ($field) => [$field->handle() => $field->display()]); - } } diff --git a/src/Http/Controllers/ExportController.php b/src/Http/Controllers/ExportController.php index 960e6d8..df0b77f 100644 --- a/src/Http/Controllers/ExportController.php +++ b/src/Http/Controllers/ExportController.php @@ -46,13 +46,13 @@ public function export(ExportRequest $request) $includeHeaders = $request->input('headers', true); if ($request->input('type') === 'users') { - $items = User::all(); + $items = User::query()->lazy(); $filename = 'users'; } else { $collectionHandle = $request->input('collection_handle'); $items = Entry::query() ->where('collection', $collectionHandle) - ->get(); + ->lazy(); $filename = $collectionHandle; }