Sheet.php 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807
  1. <?php
  2. namespace Maatwebsite\Excel;
  3. use Closure;
  4. use Illuminate\Contracts\Support\Arrayable;
  5. use Illuminate\Support\Collection;
  6. use Illuminate\Support\LazyCollection;
  7. use Maatwebsite\Excel\Concerns\FromArray;
  8. use Maatwebsite\Excel\Concerns\FromCollection;
  9. use Maatwebsite\Excel\Concerns\FromGenerator;
  10. use Maatwebsite\Excel\Concerns\FromIterator;
  11. use Maatwebsite\Excel\Concerns\FromQuery;
  12. use Maatwebsite\Excel\Concerns\FromView;
  13. use Maatwebsite\Excel\Concerns\OnEachRow;
  14. use Maatwebsite\Excel\Concerns\ShouldAutoSize;
  15. use Maatwebsite\Excel\Concerns\SkipsEmptyRows;
  16. use Maatwebsite\Excel\Concerns\ToArray;
  17. use Maatwebsite\Excel\Concerns\ToCollection;
  18. use Maatwebsite\Excel\Concerns\ToModel;
  19. use Maatwebsite\Excel\Concerns\WithCalculatedFormulas;
  20. use Maatwebsite\Excel\Concerns\WithCharts;
  21. use Maatwebsite\Excel\Concerns\WithChunkReading;
  22. use Maatwebsite\Excel\Concerns\WithColumnFormatting;
  23. use Maatwebsite\Excel\Concerns\WithColumnLimit;
  24. use Maatwebsite\Excel\Concerns\WithColumnWidths;
  25. use Maatwebsite\Excel\Concerns\WithCustomChunkSize;
  26. use Maatwebsite\Excel\Concerns\WithCustomStartCell;
  27. use Maatwebsite\Excel\Concerns\WithCustomValueBinder;
  28. use Maatwebsite\Excel\Concerns\WithDrawings;
  29. use Maatwebsite\Excel\Concerns\WithEvents;
  30. use Maatwebsite\Excel\Concerns\WithFormatData;
  31. use Maatwebsite\Excel\Concerns\WithHeadings;
  32. use Maatwebsite\Excel\Concerns\WithMappedCells;
  33. use Maatwebsite\Excel\Concerns\WithMapping;
  34. use Maatwebsite\Excel\Concerns\WithProgressBar;
  35. use Maatwebsite\Excel\Concerns\WithStrictNullComparison;
  36. use Maatwebsite\Excel\Concerns\WithStyles;
  37. use Maatwebsite\Excel\Concerns\WithTitle;
  38. use Maatwebsite\Excel\Concerns\WithValidation;
  39. use Maatwebsite\Excel\Events\AfterSheet;
  40. use Maatwebsite\Excel\Events\BeforeSheet;
  41. use Maatwebsite\Excel\Exceptions\ConcernConflictException;
  42. use Maatwebsite\Excel\Exceptions\RowSkippedException;
  43. use Maatwebsite\Excel\Exceptions\SheetNotFoundException;
  44. use Maatwebsite\Excel\Files\TemporaryFileFactory;
  45. use Maatwebsite\Excel\Helpers\ArrayHelper;
  46. use Maatwebsite\Excel\Helpers\CellHelper;
  47. use Maatwebsite\Excel\Imports\EndRowFinder;
  48. use Maatwebsite\Excel\Imports\HeadingRowExtractor;
  49. use Maatwebsite\Excel\Imports\ModelImporter;
  50. use Maatwebsite\Excel\Validators\RowValidator;
  51. use PhpOffice\PhpSpreadsheet\Cell\Cell as SpreadsheetCell;
  52. use PhpOffice\PhpSpreadsheet\Chart\Chart;
  53. use PhpOffice\PhpSpreadsheet\IOFactory;
  54. use PhpOffice\PhpSpreadsheet\Reader\Html;
  55. use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
  56. use PhpOffice\PhpSpreadsheet\Spreadsheet;
  57. use PhpOffice\PhpSpreadsheet\Worksheet\BaseDrawing;
  58. use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
  59. /** @mixin Worksheet */
  60. class Sheet
  61. {
  62. use DelegatedMacroable, HasEventBus;
  63. /**
  64. * @var int
  65. */
  66. protected $chunkSize;
  67. /**
  68. * @var TemporaryFileFactory
  69. */
  70. protected $temporaryFileFactory;
  71. /**
  72. * @var object
  73. */
  74. protected $exportable;
  75. /**
  76. * @var Worksheet
  77. */
  78. private $worksheet;
  79. /**
  80. * @param Worksheet $worksheet
  81. */
  82. public function __construct(Worksheet $worksheet)
  83. {
  84. $this->worksheet = $worksheet;
  85. $this->chunkSize = config('excel.exports.chunk_size', 100);
  86. $this->temporaryFileFactory = app(TemporaryFileFactory::class);
  87. }
  88. /**
  89. * @param Spreadsheet $spreadsheet
  90. * @param string|int $index
  91. * @return Sheet
  92. *
  93. * @throws \PhpOffice\PhpSpreadsheet\Exception
  94. * @throws SheetNotFoundException
  95. */
  96. public static function make(Spreadsheet $spreadsheet, $index)
  97. {
  98. if (is_numeric($index)) {
  99. return self::byIndex($spreadsheet, $index);
  100. }
  101. return self::byName($spreadsheet, $index);
  102. }
  103. /**
  104. * @param Spreadsheet $spreadsheet
  105. * @param int $index
  106. * @return Sheet
  107. *
  108. * @throws \PhpOffice\PhpSpreadsheet\Exception
  109. * @throws SheetNotFoundException
  110. */
  111. public static function byIndex(Spreadsheet $spreadsheet, int $index): Sheet
  112. {
  113. if (!isset($spreadsheet->getAllSheets()[$index])) {
  114. throw SheetNotFoundException::byIndex($index, $spreadsheet->getSheetCount());
  115. }
  116. return new static($spreadsheet->getSheet($index));
  117. }
  118. /**
  119. * @param Spreadsheet $spreadsheet
  120. * @param string $name
  121. * @return Sheet
  122. *
  123. * @throws SheetNotFoundException
  124. */
  125. public static function byName(Spreadsheet $spreadsheet, string $name): Sheet
  126. {
  127. if (!$spreadsheet->sheetNameExists($name)) {
  128. throw SheetNotFoundException::byName($name);
  129. }
  130. return new static($spreadsheet->getSheetByName($name));
  131. }
  132. /**
  133. * @param object $sheetExport
  134. *
  135. * @throws \PhpOffice\PhpSpreadsheet\Exception
  136. */
  137. public function open($sheetExport)
  138. {
  139. $this->exportable = $sheetExport;
  140. if ($sheetExport instanceof WithCustomValueBinder) {
  141. SpreadsheetCell::setValueBinder($sheetExport);
  142. }
  143. if ($sheetExport instanceof WithEvents) {
  144. $this->registerListeners($sheetExport->registerEvents());
  145. }
  146. $this->raise(new BeforeSheet($this, $this->exportable));
  147. if ($sheetExport instanceof WithTitle) {
  148. $title = $sheetExport->title();
  149. $title = str_replace(['*', ':', '/', '\\', '?', '[', ']'], '', $title);
  150. if (StringHelper::countCharacters($title) > Worksheet::SHEET_TITLE_MAXIMUM_LENGTH) {
  151. $title = StringHelper::substring($title, 0, Worksheet::SHEET_TITLE_MAXIMUM_LENGTH);
  152. }
  153. $this->worksheet->setTitle($title);
  154. }
  155. if (($sheetExport instanceof FromQuery || $sheetExport instanceof FromCollection || $sheetExport instanceof FromArray) && $sheetExport instanceof FromView) {
  156. throw ConcernConflictException::queryOrCollectionAndView();
  157. }
  158. if (!$sheetExport instanceof FromView && $sheetExport instanceof WithHeadings) {
  159. if ($sheetExport instanceof WithCustomStartCell) {
  160. $startCell = $sheetExport->startCell();
  161. }
  162. $this->append(
  163. ArrayHelper::ensureMultipleRows($sheetExport->headings()),
  164. $startCell ?? null,
  165. $this->hasStrictNullComparison($sheetExport)
  166. );
  167. }
  168. if ($sheetExport instanceof WithCharts) {
  169. $this->addCharts($sheetExport->charts());
  170. }
  171. }
  172. /**
  173. * @param object $sheetExport
  174. *
  175. * @throws \PhpOffice\PhpSpreadsheet\Exception
  176. * @throws \PhpOffice\PhpSpreadsheet\Reader\Exception
  177. */
  178. public function export($sheetExport)
  179. {
  180. $this->open($sheetExport);
  181. if ($sheetExport instanceof FromView) {
  182. $this->fromView($sheetExport);
  183. } else {
  184. if ($sheetExport instanceof FromQuery) {
  185. $this->fromQuery($sheetExport, $this->worksheet);
  186. }
  187. if ($sheetExport instanceof FromCollection) {
  188. $this->fromCollection($sheetExport);
  189. }
  190. if ($sheetExport instanceof FromArray) {
  191. $this->fromArray($sheetExport);
  192. }
  193. if ($sheetExport instanceof FromIterator) {
  194. $this->fromIterator($sheetExport);
  195. }
  196. if ($sheetExport instanceof FromGenerator) {
  197. $this->fromGenerator($sheetExport);
  198. }
  199. }
  200. $this->close($sheetExport);
  201. }
  202. /**
  203. * @param object $import
  204. * @param int $startRow
  205. */
  206. public function import($import, int $startRow = 1)
  207. {
  208. if ($import instanceof WithEvents) {
  209. $this->registerListeners($import->registerEvents());
  210. }
  211. $this->raise(new BeforeSheet($this, $import));
  212. if ($import instanceof WithProgressBar && !$import instanceof WithChunkReading) {
  213. $import->getConsoleOutput()->progressStart($this->worksheet->getHighestRow());
  214. }
  215. $calculatesFormulas = $import instanceof WithCalculatedFormulas;
  216. $formatData = $import instanceof WithFormatData;
  217. if ($import instanceof WithMappedCells) {
  218. app(MappedReader::class)->map($import, $this->worksheet);
  219. } else {
  220. if ($import instanceof ToModel) {
  221. app(ModelImporter::class)->import($this->worksheet, $import, $startRow);
  222. }
  223. if ($import instanceof ToCollection) {
  224. $rows = $this->toCollection($import, $startRow, null, $calculatesFormulas, $formatData);
  225. if ($import instanceof WithValidation) {
  226. $rows = $this->validated($import, $startRow, $rows);
  227. }
  228. $import->collection($rows);
  229. }
  230. if ($import instanceof ToArray) {
  231. $rows = $this->toArray($import, $startRow, null, $calculatesFormulas, $formatData);
  232. if ($import instanceof WithValidation) {
  233. $rows = $this->validated($import, $startRow, $rows);
  234. }
  235. $import->array($rows);
  236. }
  237. }
  238. if ($import instanceof OnEachRow) {
  239. $headingRow = HeadingRowExtractor::extract($this->worksheet, $import);
  240. $headerIsGrouped = HeadingRowExtractor::extractGrouping($headingRow, $import);
  241. $endColumn = $import instanceof WithColumnLimit ? $import->endColumn() : null;
  242. $preparationCallback = $this->getPreparationCallback($import);
  243. foreach ($this->worksheet->getRowIterator()->resetStart($startRow ?? 1) as $row) {
  244. $sheetRow = new Row($row, $headingRow, $headerIsGrouped);
  245. if ($import instanceof WithValidation) {
  246. $sheetRow->setPreparationCallback($preparationCallback);
  247. }
  248. $rowArray = $sheetRow->toArray(null, $import instanceof WithCalculatedFormulas, $import instanceof WithFormatData, $endColumn);
  249. $rowIsEmptyAccordingToImport = $import instanceof SkipsEmptyRows && method_exists($import, 'isEmptyWhen') && $import->isEmptyWhen($rowArray);
  250. if (!$import instanceof SkipsEmptyRows || ($import instanceof SkipsEmptyRows && (!$rowIsEmptyAccordingToImport && !$sheetRow->isEmpty($calculatesFormulas)))) {
  251. if ($import instanceof WithValidation) {
  252. $toValidate = [$sheetRow->getIndex() => $rowArray];
  253. try {
  254. app(RowValidator::class)->validate($toValidate, $import);
  255. $import->onRow($sheetRow);
  256. } catch (RowSkippedException $e) {
  257. }
  258. } else {
  259. $import->onRow($sheetRow);
  260. }
  261. }
  262. if ($import instanceof WithProgressBar) {
  263. $import->getConsoleOutput()->progressAdvance();
  264. }
  265. }
  266. }
  267. $this->raise(new AfterSheet($this, $import));
  268. if ($import instanceof WithProgressBar && !$import instanceof WithChunkReading) {
  269. $import->getConsoleOutput()->progressFinish();
  270. }
  271. }
  272. /**
  273. * @param object $import
  274. * @param int|null $startRow
  275. * @param null $nullValue
  276. * @param bool $calculateFormulas
  277. * @param bool $formatData
  278. * @return array
  279. */
  280. public function toArray($import, int $startRow = null, $nullValue = null, $calculateFormulas = false, $formatData = false)
  281. {
  282. if ($startRow > $this->worksheet->getHighestRow()) {
  283. return [];
  284. }
  285. $endRow = EndRowFinder::find($import, $startRow, $this->worksheet->getHighestRow());
  286. $headingRow = HeadingRowExtractor::extract($this->worksheet, $import);
  287. $headerIsGrouped = HeadingRowExtractor::extractGrouping($headingRow, $import);
  288. $endColumn = $import instanceof WithColumnLimit ? $import->endColumn() : null;
  289. $rows = [];
  290. foreach ($this->worksheet->getRowIterator($startRow, $endRow) as $index => $row) {
  291. $row = new Row($row, $headingRow, $headerIsGrouped);
  292. if ($import instanceof SkipsEmptyRows && $row->isEmpty($calculateFormulas, $endColumn)) {
  293. continue;
  294. }
  295. $row = $row->toArray($nullValue, $calculateFormulas, $formatData, $endColumn);
  296. if ($import && method_exists($import, 'isEmptyWhen') && $import->isEmptyWhen($row)) {
  297. continue;
  298. }
  299. if ($import instanceof WithMapping) {
  300. $row = $import->map($row);
  301. }
  302. if ($import instanceof WithValidation && method_exists($import, 'prepareForValidation')) {
  303. $row = $import->prepareForValidation($row, $index);
  304. }
  305. $rows[] = $row;
  306. if ($import instanceof WithProgressBar) {
  307. $import->getConsoleOutput()->progressAdvance();
  308. }
  309. }
  310. return $rows;
  311. }
  312. /**
  313. * @param object $import
  314. * @param int|null $startRow
  315. * @param null $nullValue
  316. * @param bool $calculateFormulas
  317. * @param bool $formatData
  318. * @return Collection
  319. */
  320. public function toCollection($import, int $startRow = null, $nullValue = null, $calculateFormulas = false, $formatData = false): Collection
  321. {
  322. $rows = $this->toArray($import, $startRow, $nullValue, $calculateFormulas, $formatData);
  323. return new Collection(array_map(function (array $row) {
  324. return new Collection($row);
  325. }, $rows));
  326. }
  327. /**
  328. * @param object $sheetExport
  329. *
  330. * @throws \PhpOffice\PhpSpreadsheet\Exception
  331. */
  332. public function close($sheetExport)
  333. {
  334. if ($sheetExport instanceof WithDrawings) {
  335. $this->addDrawings($sheetExport->drawings());
  336. }
  337. $this->exportable = $sheetExport;
  338. if ($sheetExport instanceof WithColumnFormatting) {
  339. foreach ($sheetExport->columnFormats() as $column => $format) {
  340. $this->formatColumn($column, $format);
  341. }
  342. }
  343. if ($sheetExport instanceof ShouldAutoSize) {
  344. $this->autoSize();
  345. }
  346. if ($sheetExport instanceof WithColumnWidths) {
  347. foreach ($sheetExport->columnWidths() as $column => $width) {
  348. $this->worksheet->getColumnDimension($column)->setAutoSize(false)->setWidth($width);
  349. }
  350. }
  351. if ($sheetExport instanceof WithStyles) {
  352. $styles = $sheetExport->styles($this->worksheet);
  353. if (is_array($styles)) {
  354. foreach ($styles as $coordinate => $coordinateStyles) {
  355. if (is_numeric($coordinate)) {
  356. $coordinate = 'A' . $coordinate . ':' . $this->worksheet->getHighestColumn($coordinate) . $coordinate;
  357. }
  358. $this->worksheet->getStyle($coordinate)->applyFromArray($coordinateStyles);
  359. }
  360. }
  361. }
  362. $this->raise(new AfterSheet($this, $this->exportable));
  363. $this->clearListeners();
  364. }
  365. /**
  366. * @param FromView $sheetExport
  367. * @param int|null $sheetIndex
  368. *
  369. * @throws \PhpOffice\PhpSpreadsheet\Reader\Exception
  370. */
  371. public function fromView(FromView $sheetExport, $sheetIndex = null)
  372. {
  373. $temporaryFile = $this->temporaryFileFactory->makeLocal(null, 'html');
  374. $temporaryFile->put($sheetExport->view()->render());
  375. $spreadsheet = $this->worksheet->getParent();
  376. /** @var Html $reader */
  377. $reader = IOFactory::createReader('Html');
  378. // If no sheetIndex given, insert content into the last sheet
  379. $reader->setSheetIndex($sheetIndex ?? $spreadsheet->getSheetCount() - 1);
  380. $reader->loadIntoExisting($temporaryFile->getLocalPath(), $spreadsheet);
  381. $temporaryFile->delete();
  382. }
  383. /**
  384. * @param FromQuery $sheetExport
  385. * @param Worksheet $worksheet
  386. */
  387. public function fromQuery(FromQuery $sheetExport, Worksheet $worksheet)
  388. {
  389. if ($sheetExport->query() instanceof \Laravel\Scout\Builder) {
  390. $this->fromScout($sheetExport, $worksheet);
  391. return;
  392. }
  393. $sheetExport->query()->chunk($this->getChunkSize($sheetExport), function ($chunk) use ($sheetExport) {
  394. $this->appendRows($chunk, $sheetExport);
  395. });
  396. }
  397. /**
  398. * @param FromQuery $sheetExport
  399. * @param Worksheet $worksheet
  400. */
  401. public function fromScout(FromQuery $sheetExport, Worksheet $worksheet)
  402. {
  403. $scout = $sheetExport->query();
  404. $chunkSize = $this->getChunkSize($sheetExport);
  405. $chunk = $scout->paginate($chunkSize);
  406. // Append first page
  407. $this->appendRows($chunk->items(), $sheetExport);
  408. // Append rest of pages
  409. for ($page = 2; $page <= $chunk->lastPage(); $page++) {
  410. $this->appendRows($scout->paginate($chunkSize, 'page', $page)->items(), $sheetExport);
  411. }
  412. }
  413. /**
  414. * @param FromCollection $sheetExport
  415. */
  416. public function fromCollection(FromCollection $sheetExport)
  417. {
  418. $this->appendRows($sheetExport->collection()->all(), $sheetExport);
  419. }
  420. /**
  421. * @param FromArray $sheetExport
  422. */
  423. public function fromArray(FromArray $sheetExport)
  424. {
  425. $this->appendRows($sheetExport->array(), $sheetExport);
  426. }
  427. /**
  428. * @param FromIterator $sheetExport
  429. */
  430. public function fromIterator(FromIterator $sheetExport)
  431. {
  432. $iterator = class_exists(LazyCollection::class) ? new LazyCollection(function () use ($sheetExport) {
  433. foreach ($sheetExport->iterator() as $row) {
  434. yield $row;
  435. }
  436. }) : $sheetExport->iterator();
  437. $this->appendRows($iterator, $sheetExport);
  438. }
  439. /**
  440. * @param FromGenerator $sheetExport
  441. */
  442. public function fromGenerator(FromGenerator $sheetExport)
  443. {
  444. $generator = class_exists(LazyCollection::class) ? new LazyCollection(function () use ($sheetExport) {
  445. foreach ($sheetExport->generator() as $row) {
  446. yield $row;
  447. }
  448. }) : $sheetExport->generator();
  449. $this->appendRows($generator, $sheetExport);
  450. }
  451. /**
  452. * @param array $rows
  453. * @param string|null $startCell
  454. * @param bool $strictNullComparison
  455. */
  456. public function append(array $rows, string $startCell = null, bool $strictNullComparison = false)
  457. {
  458. if (!$startCell) {
  459. $startCell = 'A1';
  460. }
  461. if ($this->hasRows()) {
  462. $startCell = CellHelper::getColumnFromCoordinate($startCell) . ($this->worksheet->getHighestRow() + 1);
  463. }
  464. $this->worksheet->fromArray($rows, null, $startCell, $strictNullComparison);
  465. }
  466. public function autoSize()
  467. {
  468. foreach ($this->buildColumnRange('A', $this->worksheet->getHighestDataColumn()) as $col) {
  469. $dimension = $this->worksheet->getColumnDimension($col);
  470. // Only auto-size columns that have not have an explicit width.
  471. if ($dimension->getWidth() == -1) {
  472. $dimension->setAutoSize(true);
  473. }
  474. }
  475. }
  476. /**
  477. * @param string $column
  478. * @param string $format
  479. *
  480. * @throws \PhpOffice\PhpSpreadsheet\Exception
  481. */
  482. public function formatColumn(string $column, string $format)
  483. {
  484. // If the column is a range, we wouldn't need to calculate the range.
  485. if (stripos($column, ':') !== false) {
  486. $this->worksheet
  487. ->getStyle($column)
  488. ->getNumberFormat()
  489. ->setFormatCode($format);
  490. } else {
  491. $this->worksheet
  492. ->getStyle($column . '1:' . $column . $this->worksheet->getHighestRow())
  493. ->getNumberFormat()
  494. ->setFormatCode($format);
  495. }
  496. }
  497. /**
  498. * @param int $chunkSize
  499. * @return Sheet
  500. */
  501. public function chunkSize(int $chunkSize)
  502. {
  503. $this->chunkSize = $chunkSize;
  504. return $this;
  505. }
  506. /**
  507. * @return Worksheet
  508. */
  509. public function getDelegate()
  510. {
  511. return $this->worksheet;
  512. }
  513. /**
  514. * @param Chart|Chart[] $charts
  515. */
  516. public function addCharts($charts)
  517. {
  518. $charts = \is_array($charts) ? $charts : [$charts];
  519. foreach ($charts as $chart) {
  520. $this->worksheet->addChart($chart);
  521. }
  522. }
  523. /**
  524. * @param BaseDrawing|BaseDrawing[] $drawings
  525. */
  526. public function addDrawings($drawings)
  527. {
  528. $drawings = \is_array($drawings) ? $drawings : [$drawings];
  529. foreach ($drawings as $drawing) {
  530. $drawing->setWorksheet($this->worksheet);
  531. }
  532. }
  533. /**
  534. * @param string $concern
  535. * @return string
  536. */
  537. public function hasConcern(string $concern): string
  538. {
  539. return $this->exportable instanceof $concern;
  540. }
  541. /**
  542. * @param iterable $rows
  543. * @param object $sheetExport
  544. */
  545. public function appendRows($rows, $sheetExport)
  546. {
  547. if (method_exists($sheetExport, 'prepareRows')) {
  548. $rows = $sheetExport->prepareRows($rows);
  549. }
  550. $rows = $rows instanceof LazyCollection ? $rows : new Collection($rows);
  551. $rows->flatMap(function ($row) use ($sheetExport) {
  552. if ($sheetExport instanceof WithMapping) {
  553. $row = $sheetExport->map($row);
  554. }
  555. if ($sheetExport instanceof WithCustomValueBinder) {
  556. SpreadsheetCell::setValueBinder($sheetExport);
  557. }
  558. return ArrayHelper::ensureMultipleRows(
  559. static::mapArraybleRow($row)
  560. );
  561. })->chunk(1000)->each(function ($rows) use ($sheetExport) {
  562. $this->append(
  563. $rows->toArray(),
  564. $sheetExport instanceof WithCustomStartCell ? $sheetExport->startCell() : null,
  565. $this->hasStrictNullComparison($sheetExport)
  566. );
  567. });
  568. }
  569. /**
  570. * @param mixed $row
  571. * @return array
  572. */
  573. public static function mapArraybleRow($row): array
  574. {
  575. // When dealing with eloquent models, we'll skip the relations
  576. // as we won't be able to display them anyway.
  577. if (is_object($row) && method_exists($row, 'attributesToArray')) {
  578. return $row->attributesToArray();
  579. }
  580. if ($row instanceof Arrayable) {
  581. return $row->toArray();
  582. }
  583. // Convert StdObjects to arrays
  584. if (is_object($row)) {
  585. return json_decode(json_encode($row), true);
  586. }
  587. return $row;
  588. }
  589. /**
  590. * @param $sheetImport
  591. * @return int
  592. */
  593. public function getStartRow($sheetImport): int
  594. {
  595. return HeadingRowExtractor::determineStartRow($sheetImport);
  596. }
  597. /**
  598. * Disconnect the sheet.
  599. */
  600. public function disconnect()
  601. {
  602. $this->worksheet->disconnectCells();
  603. unset($this->worksheet);
  604. }
  605. /**
  606. * @return Collection|array
  607. */
  608. protected function validated(WithValidation $import, int $startRow, $rows)
  609. {
  610. $toValidate = (new Collection($rows))->mapWithKeys(function ($row, $index) use ($startRow) {
  611. return [($startRow + $index) => $row];
  612. });
  613. try {
  614. app(RowValidator::class)->validate($toValidate->toArray(), $import);
  615. } catch (RowSkippedException $e) {
  616. foreach ($e->skippedRows() as $row) {
  617. unset($rows[$row - $startRow]);
  618. }
  619. }
  620. return $rows;
  621. }
  622. /**
  623. * @param string $lower
  624. * @param string $upper
  625. * @return \Generator
  626. */
  627. protected function buildColumnRange(string $lower, string $upper)
  628. {
  629. $upper++;
  630. for ($i = $lower; $i !== $upper; $i++) {
  631. yield $i;
  632. }
  633. }
  634. /**
  635. * @return bool
  636. */
  637. private function hasRows(): bool
  638. {
  639. $startCell = 'A1';
  640. if ($this->exportable instanceof WithCustomStartCell) {
  641. $startCell = $this->exportable->startCell();
  642. }
  643. return $this->worksheet->cellExists($startCell);
  644. }
  645. /**
  646. * @param object $sheetExport
  647. * @return bool
  648. */
  649. private function hasStrictNullComparison($sheetExport): bool
  650. {
  651. if ($sheetExport instanceof WithStrictNullComparison) {
  652. return true;
  653. }
  654. return config('excel.exports.strict_null_comparison', false);
  655. }
  656. /**
  657. * @param object|WithCustomChunkSize $export
  658. * @return int
  659. */
  660. private function getChunkSize($export): int
  661. {
  662. if ($export instanceof WithCustomChunkSize) {
  663. return $export->chunkSize();
  664. }
  665. return $this->chunkSize;
  666. }
  667. /**
  668. * @param object|WithValidation $import
  669. * @return Closure|null
  670. */
  671. private function getPreparationCallback($import)
  672. {
  673. if (!$import instanceof WithValidation || !method_exists($import, 'prepareForValidation')) {
  674. return null;
  675. }
  676. return function (array $data, int $index) use ($import) {
  677. return $import->prepareForValidation($data, $index);
  678. };
  679. }
  680. }