PageRenderTime 27ms CodeModel.GetById 23ms RepoModel.GetById 1ms app.codeStats 0ms

/vendor/maatwebsite/excel/src/Reader.php

https://gitlab.com/jjpa2018/dashboard
PHP | 464 lines | 268 code | 72 blank | 124 comment | 25 complexity | 485481b4aa7d45461dc616ce2a54b356 MD5 | raw file
  1. <?php
  2. namespace Maatwebsite\Excel;
  3. use Illuminate\Contracts\Queue\ShouldQueue;
  4. use Illuminate\Support\Collection;
  5. use InvalidArgumentException;
  6. use Maatwebsite\Excel\Concerns\HasReferencesToOtherSheets;
  7. use Maatwebsite\Excel\Concerns\SkipsUnknownSheets;
  8. use Maatwebsite\Excel\Concerns\WithCalculatedFormulas;
  9. use Maatwebsite\Excel\Concerns\WithChunkReading;
  10. use Maatwebsite\Excel\Concerns\WithCustomValueBinder;
  11. use Maatwebsite\Excel\Concerns\WithEvents;
  12. use Maatwebsite\Excel\Concerns\WithFormatData;
  13. use Maatwebsite\Excel\Concerns\WithMultipleSheets;
  14. use Maatwebsite\Excel\Events\AfterImport;
  15. use Maatwebsite\Excel\Events\BeforeImport;
  16. use Maatwebsite\Excel\Events\ImportFailed;
  17. use Maatwebsite\Excel\Exceptions\NoTypeDetectedException;
  18. use Maatwebsite\Excel\Exceptions\SheetNotFoundException;
  19. use Maatwebsite\Excel\Factories\ReaderFactory;
  20. use Maatwebsite\Excel\Files\TemporaryFile;
  21. use Maatwebsite\Excel\Files\TemporaryFileFactory;
  22. use Maatwebsite\Excel\Transactions\TransactionHandler;
  23. use PhpOffice\PhpSpreadsheet\Cell\Cell;
  24. use PhpOffice\PhpSpreadsheet\Reader\Exception;
  25. use PhpOffice\PhpSpreadsheet\Reader\IReader;
  26. use PhpOffice\PhpSpreadsheet\Spreadsheet;
  27. use Symfony\Component\HttpFoundation\File\UploadedFile;
  28. use Throwable;
  29. /** @mixin Spreadsheet */
  30. class Reader
  31. {
  32. use DelegatedMacroable, HasEventBus;
  33. /**
  34. * @var Spreadsheet
  35. */
  36. protected $spreadsheet;
  37. /**
  38. * @var object[]
  39. */
  40. protected $sheetImports = [];
  41. /**
  42. * @var TemporaryFile
  43. */
  44. protected $currentFile;
  45. /**
  46. * @var TemporaryFileFactory
  47. */
  48. protected $temporaryFileFactory;
  49. /**
  50. * @var TransactionHandler
  51. */
  52. protected $transaction;
  53. /**
  54. * @var IReader
  55. */
  56. protected $reader;
  57. /**
  58. * @param TemporaryFileFactory $temporaryFileFactory
  59. * @param TransactionHandler $transaction
  60. */
  61. public function __construct(TemporaryFileFactory $temporaryFileFactory, TransactionHandler $transaction)
  62. {
  63. $this->setDefaultValueBinder();
  64. $this->transaction = $transaction;
  65. $this->temporaryFileFactory = $temporaryFileFactory;
  66. }
  67. public function __sleep()
  68. {
  69. return ['spreadsheet', 'sheetImports', 'currentFile', 'temporaryFileFactory', 'reader'];
  70. }
  71. public function __wakeup()
  72. {
  73. $this->transaction = app(TransactionHandler::class);
  74. }
  75. /**
  76. * @param object $import
  77. * @param string|UploadedFile $filePath
  78. * @param string|null $readerType
  79. * @param string|null $disk
  80. * @return \Illuminate\Foundation\Bus\PendingDispatch|$this
  81. *
  82. * @throws NoTypeDetectedException
  83. * @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
  84. * @throws Exception
  85. */
  86. public function read($import, $filePath, string $readerType = null, string $disk = null)
  87. {
  88. $this->reader = $this->getReader($import, $filePath, $readerType, $disk);
  89. if ($import instanceof WithChunkReading) {
  90. return (new ChunkReader)->read($import, $this, $this->currentFile);
  91. }
  92. try {
  93. $this->loadSpreadsheet($import, $this->reader);
  94. ($this->transaction)(function () use ($import) {
  95. $sheetsToDisconnect = [];
  96. foreach ($this->sheetImports as $index => $sheetImport) {
  97. if ($sheet = $this->getSheet($import, $sheetImport, $index)) {
  98. $sheet->import($sheetImport, $sheet->getStartRow($sheetImport));
  99. // when using WithCalculatedFormulas we need to keep the sheet until all sheets are imported
  100. if (!($sheetImport instanceof HasReferencesToOtherSheets)) {
  101. $sheet->disconnect();
  102. } else {
  103. $sheetsToDisconnect[] = $sheet;
  104. }
  105. }
  106. }
  107. foreach ($sheetsToDisconnect as $sheet) {
  108. $sheet->disconnect();
  109. }
  110. });
  111. $this->afterImport($import);
  112. } catch (Throwable $e) {
  113. $this->raise(new ImportFailed($e));
  114. $this->garbageCollect();
  115. throw $e;
  116. }
  117. return $this;
  118. }
  119. /**
  120. * @param object $import
  121. * @param string|UploadedFile $filePath
  122. * @param string $readerType
  123. * @param string|null $disk
  124. * @return array
  125. *
  126. * @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
  127. * @throws \PhpOffice\PhpSpreadsheet\Exception
  128. * @throws NoTypeDetectedException
  129. * @throws Exceptions\SheetNotFoundException
  130. */
  131. public function toArray($import, $filePath, string $readerType = null, string $disk = null): array
  132. {
  133. $this->reader = $this->getReader($import, $filePath, $readerType, $disk);
  134. $this->loadSpreadsheet($import);
  135. $sheets = [];
  136. $sheetsToDisconnect = [];
  137. foreach ($this->sheetImports as $index => $sheetImport) {
  138. $calculatesFormulas = $sheetImport instanceof WithCalculatedFormulas;
  139. $formatData = $sheetImport instanceof WithFormatData;
  140. if ($sheet = $this->getSheet($import, $sheetImport, $index)) {
  141. $sheets[$index] = $sheet->toArray($sheetImport, $sheet->getStartRow($sheetImport), null, $calculatesFormulas, $formatData);
  142. // when using WithCalculatedFormulas we need to keep the sheet until all sheets are imported
  143. if (!($sheetImport instanceof HasReferencesToOtherSheets)) {
  144. $sheet->disconnect();
  145. } else {
  146. $sheetsToDisconnect[] = $sheet;
  147. }
  148. }
  149. }
  150. foreach ($sheetsToDisconnect as $sheet) {
  151. $sheet->disconnect();
  152. }
  153. $this->afterImport($import);
  154. return $sheets;
  155. }
  156. /**
  157. * @param object $import
  158. * @param string|UploadedFile $filePath
  159. * @param string $readerType
  160. * @param string|null $disk
  161. * @return Collection
  162. *
  163. * @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
  164. * @throws \PhpOffice\PhpSpreadsheet\Exception
  165. * @throws NoTypeDetectedException
  166. * @throws Exceptions\SheetNotFoundException
  167. */
  168. public function toCollection($import, $filePath, string $readerType = null, string $disk = null): Collection
  169. {
  170. $this->reader = $this->getReader($import, $filePath, $readerType, $disk);
  171. $this->loadSpreadsheet($import);
  172. $sheets = new Collection();
  173. $sheetsToDisconnect = [];
  174. foreach ($this->sheetImports as $index => $sheetImport) {
  175. $calculatesFormulas = $sheetImport instanceof WithCalculatedFormulas;
  176. $formatData = $sheetImport instanceof WithFormatData;
  177. if ($sheet = $this->getSheet($import, $sheetImport, $index)) {
  178. $sheets->put($index, $sheet->toCollection($sheetImport, $sheet->getStartRow($sheetImport), null, $calculatesFormulas, $formatData));
  179. // when using WithCalculatedFormulas we need to keep the sheet until all sheets are imported
  180. if (!($sheetImport instanceof HasReferencesToOtherSheets)) {
  181. $sheet->disconnect();
  182. } else {
  183. $sheetsToDisconnect[] = $sheet;
  184. }
  185. }
  186. }
  187. foreach ($sheetsToDisconnect as $sheet) {
  188. $sheet->disconnect();
  189. }
  190. $this->afterImport($import);
  191. return $sheets;
  192. }
  193. /**
  194. * @return Spreadsheet
  195. */
  196. public function getDelegate()
  197. {
  198. return $this->spreadsheet;
  199. }
  200. /**
  201. * @return $this
  202. */
  203. public function setDefaultValueBinder(): self
  204. {
  205. Cell::setValueBinder(
  206. app(config('excel.value_binder.default', DefaultValueBinder::class))
  207. );
  208. return $this;
  209. }
  210. /**
  211. * @param object $import
  212. */
  213. public function loadSpreadsheet($import)
  214. {
  215. $this->sheetImports = $this->buildSheetImports($import);
  216. $this->readSpreadsheet();
  217. // When no multiple sheets, use the main import object
  218. // for each loaded sheet in the spreadsheet
  219. if (!$import instanceof WithMultipleSheets) {
  220. $this->sheetImports = array_fill(0, $this->spreadsheet->getSheetCount(), $import);
  221. }
  222. $this->beforeImport($import);
  223. }
  224. public function readSpreadsheet()
  225. {
  226. $this->spreadsheet = $this->reader->load(
  227. $this->currentFile->getLocalPath()
  228. );
  229. }
  230. /**
  231. * @param object $import
  232. */
  233. public function beforeImport($import)
  234. {
  235. $this->raise(new BeforeImport($this, $import));
  236. }
  237. /**
  238. * @param object $import
  239. */
  240. public function afterImport($import)
  241. {
  242. $this->raise(new AfterImport($this, $import));
  243. $this->garbageCollect();
  244. }
  245. /**
  246. * @return IReader
  247. */
  248. public function getPhpSpreadsheetReader(): IReader
  249. {
  250. return $this->reader;
  251. }
  252. /**
  253. * @param object $import
  254. * @return array
  255. */
  256. public function getWorksheets($import): array
  257. {
  258. // Csv doesn't have worksheets.
  259. if (!method_exists($this->reader, 'listWorksheetNames')) {
  260. return ['Worksheet' => $import];
  261. }
  262. $worksheets = [];
  263. $worksheetNames = $this->reader->listWorksheetNames($this->currentFile->getLocalPath());
  264. if ($import instanceof WithMultipleSheets) {
  265. $sheetImports = $import->sheets();
  266. foreach ($sheetImports as $index => $sheetImport) {
  267. // Translate index to name.
  268. if (is_numeric($index)) {
  269. $index = $worksheetNames[$index] ?? $index;
  270. }
  271. // Specify with worksheet name should have which import.
  272. $worksheets[$index] = $sheetImport;
  273. }
  274. // Load specific sheets.
  275. if (method_exists($this->reader, 'setLoadSheetsOnly')) {
  276. $this->reader->setLoadSheetsOnly(
  277. collect($worksheetNames)->intersect(array_keys($worksheets))->values()->all()
  278. );
  279. }
  280. } else {
  281. // Each worksheet the same import class.
  282. foreach ($worksheetNames as $name) {
  283. $worksheets[$name] = $import;
  284. }
  285. }
  286. return $worksheets;
  287. }
  288. /**
  289. * @return array
  290. */
  291. public function getTotalRows(): array
  292. {
  293. $info = $this->reader->listWorksheetInfo($this->currentFile->getLocalPath());
  294. $totalRows = [];
  295. foreach ($info as $sheet) {
  296. $totalRows[$sheet['worksheetName']] = $sheet['totalRows'];
  297. }
  298. return $totalRows;
  299. }
  300. /**
  301. * @param $import
  302. * @param $sheetImport
  303. * @param $index
  304. * @return Sheet|null
  305. *
  306. * @throws \PhpOffice\PhpSpreadsheet\Exception
  307. * @throws SheetNotFoundException
  308. */
  309. protected function getSheet($import, $sheetImport, $index)
  310. {
  311. try {
  312. return Sheet::make($this->spreadsheet, $index);
  313. } catch (SheetNotFoundException $e) {
  314. if ($import instanceof SkipsUnknownSheets) {
  315. $import->onUnknownSheet($index);
  316. return null;
  317. }
  318. if ($sheetImport instanceof SkipsUnknownSheets) {
  319. $sheetImport->onUnknownSheet($index);
  320. return null;
  321. }
  322. throw $e;
  323. }
  324. }
  325. /**
  326. * @param object $import
  327. * @return array
  328. */
  329. private function buildSheetImports($import): array
  330. {
  331. $sheetImports = [];
  332. if ($import instanceof WithMultipleSheets) {
  333. $sheetImports = $import->sheets();
  334. // When only sheet names are given and the reader has
  335. // an option to load only the selected sheets.
  336. if (
  337. method_exists($this->reader, 'setLoadSheetsOnly')
  338. && count(array_filter(array_keys($sheetImports), 'is_numeric')) === 0
  339. ) {
  340. $this->reader->setLoadSheetsOnly(array_keys($sheetImports));
  341. }
  342. }
  343. return $sheetImports;
  344. }
  345. /**
  346. * @param object $import
  347. * @param string|UploadedFile $filePath
  348. * @param string|null $readerType
  349. * @param string $disk
  350. * @return IReader
  351. *
  352. * @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
  353. * @throws NoTypeDetectedException
  354. * @throws \PhpOffice\PhpSpreadsheet\Reader\Exception
  355. * @throws InvalidArgumentException
  356. */
  357. private function getReader($import, $filePath, string $readerType = null, string $disk = null): IReader
  358. {
  359. $shouldQueue = $import instanceof ShouldQueue;
  360. if ($shouldQueue && !$import instanceof WithChunkReading) {
  361. throw new InvalidArgumentException('ShouldQueue is only supported in combination with WithChunkReading.');
  362. }
  363. if ($import instanceof WithEvents) {
  364. $this->registerListeners($import->registerEvents());
  365. }
  366. if ($import instanceof WithCustomValueBinder) {
  367. Cell::setValueBinder($import);
  368. }
  369. $fileExtension = pathinfo($filePath, PATHINFO_EXTENSION);
  370. $temporaryFile = $shouldQueue ? $this->temporaryFileFactory->make($fileExtension) : $this->temporaryFileFactory->makeLocal(null, $fileExtension);
  371. $this->currentFile = $temporaryFile->copyFrom(
  372. $filePath,
  373. $disk
  374. );
  375. return ReaderFactory::make(
  376. $import,
  377. $this->currentFile,
  378. $readerType
  379. );
  380. }
  381. /**
  382. * Garbage collect.
  383. */
  384. private function garbageCollect()
  385. {
  386. $this->clearListeners();
  387. $this->setDefaultValueBinder();
  388. // Force garbage collecting
  389. unset($this->sheetImports, $this->spreadsheet);
  390. $this->currentFile->delete();
  391. }
  392. }