├── .phpunit.cache └── test-results ├── .styleci.yml ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── SECURITY.md ├── composer.json ├── config └── excel.php └── src ├── Cache ├── BatchCache.php ├── BatchCacheDeprecated.php ├── CacheManager.php ├── MemoryCache.php └── MemoryCacheDeprecated.php ├── Cell.php ├── ChunkReader.php ├── Concerns ├── Exportable.php ├── FromArray.php ├── FromCollection.php ├── FromGenerator.php ├── FromIterator.php ├── FromQuery.php ├── FromView.php ├── HasReferencesToOtherSheets.php ├── Importable.php ├── MapsCsvSettings.php ├── OnEachRow.php ├── PersistRelations.php ├── RegistersEventListeners.php ├── RemembersChunkOffset.php ├── RemembersRowNumber.php ├── ShouldAutoSize.php ├── ShouldQueueWithoutChain.php ├── SkipsEmptyRows.php ├── SkipsErrors.php ├── SkipsFailures.php ├── SkipsOnError.php ├── SkipsOnFailure.php ├── SkipsUnknownSheets.php ├── ToArray.php ├── ToCollection.php ├── ToModel.php ├── WithBackgroundColor.php ├── WithBatchInserts.php ├── WithCalculatedFormulas.php ├── WithCharts.php ├── WithChunkReading.php ├── WithColumnFormatting.php ├── WithColumnLimit.php ├── WithColumnWidths.php ├── WithConditionalSheets.php ├── WithCustomChunkSize.php ├── WithCustomCsvSettings.php ├── WithCustomQuerySize.php ├── WithCustomStartCell.php ├── WithCustomValueBinder.php ├── WithDefaultStyles.php ├── WithDrawings.php ├── WithEvents.php ├── WithFormatData.php ├── WithGroupedHeadingRow.php ├── WithHeadingRow.php ├── WithHeadings.php ├── WithLimit.php ├── WithMappedCells.php ├── WithMapping.php ├── WithMultipleSheets.php ├── WithPreCalculateFormulas.php ├── WithProgressBar.php ├── WithProperties.php ├── WithReadFilter.php ├── WithSkipDuplicates.php ├── WithStartRow.php ├── WithStrictNullComparison.php ├── WithStyles.php ├── WithTitle.php ├── WithUpsertColumns.php ├── WithUpserts.php └── WithValidation.php ├── Console ├── ExportMakeCommand.php ├── ImportMakeCommand.php ├── WithModelStub.php └── stubs │ ├── export.model.stub │ ├── export.plain.stub │ ├── export.query-model.stub │ ├── export.query.stub │ ├── import.collection.stub │ └── import.model.stub ├── DefaultValueBinder.php ├── DelegatedMacroable.php ├── Events ├── AfterBatch.php ├── AfterChunk.php ├── AfterImport.php ├── AfterSheet.php ├── BeforeExport.php ├── BeforeImport.php ├── BeforeSheet.php ├── BeforeWriting.php ├── Event.php └── ImportFailed.php ├── Excel.php ├── ExcelServiceProvider.php ├── Exceptions ├── ConcernConflictException.php ├── LaravelExcelException.php ├── NoFilePathGivenException.php ├── NoFilenameGivenException.php ├── NoSheetsFoundException.php ├── NoTypeDetectedException.php ├── RowSkippedException.php ├── SheetNotFoundException.php └── UnreadableFileException.php ├── Exporter.php ├── Facades └── Excel.php ├── Factories ├── ReaderFactory.php └── WriterFactory.php ├── Fakes ├── ExcelFake.php └── fake_file ├── Files ├── Disk.php ├── Filesystem.php ├── LocalTemporaryFile.php ├── RemoteTemporaryFile.php ├── TemporaryFile.php └── TemporaryFileFactory.php ├── Filters ├── ChunkReadFilter.php └── LimitFilter.php ├── HasEventBus.php ├── HeadingRowImport.php ├── Helpers ├── ArrayHelper.php ├── CellHelper.php └── FileTypeDetector.php ├── Importer.php ├── Imports ├── EndRowFinder.php ├── HeadingRowExtractor.php ├── HeadingRowFormatter.php ├── ModelImporter.php ├── ModelManager.php └── Persistence │ └── CascadePersistManager.php ├── Jobs ├── AfterImportJob.php ├── AppendDataToSheet.php ├── AppendPaginatedToSheet.php ├── AppendQueryToSheet.php ├── AppendViewToSheet.php ├── CloseSheet.php ├── ExtendedQueueable.php ├── Middleware │ └── LocalizeJob.php ├── ProxyFailures.php ├── QueueExport.php ├── QueueImport.php ├── ReadChunk.php └── StoreQueuedExport.php ├── MappedReader.php ├── Middleware ├── CellMiddleware.php ├── ConvertEmptyCellValuesToNull.php └── TrimCellValue.php ├── Mixins ├── DownloadCollectionMixin.php ├── DownloadQueryMacro.php ├── ImportAsMacro.php ├── ImportMacro.php ├── StoreCollectionMixin.php └── StoreQueryMacro.php ├── QueuedWriter.php ├── Reader.php ├── RegistersCustomConcerns.php ├── Row.php ├── SettingsProvider.php ├── Sheet.php ├── Transactions ├── DbTransactionHandler.php ├── NullTransactionHandler.php ├── TransactionHandler.php └── TransactionManager.php ├── Validators ├── Failure.php ├── RowValidator.php └── ValidationException.php └── Writer.php /.styleci.yml: -------------------------------------------------------------------------------- 1 | preset: laravel 2 | 3 | risky: false 4 | 5 | enabled: 6 | - align_double_arrow 7 | - align_equals 8 | - concat_with_spaces 9 | - ordered_class_elements 10 | 11 | disabled: 12 | - concat_without_spaces 13 | - not_operator_with_successor_space 14 | - unalign_equals 15 | 16 | finder: 17 | not-name: 18 | - "*.md" 19 | not-path: 20 | - ".github" -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at patrick@spartner.nl. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: http://contributor-covenant.org 46 | [version]: http://contributor-covenant.org/version/1/4/ 47 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Find the contributing guide at: https://docs.laravel-excel.com/3.1/getting-started/contributing.html 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Spartner 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 |
3 |

4 | 5 | Laravel Excel logo 6 |

7 |
8 | 9 | 10 |

Supercharged Excel exports and imports

11 | 12 |

13 | A simple, but elegant Laravel wrapper around PhpSpreadsheet 14 | exports and imports. 15 |

16 | 17 |

18 | Quickstart 19 | · 20 | Documentation 21 | · 22 | Video Course 23 | · 24 | Nova 25 | · 26 | Blog 27 | · 28 | Contributing 29 | · 30 | Support 31 |

32 | 33 |

34 | 35 | Github Actions 36 | 37 | 38 | 39 | StyleCI 40 | 41 | 42 | 43 | Latest Stable Version 44 | 45 | 46 | 47 | Total Downloads 48 | 49 | 50 | 51 | License 52 | 53 |

54 | 55 | ## ✨ Features 56 | 57 | - **Easily export collections to Excel.** Supercharge your Laravel collections and export them directly to an Excel or CSV document. Exporting has never been so easy. 58 | 59 | - **Supercharged exports.** Export queries with automatic chunking for better performance. You provide us the query, we handle the performance. Exporting even larger datasets? No worries, Laravel Excel has your back. You can queue your exports so all of this happens in the background. 60 | 61 | - **Supercharged imports.** Import workbooks and worksheets to Eloquent models with chunk reading and batch inserts! Have large files? You can queue every chunk of a file! Your entire import will happen in the background. 62 | 63 | - **Export Blade views.** Want to have a custom layout in your spreadsheet? Use a HTML table in a Blade view and export that to Excel. 64 | 65 | ![banner](https://user-images.githubusercontent.com/7728097/57463977-2263fc80-727c-11e9-833d-669d816fb7fb.jpg) 66 |
67 | 68 | ## 🎓 Learning Laravel Excel 69 | 70 | You can find the full documentation of Laravel Excel [on the website](https://docs.laravel-excel.com). 71 | 72 | We welcome suggestions for improving our docs. The documentation repository can be found at [https://github.com/SpartnerNL/laravel-excel-docs](https://github.com/SpartnerNL/laravel-excel-docs). 73 | 74 | Some articles and tutorials can be found on our blog: https://medium.com/maatwebsite/laravel-excel/home 75 | 76 | ## :mailbox_with_mail: License & Postcardware 77 | 78 | ![1_5nblgs68uarg0wxxejozdq](https://user-images.githubusercontent.com/7728097/53638144-9e5f1a00-3c25-11e9-9f4a-fc71c9d94562.jpg) 79 | 80 | Laravel Excel is created with love and care by Spartner (formerly known as Maatwebsite) to give back to the Laravel community. It is completely free (MIT license) to use, however the package is licensed as Postcardware. This means that if it makes it to your production environment, we would very much appreciate receiving a postcard from your hometown. 81 | 82 | **Spartner** 83 | Markt 2 84 | 6231 LS Meerssen 85 | The Netherlands. 86 | 87 | More about the license can be found at: [https://docs.laravel-excel.com/3.1/getting-started/license.html](https://docs.laravel-excel.com/3.1/getting-started/license.html) 88 | 89 | ## Created by Spartner (formerly Maatwebsite) 90 | 91 | We are a strategic development partner, creating web-based custom built software from Laravel. In need of a digital solution for your challenge? Give us a call. 92 | 93 | https://spartner.software 94 | info@spartner.nl 95 | +31 (0) 10 - 7449312 96 | 97 | ## :wrench: Supported Versions 98 | 99 | Versions will be supported for a limited amount of time. 100 | 101 | | Version | Laravel Version | Php Version | Support | 102 | |---- |----|----|----| 103 | | 2.1 | <=5.6 | <=7.0 | Unsupported since 15-5-2018 | 104 | | 3.0 | ^5.5 | ^7.0 | Unsupported since 31-12-2018 | 105 | | 3.1 | >=5.8 \| <=11.x | ^7.2 \| ^8.0 | New features | 106 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | **PLEASE DON'T DISCLOSE SECURITY-RELATED ISSUES PUBLICLY, [SEE BELOW](#reporting-a-vulnerability).** 4 | 5 | ## Supported Versions 6 | 7 | Version | Security Fixes Until 8 | --- | --- 9 | 3.1 | - 10 | 3.0 | 31-12-2018 11 | 2.1 | 15-5-2018 12 | 13 | ## Reporting a Vulnerability 14 | 15 | If you discover a security vulnerability within Laravel Excel, please send an email to Patrick Brouwers at patrick@spartner.nl. All security vulnerabilities will be promptly addressed. 16 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "maatwebsite/excel", 3 | "description": "Supercharged Excel exports and imports in Laravel", 4 | "license": "MIT", 5 | "keywords": [ 6 | "laravel", 7 | "php", 8 | "phpspreadsheet", 9 | "phpexcel", 10 | "excel", 11 | "csv", 12 | "export", 13 | "import", 14 | "batch" 15 | ], 16 | "authors": [ 17 | { 18 | "name": "Patrick Brouwers", 19 | "email": "patrick@spartner.nl" 20 | } 21 | ], 22 | "require": { 23 | "ext-json": "*", 24 | "php": "^7.0||^8.0", 25 | "phpoffice/phpspreadsheet": "^1.29.9", 26 | "illuminate/support": "5.8.*||^6.0||^7.0||^8.0||^9.0||^10.0||^11.0||^12.0", 27 | "psr/simple-cache": "^1.0||^2.0||^3.0", 28 | "composer/semver": "^3.3" 29 | }, 30 | "require-dev": { 31 | "orchestra/testbench": "^6.0||^7.0||^8.0||^9.0||^10.0", 32 | "predis/predis": "^1.1", 33 | "laravel/scout": "^7.0||^8.0||^9.0||^10.0" 34 | }, 35 | "autoload": { 36 | "psr-4": { 37 | "Maatwebsite\\Excel\\": "src/" 38 | } 39 | }, 40 | "autoload-dev": { 41 | "psr-4": { 42 | "Maatwebsite\\Excel\\Tests\\": "tests/" 43 | } 44 | }, 45 | "extra": { 46 | "laravel": { 47 | "providers": [ 48 | "Maatwebsite\\Excel\\ExcelServiceProvider" 49 | ], 50 | "aliases": { 51 | "Excel": "Maatwebsite\\Excel\\Facades\\Excel" 52 | } 53 | } 54 | }, 55 | "minimum-stability": "dev", 56 | "prefer-stable": true 57 | } -------------------------------------------------------------------------------- /src/Cache/BatchCache.php: -------------------------------------------------------------------------------- 1 | cache = $cache; 36 | $this->memory = $memory; 37 | $this->defaultTTL = $defaultTTL; 38 | } 39 | 40 | public function __sleep() 41 | { 42 | return ['memory']; 43 | } 44 | 45 | public function __wakeup() 46 | { 47 | $this->cache = Cache::driver( 48 | config('excel.cache.illuminate.store') 49 | ); 50 | } 51 | 52 | /** 53 | * {@inheritdoc} 54 | */ 55 | public function get(string $key, mixed $default = null): mixed 56 | { 57 | if ($this->memory->has($key)) { 58 | return $this->memory->get($key); 59 | } 60 | 61 | return $this->cache->get($key, $default); 62 | } 63 | 64 | /** 65 | * {@inheritdoc} 66 | */ 67 | public function set(string $key, mixed $value, null|int|\DateInterval $ttl = null): bool 68 | { 69 | if (func_num_args() === 2) { 70 | $ttl = value($this->defaultTTL); 71 | } 72 | 73 | $this->memory->set($key, $value, $ttl); 74 | 75 | if ($this->memory->reachedMemoryLimit()) { 76 | return $this->cache->setMultiple($this->memory->flush(), $ttl); 77 | } 78 | 79 | return true; 80 | } 81 | 82 | /** 83 | * {@inheritdoc} 84 | */ 85 | public function delete(string $key): bool 86 | { 87 | if ($this->memory->has($key)) { 88 | return $this->memory->delete($key); 89 | } 90 | 91 | return $this->cache->delete($key); 92 | } 93 | 94 | /** 95 | * {@inheritdoc} 96 | */ 97 | public function clear(): bool 98 | { 99 | $this->memory->clear(); 100 | 101 | return $this->cache->clear(); 102 | } 103 | 104 | /** 105 | * {@inheritdoc} 106 | */ 107 | public function getMultiple(iterable $keys, mixed $default = null): iterable 108 | { 109 | // Check if all keys are still in memory 110 | $memory = $this->memory->getMultiple($keys, $default); 111 | $actualItemsInMemory = count(array_filter($memory)); 112 | 113 | if ($actualItemsInMemory === count($keys)) { 114 | return $memory; 115 | } 116 | 117 | // Get all rows from cache if none is hold in memory. 118 | if ($actualItemsInMemory === 0) { 119 | return $this->cache->getMultiple($keys, $default); 120 | } 121 | 122 | // Add missing values from cache. 123 | foreach ($this->cache->getMultiple($keys, $default) as $key => $value) { 124 | if (null !== $value) { 125 | $memory[$key] = $value; 126 | } 127 | } 128 | 129 | return $memory; 130 | } 131 | 132 | /** 133 | * {@inheritdoc} 134 | */ 135 | public function setMultiple(iterable $values, null|int|\DateInterval $ttl = null): bool 136 | { 137 | if (func_num_args() === 1) { 138 | $ttl = value($this->defaultTTL); 139 | } 140 | 141 | $this->memory->setMultiple($values, $ttl); 142 | 143 | if ($this->memory->reachedMemoryLimit()) { 144 | return $this->cache->setMultiple($this->memory->flush(), $ttl); 145 | } 146 | 147 | return true; 148 | } 149 | 150 | /** 151 | * {@inheritdoc} 152 | */ 153 | public function deleteMultiple(iterable $keys): bool 154 | { 155 | $keys = is_array($keys) ? $keys : iterator_to_array($keys); 156 | 157 | $this->memory->deleteMultiple($keys); 158 | 159 | return $this->cache->deleteMultiple($keys); 160 | } 161 | 162 | /** 163 | * {@inheritdoc} 164 | */ 165 | public function has(string $key): bool 166 | { 167 | if ($this->memory->has($key)) { 168 | return true; 169 | } 170 | 171 | return $this->cache->has($key); 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /src/Cache/BatchCacheDeprecated.php: -------------------------------------------------------------------------------- 1 | cache = $cache; 36 | $this->memory = $memory; 37 | $this->defaultTTL = $defaultTTL; 38 | } 39 | 40 | public function __sleep() 41 | { 42 | return ['memory']; 43 | } 44 | 45 | public function __wakeup() 46 | { 47 | $this->cache = Cache::driver( 48 | config('excel.cache.illuminate.store') 49 | ); 50 | } 51 | 52 | /** 53 | * {@inheritdoc} 54 | */ 55 | public function get($key, $default = null) 56 | { 57 | if ($this->memory->has($key)) { 58 | return $this->memory->get($key); 59 | } 60 | 61 | return $this->cache->get($key, $default); 62 | } 63 | 64 | /** 65 | * {@inheritdoc} 66 | */ 67 | public function set($key, $value, $ttl = null) 68 | { 69 | if (func_num_args() === 2) { 70 | $ttl = value($this->defaultTTL); 71 | } 72 | 73 | $this->memory->set($key, $value, $ttl); 74 | 75 | if ($this->memory->reachedMemoryLimit()) { 76 | return $this->cache->setMultiple($this->memory->flush(), $ttl); 77 | } 78 | 79 | return true; 80 | } 81 | 82 | /** 83 | * {@inheritdoc} 84 | */ 85 | public function delete($key) 86 | { 87 | if ($this->memory->has($key)) { 88 | return $this->memory->delete($key); 89 | } 90 | 91 | return $this->cache->delete($key); 92 | } 93 | 94 | /** 95 | * {@inheritdoc} 96 | */ 97 | public function clear() 98 | { 99 | $this->memory->clear(); 100 | 101 | return $this->cache->clear(); 102 | } 103 | 104 | /** 105 | * {@inheritdoc} 106 | */ 107 | public function getMultiple($keys, $default = null) 108 | { 109 | // Check if all keys are still in memory 110 | $memory = $this->memory->getMultiple($keys, $default); 111 | $actualItemsInMemory = count(array_filter($memory)); 112 | 113 | if ($actualItemsInMemory === count($keys)) { 114 | return $memory; 115 | } 116 | 117 | // Get all rows from cache if none is hold in memory. 118 | if ($actualItemsInMemory === 0) { 119 | return $this->cache->getMultiple($keys, $default); 120 | } 121 | 122 | // Add missing values from cache. 123 | foreach ($this->cache->getMultiple($keys, $default) as $key => $value) { 124 | if (null !== $value) { 125 | $memory[$key] = $value; 126 | } 127 | } 128 | 129 | return $memory; 130 | } 131 | 132 | /** 133 | * {@inheritdoc} 134 | */ 135 | public function setMultiple($values, $ttl = null) 136 | { 137 | if (func_num_args() === 1) { 138 | $ttl = value($this->defaultTTL); 139 | } 140 | 141 | $this->memory->setMultiple($values, $ttl); 142 | 143 | if ($this->memory->reachedMemoryLimit()) { 144 | return $this->cache->setMultiple($this->memory->flush(), $ttl); 145 | } 146 | 147 | return true; 148 | } 149 | 150 | /** 151 | * {@inheritdoc} 152 | */ 153 | public function deleteMultiple($keys) 154 | { 155 | $keys = is_array($keys) ? $keys : iterator_to_array($keys); 156 | 157 | $this->memory->deleteMultiple($keys); 158 | 159 | return $this->cache->deleteMultiple($keys); 160 | } 161 | 162 | /** 163 | * {@inheritdoc} 164 | */ 165 | public function has($key) 166 | { 167 | if ($this->memory->has($key)) { 168 | return true; 169 | } 170 | 171 | return $this->cache->has($key); 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /src/Cache/CacheManager.php: -------------------------------------------------------------------------------- 1 | createIlluminateDriver(), 62 | $this->createMemoryDriver(), 63 | config('excel.cache.default_ttl') 64 | ); 65 | } 66 | 67 | return new BatchCache( 68 | $this->createIlluminateDriver(), 69 | $this->createMemoryDriver(), 70 | config('excel.cache.default_ttl') 71 | ); 72 | } 73 | 74 | /** 75 | * @return CacheInterface 76 | */ 77 | public function createIlluminateDriver(): CacheInterface 78 | { 79 | return Cache::driver( 80 | config('excel.cache.illuminate.store') 81 | ); 82 | } 83 | 84 | public function flush() 85 | { 86 | $this->driver()->clear(); 87 | } 88 | 89 | public function isInMemory(): bool 90 | { 91 | return $this->getDefaultDriver() === self::DRIVER_MEMORY; 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/Cache/MemoryCache.php: -------------------------------------------------------------------------------- 1 | memoryLimit = $memoryLimit; 26 | } 27 | 28 | /** 29 | * {@inheritdoc} 30 | */ 31 | public function clear(): bool 32 | { 33 | $this->cache = []; 34 | 35 | return true; 36 | } 37 | 38 | /** 39 | * {@inheritdoc} 40 | */ 41 | public function delete(string $key): bool 42 | { 43 | unset($this->cache[$key]); 44 | 45 | return true; 46 | } 47 | 48 | /** 49 | * {@inheritdoc} 50 | */ 51 | public function deleteMultiple($keys): bool 52 | { 53 | foreach ($keys as $key) { 54 | $this->delete($key); 55 | } 56 | 57 | return true; 58 | } 59 | 60 | /** 61 | * {@inheritdoc} 62 | */ 63 | public function get(string $key, mixed $default = null): mixed 64 | { 65 | if ($this->has($key)) { 66 | return $this->cache[$key]; 67 | } 68 | 69 | return $default; 70 | } 71 | 72 | /** 73 | * {@inheritdoc} 74 | */ 75 | public function getMultiple(iterable $keys, mixed $default = null): iterable 76 | { 77 | $results = []; 78 | foreach ($keys as $key) { 79 | $results[$key] = $this->get($key, $default); 80 | } 81 | 82 | return $results; 83 | } 84 | 85 | /** 86 | * {@inheritdoc} 87 | */ 88 | public function has($key): bool 89 | { 90 | return isset($this->cache[$key]); 91 | } 92 | 93 | /** 94 | * {@inheritdoc} 95 | */ 96 | public function set(string $key, mixed $value, null|int|\DateInterval $ttl = null): bool 97 | { 98 | $this->cache[$key] = $value; 99 | 100 | return true; 101 | } 102 | 103 | /** 104 | * {@inheritdoc} 105 | */ 106 | public function setMultiple($values, $ttl = null): bool 107 | { 108 | foreach ($values as $key => $value) { 109 | $this->set($key, $value); 110 | } 111 | 112 | return true; 113 | } 114 | 115 | /** 116 | * @return bool 117 | */ 118 | public function reachedMemoryLimit(): bool 119 | { 120 | // When no limit is given, we'll never reach any limit. 121 | if (null === $this->memoryLimit) { 122 | return false; 123 | } 124 | 125 | return count($this->cache) >= $this->memoryLimit; 126 | } 127 | 128 | /** 129 | * @return array 130 | */ 131 | public function flush(): array 132 | { 133 | $memory = $this->cache; 134 | 135 | foreach ($memory as $cell) { 136 | if ($cell instanceof Cell) { 137 | $cell->detach(); 138 | } 139 | } 140 | 141 | $this->clear(); 142 | 143 | return $memory; 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /src/Cache/MemoryCacheDeprecated.php: -------------------------------------------------------------------------------- 1 | memoryLimit = $memoryLimit; 26 | } 27 | 28 | /** 29 | * {@inheritdoc} 30 | */ 31 | public function clear() 32 | { 33 | $this->cache = []; 34 | 35 | return true; 36 | } 37 | 38 | /** 39 | * {@inheritdoc} 40 | */ 41 | public function delete($key) 42 | { 43 | unset($this->cache[$key]); 44 | 45 | return true; 46 | } 47 | 48 | /** 49 | * {@inheritdoc} 50 | */ 51 | public function deleteMultiple($keys) 52 | { 53 | foreach ($keys as $key) { 54 | $this->delete($key); 55 | } 56 | 57 | return true; 58 | } 59 | 60 | /** 61 | * {@inheritdoc} 62 | */ 63 | public function get($key, $default = null) 64 | { 65 | if ($this->has($key)) { 66 | return $this->cache[$key]; 67 | } 68 | 69 | return $default; 70 | } 71 | 72 | /** 73 | * {@inheritdoc} 74 | */ 75 | public function getMultiple($keys, $default = null) 76 | { 77 | $results = []; 78 | foreach ($keys as $key) { 79 | $results[$key] = $this->get($key, $default); 80 | } 81 | 82 | return $results; 83 | } 84 | 85 | /** 86 | * {@inheritdoc} 87 | */ 88 | public function has($key) 89 | { 90 | return isset($this->cache[$key]); 91 | } 92 | 93 | /** 94 | * {@inheritdoc} 95 | */ 96 | public function set($key, $value, $ttl = null) 97 | { 98 | $this->cache[$key] = $value; 99 | 100 | return true; 101 | } 102 | 103 | /** 104 | * {@inheritdoc} 105 | */ 106 | public function setMultiple($values, $ttl = null) 107 | { 108 | foreach ($values as $key => $value) { 109 | $this->set($key, $value); 110 | } 111 | 112 | return true; 113 | } 114 | 115 | /** 116 | * @return bool 117 | */ 118 | public function reachedMemoryLimit(): bool 119 | { 120 | // When no limit is given, we'll never reach any limit. 121 | if (null === $this->memoryLimit) { 122 | return false; 123 | } 124 | 125 | return count($this->cache) >= $this->memoryLimit; 126 | } 127 | 128 | /** 129 | * @return array 130 | */ 131 | public function flush(): array 132 | { 133 | $memory = $this->cache; 134 | 135 | foreach ($memory as $cell) { 136 | if ($cell instanceof Cell) { 137 | $cell->detach(); 138 | } 139 | } 140 | 141 | $this->clear(); 142 | 143 | return $memory; 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /src/Cell.php: -------------------------------------------------------------------------------- 1 | cell = $cell; 28 | } 29 | 30 | /** 31 | * @param Worksheet $worksheet 32 | * @param string $coordinate 33 | * @return Cell 34 | * 35 | * @throws \PhpOffice\PhpSpreadsheet\Exception 36 | */ 37 | public static function make(Worksheet $worksheet, string $coordinate) 38 | { 39 | return new static($worksheet->getCell($coordinate)); 40 | } 41 | 42 | /** 43 | * @return SpreadsheetCell 44 | */ 45 | public function getDelegate(): SpreadsheetCell 46 | { 47 | return $this->cell; 48 | } 49 | 50 | /** 51 | * @param null $nullValue 52 | * @param bool $calculateFormulas 53 | * @param bool $formatData 54 | * @return mixed 55 | */ 56 | public function getValue($nullValue = null, $calculateFormulas = false, $formatData = true) 57 | { 58 | $value = $nullValue; 59 | if ($this->cell->getValue() !== null) { 60 | if ($this->cell->getValue() instanceof RichText) { 61 | $value = $this->cell->getValue()->getPlainText(); 62 | } elseif ($calculateFormulas) { 63 | try { 64 | $value = $this->cell->getCalculatedValue(); 65 | } catch (Exception $e) { 66 | $value = $this->cell->getOldCalculatedValue(); 67 | } 68 | } else { 69 | $value = $this->cell->getValue(); 70 | } 71 | 72 | if ($formatData) { 73 | $style = $this->cell->getWorksheet()->getParent()->getCellXfByIndex($this->cell->getXfIndex()); 74 | $value = NumberFormat::toFormattedString( 75 | $value, 76 | ($style && $style->getNumberFormat()) ? $style->getNumberFormat()->getFormatCode() : NumberFormat::FORMAT_GENERAL 77 | ); 78 | } 79 | } 80 | 81 | return app(Pipeline::class)->send($value)->through(config('excel.imports.cells.middleware', []))->thenReturn(); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/ChunkReader.php: -------------------------------------------------------------------------------- 1 | container = $container; 34 | } 35 | 36 | /** 37 | * @param WithChunkReading $import 38 | * @param Reader $reader 39 | * @param TemporaryFile $temporaryFile 40 | * @return PendingDispatch|Collection|null 41 | */ 42 | public function read(WithChunkReading $import, Reader $reader, TemporaryFile $temporaryFile) 43 | { 44 | if ($import instanceof WithEvents) { 45 | $reader->beforeImport($import); 46 | } 47 | 48 | $chunkSize = $import->chunkSize(); 49 | $totalRows = $reader->getTotalRows(); 50 | $worksheets = $reader->getWorksheets($import); 51 | $queue = property_exists($import, 'queue') ? $import->queue : null; 52 | $delayCleanup = property_exists($import, 'cleanupInterval') ? $import->cleanupInterval : 60; 53 | 54 | if ($import instanceof WithProgressBar) { 55 | $import->getConsoleOutput()->progressStart(array_sum($totalRows)); 56 | } 57 | 58 | $jobs = new Collection(); 59 | foreach ($worksheets as $name => $sheetImport) { 60 | $startRow = HeadingRowExtractor::determineStartRow($sheetImport); 61 | 62 | if ($sheetImport instanceof WithLimit) { 63 | $limit = $sheetImport->limit(); 64 | 65 | if ($limit <= $totalRows[$name]) { 66 | $totalRows[$name] = $sheetImport->limit(); 67 | } 68 | } 69 | 70 | for ($currentRow = $startRow; $currentRow <= $totalRows[$name]; $currentRow += $chunkSize) { 71 | $jobs->push(new ReadChunk( 72 | $import, 73 | $reader->getPhpSpreadsheetReader(), 74 | $temporaryFile, 75 | $name, 76 | $sheetImport, 77 | $currentRow, 78 | $chunkSize 79 | )); 80 | } 81 | } 82 | 83 | $afterImportJob = new AfterImportJob($import, $reader); 84 | 85 | if ($import instanceof ShouldQueueWithoutChain) { 86 | $afterImportJob->setInterval($delayCleanup); 87 | $afterImportJob->setDependencies($jobs); 88 | $jobs->push($afterImportJob->delay($delayCleanup)); 89 | 90 | return $jobs->each(function ($job) use ($queue) { 91 | dispatch($job->onQueue($queue)); 92 | }); 93 | } 94 | 95 | $jobs->push($afterImportJob); 96 | 97 | if ($import instanceof ShouldQueue) { 98 | return new PendingDispatch( 99 | (new QueueImport($import))->chain($jobs->toArray()) 100 | ); 101 | } 102 | 103 | $jobs->each(function ($job) { 104 | try { 105 | function_exists('dispatch_now') 106 | ? dispatch_now($job) 107 | : $this->dispatchNow($job); 108 | } catch (Throwable $e) { 109 | if (method_exists($job, 'failed')) { 110 | $job->failed($e); 111 | } 112 | throw $e; 113 | } 114 | }); 115 | 116 | if ($import instanceof WithProgressBar) { 117 | $import->getConsoleOutput()->progressFinish(); 118 | } 119 | 120 | unset($jobs); 121 | 122 | return null; 123 | } 124 | 125 | /** 126 | * Dispatch a command to its appropriate handler in the current process without using the synchronous queue. 127 | * 128 | * @param object $command 129 | * @param mixed $handler 130 | * @return mixed 131 | */ 132 | protected function dispatchNow($command, $handler = null) 133 | { 134 | $uses = class_uses_recursive($command); 135 | 136 | if (in_array(InteractsWithQueue::class, $uses) && 137 | in_array(Queueable::class, $uses) && !$command->job 138 | ) { 139 | $command->setJob(new SyncJob($this->container, json_encode([]), 'sync', 'sync')); 140 | } 141 | 142 | $method = method_exists($command, 'handle') ? 'handle' : '__invoke'; 143 | 144 | return $this->container->call([$command, $method]); 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /src/Concerns/Exportable.php: -------------------------------------------------------------------------------- 1 | headers ?? []; 23 | $fileName = $fileName ?? $this->fileName ?? null; 24 | $writerType = $writerType ?? $this->writerType ?? null; 25 | 26 | if (null === $fileName) { 27 | throw new NoFilenameGivenException(); 28 | } 29 | 30 | return $this->getExporter()->download($this, $fileName, $writerType, $headers); 31 | } 32 | 33 | /** 34 | * @param string $filePath 35 | * @param string|null $disk 36 | * @param string|null $writerType 37 | * @param mixed $diskOptions 38 | * @return bool|PendingDispatch 39 | * 40 | * @throws NoFilePathGivenException 41 | */ 42 | public function store(?string $filePath = null, ?string $disk = null, ?string $writerType = null, $diskOptions = []) 43 | { 44 | $filePath = $filePath ?? $this->filePath ?? null; 45 | 46 | if (null === $filePath) { 47 | throw NoFilePathGivenException::export(); 48 | } 49 | 50 | return $this->getExporter()->store( 51 | $this, 52 | $filePath, 53 | $disk ?? $this->disk ?? null, 54 | $writerType ?? $this->writerType ?? null, 55 | $diskOptions ?: $this->diskOptions ?? [] 56 | ); 57 | } 58 | 59 | /** 60 | * @param string|null $filePath 61 | * @param string|null $disk 62 | * @param string|null $writerType 63 | * @param mixed $diskOptions 64 | * @return PendingDispatch 65 | * 66 | * @throws NoFilePathGivenException 67 | */ 68 | public function queue(?string $filePath = null, ?string $disk = null, ?string $writerType = null, $diskOptions = []) 69 | { 70 | $filePath = $filePath ?? $this->filePath ?? null; 71 | 72 | if (null === $filePath) { 73 | throw NoFilePathGivenException::export(); 74 | } 75 | 76 | return $this->getExporter()->queue( 77 | $this, 78 | $filePath, 79 | $disk ?? $this->disk ?? null, 80 | $writerType ?? $this->writerType ?? null, 81 | $diskOptions ?: $this->diskOptions ?? [] 82 | ); 83 | } 84 | 85 | /** 86 | * @param string|null $writerType 87 | * @return string 88 | */ 89 | public function raw($writerType = null) 90 | { 91 | $writerType = $writerType ?? $this->writerType ?? null; 92 | 93 | return $this->getExporter()->raw($this, $writerType); 94 | } 95 | 96 | /** 97 | * Create an HTTP response that represents the object. 98 | * 99 | * @param \Illuminate\Http\Request $request 100 | * @return \Illuminate\Http\Response 101 | * 102 | * @throws NoFilenameGivenException 103 | */ 104 | public function toResponse($request) 105 | { 106 | return $this->download(); 107 | } 108 | 109 | /** 110 | * @return Exporter 111 | */ 112 | private function getExporter(): Exporter 113 | { 114 | return app(Exporter::class); 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/Concerns/FromArray.php: -------------------------------------------------------------------------------- 1 | getFilePath($filePath); 34 | 35 | return $this->getImporter()->import( 36 | $this, 37 | $filePath, 38 | $disk ?? $this->disk ?? null, 39 | $readerType ?? $this->readerType ?? null 40 | ); 41 | } 42 | 43 | /** 44 | * @param string|UploadedFile|null $filePath 45 | * @param string|null $disk 46 | * @param string|null $readerType 47 | * @return array 48 | * 49 | * @throws NoFilePathGivenException 50 | */ 51 | public function toArray($filePath = null, ?string $disk = null, ?string $readerType = null): array 52 | { 53 | $filePath = $this->getFilePath($filePath); 54 | 55 | return $this->getImporter()->toArray( 56 | $this, 57 | $filePath, 58 | $disk ?? $this->disk ?? null, 59 | $readerType ?? $this->readerType ?? null 60 | ); 61 | } 62 | 63 | /** 64 | * @param string|UploadedFile|null $filePath 65 | * @param string|null $disk 66 | * @param string|null $readerType 67 | * @return Collection 68 | * 69 | * @throws NoFilePathGivenException 70 | */ 71 | public function toCollection($filePath = null, ?string $disk = null, ?string $readerType = null): Collection 72 | { 73 | $filePath = $this->getFilePath($filePath); 74 | 75 | return $this->getImporter()->toCollection( 76 | $this, 77 | $filePath, 78 | $disk ?? $this->disk ?? null, 79 | $readerType ?? $this->readerType ?? null 80 | ); 81 | } 82 | 83 | /** 84 | * @param string|UploadedFile|null $filePath 85 | * @param string|null $disk 86 | * @param string|null $readerType 87 | * @return PendingDispatch 88 | * 89 | * @throws NoFilePathGivenException 90 | * @throws InvalidArgumentException 91 | */ 92 | public function queue($filePath = null, ?string $disk = null, ?string $readerType = null) 93 | { 94 | if (!$this instanceof ShouldQueue) { 95 | throw new InvalidArgumentException('Importable should implement ShouldQueue to be queued.'); 96 | } 97 | 98 | return $this->import($filePath, $disk, $readerType); 99 | } 100 | 101 | /** 102 | * @param OutputStyle $output 103 | * @return $this 104 | */ 105 | public function withOutput(OutputStyle $output) 106 | { 107 | $this->output = $output; 108 | 109 | return $this; 110 | } 111 | 112 | /** 113 | * @return OutputStyle 114 | */ 115 | public function getConsoleOutput(): OutputStyle 116 | { 117 | if (!$this->output instanceof OutputStyle) { 118 | $this->output = new OutputStyle(new StringInput(''), new NullOutput()); 119 | } 120 | 121 | return $this->output; 122 | } 123 | 124 | /** 125 | * @param UploadedFile|string|null $filePath 126 | * @return UploadedFile|string 127 | * 128 | * @throws NoFilePathGivenException 129 | */ 130 | private function getFilePath($filePath = null) 131 | { 132 | $filePath = $filePath ?? $this->filePath ?? null; 133 | 134 | if (null === $filePath) { 135 | throw NoFilePathGivenException::import(); 136 | } 137 | 138 | return $filePath; 139 | } 140 | 141 | /** 142 | * @return Importer 143 | */ 144 | private function getImporter(): Importer 145 | { 146 | return app(Importer::class); 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /src/Concerns/MapsCsvSettings.php: -------------------------------------------------------------------------------- 1 | 'beforeExport', 24 | BeforeWriting::class => 'beforeWriting', 25 | BeforeImport::class => 'beforeImport', 26 | AfterImport::class => 'afterImport', 27 | AfterBatch::class => 'afterBatch', 28 | AfterChunk::class => 'afterChunk', 29 | ImportFailed::class => 'importFailed', 30 | BeforeSheet::class => 'beforeSheet', 31 | AfterSheet::class => 'afterSheet', 32 | ]; 33 | $listeners = []; 34 | 35 | foreach ($listenersClasses as $class => $name) { 36 | // Method names are case insensitive in php 37 | if (method_exists($this, $name)) { 38 | // Allow methods to not be static 39 | $listeners[$class] = [$this, $name]; 40 | } 41 | } 42 | 43 | return $listeners; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Concerns/RemembersChunkOffset.php: -------------------------------------------------------------------------------- 1 | chunkOffset = $chunkOffset; 18 | } 19 | 20 | /** 21 | * @return int|null 22 | */ 23 | public function getChunkOffset() 24 | { 25 | return $this->chunkOffset; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Concerns/RemembersRowNumber.php: -------------------------------------------------------------------------------- 1 | rowNumber = $rowNumber; 18 | } 19 | 20 | /** 21 | * @return int|null 22 | */ 23 | public function getRowNumber() 24 | { 25 | return $this->rowNumber; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Concerns/ShouldAutoSize.php: -------------------------------------------------------------------------------- 1 | errors[] = $e; 21 | } 22 | 23 | /** 24 | * @return Throwable[]|Collection 25 | */ 26 | public function errors(): Collection 27 | { 28 | return new Collection($this->errors); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Concerns/SkipsFailures.php: -------------------------------------------------------------------------------- 1 | failures = array_merge($this->failures, $failures); 21 | } 22 | 23 | /** 24 | * @return Failure[]|Collection 25 | */ 26 | public function failures(): Collection 27 | { 28 | return new Collection($this->failures); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Concerns/SkipsOnError.php: -------------------------------------------------------------------------------- 1 | conditionallySelectedSheets = is_array($sheets) ? $sheets : func_get_args(); 19 | 20 | return $this; 21 | } 22 | 23 | /** 24 | * @return array 25 | */ 26 | public function sheets(): array 27 | { 28 | return \array_filter($this->conditionalSheets(), function ($name) { 29 | return \in_array($name, $this->conditionallySelectedSheets, false); 30 | }, ARRAY_FILTER_USE_KEY); 31 | } 32 | 33 | /** 34 | * @return array 35 | */ 36 | abstract public function conditionalSheets(): array; 37 | } 38 | -------------------------------------------------------------------------------- /src/Concerns/WithCustomChunkSize.php: -------------------------------------------------------------------------------- 1 | count() by the chunk size. 10 | * Depending on the implementation of the query() method (eg. When using a groupBy clause), this calculation might not be correct. 11 | * 12 | * When this is the case, you should use this method to provide a custom calculation of the query size. 13 | * 14 | * @return int 15 | */ 16 | public function querySize(): int; 17 | } 18 | -------------------------------------------------------------------------------- /src/Concerns/WithCustomStartCell.php: -------------------------------------------------------------------------------- 1 | option('model') && $this->option('query')) { 41 | return $this->resolveStubPath('/stubs/export.query-model.stub'); 42 | } elseif ($this->option('model')) { 43 | return $this->resolveStubPath('/stubs/export.model.stub'); 44 | } elseif ($this->option('query')) { 45 | return $this->resolveStubPath('/stubs/export.query.stub'); 46 | } 47 | 48 | return $this->resolveStubPath('/stubs/export.plain.stub'); 49 | } 50 | 51 | /** 52 | * Get the default namespace for the class. 53 | * 54 | * @param string $rootNamespace 55 | * @return string 56 | */ 57 | protected function getDefaultNamespace($rootNamespace) 58 | { 59 | return $rootNamespace . '\Exports'; 60 | } 61 | 62 | /** 63 | * Build the class with the given name. 64 | * Remove the base controller import if we are already in base namespace. 65 | * 66 | * @param string $name 67 | * @return string 68 | */ 69 | protected function buildClass($name) 70 | { 71 | $replace = []; 72 | if ($this->option('model')) { 73 | $replace = $this->buildModelReplacements($replace); 74 | } 75 | 76 | return str_replace( 77 | array_keys($replace), array_values($replace), parent::buildClass($name) 78 | ); 79 | } 80 | 81 | /** 82 | * Get the console command options. 83 | * 84 | * @return array 85 | */ 86 | protected function getOptions() 87 | { 88 | return [ 89 | ['model', 'm', InputOption::VALUE_OPTIONAL, 'Generate an export for the given model.'], 90 | ['query', '', InputOption::VALUE_NONE, 'Generate an export for a query.'], 91 | ]; 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/Console/ImportMakeCommand.php: -------------------------------------------------------------------------------- 1 | option('model') 41 | ? $this->resolveStubPath('/stubs/import.model.stub') 42 | : $this->resolveStubPath('/stubs/import.collection.stub'); 43 | } 44 | 45 | /** 46 | * Get the default namespace for the class. 47 | * 48 | * @param string $rootNamespace 49 | * @return string 50 | */ 51 | protected function getDefaultNamespace($rootNamespace) 52 | { 53 | return $rootNamespace . '\Imports'; 54 | } 55 | 56 | /** 57 | * Build the class with the given name. 58 | * Remove the base controller import if we are already in base namespace. 59 | * 60 | * @param string $name 61 | * @return string 62 | */ 63 | protected function buildClass($name) 64 | { 65 | $replace = []; 66 | if ($this->option('model')) { 67 | $replace = $this->buildModelReplacements($replace); 68 | } 69 | 70 | return str_replace( 71 | array_keys($replace), array_values($replace), parent::buildClass($name) 72 | ); 73 | } 74 | 75 | /** 76 | * Get the console command options. 77 | * 78 | * @return array 79 | */ 80 | protected function getOptions() 81 | { 82 | return [ 83 | ['model', 'm', InputOption::VALUE_OPTIONAL, 'Generate an import for the given model.'], 84 | ]; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/Console/WithModelStub.php: -------------------------------------------------------------------------------- 1 | parseModel($this->option('model')); 19 | 20 | return array_merge($replace, [ 21 | 'DummyFullModelClass' => $modelClass, 22 | 'DummyModelClass' => class_basename($modelClass), 23 | ]); 24 | } 25 | 26 | /** 27 | * Get the fully-qualified model class name. 28 | * 29 | * @param string $model 30 | * @return string 31 | */ 32 | protected function parseModel($model): string 33 | { 34 | if (preg_match('([^A-Za-z0-9_/\\\\])', $model)) { 35 | throw new InvalidArgumentException('Model name contains invalid characters.'); 36 | } 37 | 38 | $model = ltrim($model, '\\/'); 39 | 40 | $model = str_replace('/', '\\', $model); 41 | 42 | $rootNamespace = $this->rootNamespace(); 43 | 44 | if (Str::startsWith($model, $rootNamespace)) { 45 | return $model; 46 | } 47 | 48 | $model = is_dir(app_path('Models')) 49 | ? $rootNamespace . 'Models\\' . $model 50 | : $rootNamespace . $model; 51 | 52 | return $model; 53 | } 54 | 55 | /** 56 | * Resolve the fully-qualified path to the stub. 57 | * 58 | * @param string $stub 59 | * @return string 60 | */ 61 | protected function resolveStubPath($stub) 62 | { 63 | return file_exists($customPath = $this->laravel->basePath(trim($stub, '/'))) 64 | ? $customPath 65 | : __DIR__ . $stub; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/Console/stubs/export.model.stub: -------------------------------------------------------------------------------- 1 | getDelegate(), $method)) { 23 | return call_user_func_array([$this->getDelegate(), $method], $parameters); 24 | } 25 | 26 | array_unshift($parameters, $this); 27 | 28 | return $this->__callMacro($method, $parameters); 29 | } 30 | 31 | /** 32 | * @return object 33 | */ 34 | abstract public function getDelegate(); 35 | } 36 | -------------------------------------------------------------------------------- /src/Events/AfterBatch.php: -------------------------------------------------------------------------------- 1 | manager = $manager; 33 | $this->batchSize = $batchSize; 34 | $this->startRow = $startRow; 35 | parent::__construct($importable); 36 | } 37 | 38 | public function getManager(): ModelManager 39 | { 40 | return $this->manager; 41 | } 42 | 43 | /** 44 | * @return mixed 45 | */ 46 | public function getDelegate() 47 | { 48 | return $this->manager; 49 | } 50 | 51 | public function getBatchSize(): int 52 | { 53 | return $this->batchSize; 54 | } 55 | 56 | public function getStartRow(): int 57 | { 58 | return $this->startRow; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/Events/AfterChunk.php: -------------------------------------------------------------------------------- 1 | sheet = $sheet; 22 | $this->startRow = $startRow; 23 | parent::__construct($importable); 24 | } 25 | 26 | public function getSheet(): Sheet 27 | { 28 | return $this->sheet; 29 | } 30 | 31 | public function getDelegate() 32 | { 33 | return $this->sheet; 34 | } 35 | 36 | public function getStartRow(): int 37 | { 38 | return $this->startRow; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Events/AfterImport.php: -------------------------------------------------------------------------------- 1 | reader = $reader; 21 | parent::__construct($importable); 22 | } 23 | 24 | /** 25 | * @return Reader 26 | */ 27 | public function getReader(): Reader 28 | { 29 | return $this->reader; 30 | } 31 | 32 | /** 33 | * @return mixed 34 | */ 35 | public function getDelegate() 36 | { 37 | return $this->reader; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Events/AfterSheet.php: -------------------------------------------------------------------------------- 1 | sheet = $sheet; 21 | parent::__construct($exportable); 22 | } 23 | 24 | /** 25 | * @return Sheet 26 | */ 27 | public function getSheet(): Sheet 28 | { 29 | return $this->sheet; 30 | } 31 | 32 | /** 33 | * @return mixed 34 | */ 35 | public function getDelegate() 36 | { 37 | return $this->sheet; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Events/BeforeExport.php: -------------------------------------------------------------------------------- 1 | writer = $writer; 21 | parent::__construct($exportable); 22 | } 23 | 24 | /** 25 | * @return Writer 26 | */ 27 | public function getWriter(): Writer 28 | { 29 | return $this->writer; 30 | } 31 | 32 | /** 33 | * @return mixed 34 | */ 35 | public function getDelegate() 36 | { 37 | return $this->writer; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Events/BeforeImport.php: -------------------------------------------------------------------------------- 1 | reader = $reader; 21 | parent::__construct($importable); 22 | } 23 | 24 | /** 25 | * @return Reader 26 | */ 27 | public function getReader(): Reader 28 | { 29 | return $this->reader; 30 | } 31 | 32 | /** 33 | * @return mixed 34 | */ 35 | public function getDelegate() 36 | { 37 | return $this->reader; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Events/BeforeSheet.php: -------------------------------------------------------------------------------- 1 | sheet = $sheet; 21 | parent::__construct($exportable); 22 | } 23 | 24 | /** 25 | * @return Sheet 26 | */ 27 | public function getSheet(): Sheet 28 | { 29 | return $this->sheet; 30 | } 31 | 32 | /** 33 | * @return mixed 34 | */ 35 | public function getDelegate() 36 | { 37 | return $this->sheet; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Events/BeforeWriting.php: -------------------------------------------------------------------------------- 1 | writer = $writer; 26 | parent::__construct($exportable); 27 | } 28 | 29 | /** 30 | * @return Writer 31 | */ 32 | public function getWriter(): Writer 33 | { 34 | return $this->writer; 35 | } 36 | 37 | /** 38 | * @return mixed 39 | */ 40 | public function getDelegate() 41 | { 42 | return $this->writer; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Events/Event.php: -------------------------------------------------------------------------------- 1 | concernable = $concernable; 21 | } 22 | 23 | /** 24 | * @return object 25 | */ 26 | public function getConcernable() 27 | { 28 | return $this->concernable; 29 | } 30 | 31 | /** 32 | * @return mixed 33 | */ 34 | abstract public function getDelegate(); 35 | 36 | /** 37 | * @param string $concern 38 | * @return bool 39 | */ 40 | public function appliesToConcern(string $concern): bool 41 | { 42 | return $this->getConcernable() instanceof $concern; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Events/ImportFailed.php: -------------------------------------------------------------------------------- 1 | e = $e; 20 | } 21 | 22 | /** 23 | * @return Throwable 24 | */ 25 | public function getException(): Throwable 26 | { 27 | return $this->e; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Excel.php: -------------------------------------------------------------------------------- 1 | writer = $writer; 74 | $this->reader = $reader; 75 | $this->filesystem = $filesystem; 76 | $this->queuedWriter = $queuedWriter; 77 | } 78 | 79 | /** 80 | * {@inheritdoc} 81 | */ 82 | public function download($export, string $fileName, ?string $writerType = null, array $headers = []) 83 | { 84 | // Clear output buffer to prevent stuff being prepended to the Excel output. 85 | if (ob_get_length() > 0) { 86 | ob_end_clean(); 87 | ob_start(); 88 | } 89 | 90 | return response()->download( 91 | $this->export($export, $fileName, $writerType)->getLocalPath(), 92 | $fileName, 93 | $headers 94 | )->deleteFileAfterSend(true); 95 | } 96 | 97 | /** 98 | * {@inheritdoc} 99 | * 100 | * @param string|null $disk Fallback for usage with named properties 101 | */ 102 | public function store($export, string $filePath, ?string $diskName = null, ?string $writerType = null, $diskOptions = [], ?string $disk = null) 103 | { 104 | if ($export instanceof ShouldQueue) { 105 | return $this->queue($export, $filePath, $diskName ?: $disk, $writerType, $diskOptions); 106 | } 107 | 108 | $temporaryFile = $this->export($export, $filePath, $writerType); 109 | 110 | $exported = $this->filesystem->disk($diskName ?: $disk, $diskOptions)->copy( 111 | $temporaryFile, 112 | $filePath 113 | ); 114 | 115 | $temporaryFile->delete(); 116 | 117 | return $exported; 118 | } 119 | 120 | /** 121 | * {@inheritdoc} 122 | */ 123 | public function queue($export, string $filePath, ?string $disk = null, ?string $writerType = null, $diskOptions = []) 124 | { 125 | $writerType = FileTypeDetector::detectStrict($filePath, $writerType); 126 | 127 | return $this->queuedWriter->store( 128 | $export, 129 | $filePath, 130 | $disk, 131 | $writerType, 132 | $diskOptions 133 | ); 134 | } 135 | 136 | /** 137 | * {@inheritdoc} 138 | */ 139 | public function raw($export, string $writerType) 140 | { 141 | $temporaryFile = $this->writer->export($export, $writerType); 142 | 143 | $contents = $temporaryFile->contents(); 144 | $temporaryFile->delete(); 145 | 146 | return $contents; 147 | } 148 | 149 | /** 150 | * {@inheritdoc} 151 | */ 152 | public function import($import, $filePath, ?string $disk = null, ?string $readerType = null) 153 | { 154 | $readerType = FileTypeDetector::detect($filePath, $readerType); 155 | $response = $this->reader->read($import, $filePath, $readerType, $disk); 156 | 157 | if ($response instanceof PendingDispatch) { 158 | return $response; 159 | } 160 | 161 | return $this; 162 | } 163 | 164 | /** 165 | * {@inheritdoc} 166 | */ 167 | public function toArray($import, $filePath, ?string $disk = null, ?string $readerType = null): array 168 | { 169 | $readerType = FileTypeDetector::detect($filePath, $readerType); 170 | 171 | return $this->reader->toArray($import, $filePath, $readerType, $disk); 172 | } 173 | 174 | /** 175 | * {@inheritdoc} 176 | */ 177 | public function toCollection($import, $filePath, ?string $disk = null, ?string $readerType = null): Collection 178 | { 179 | $readerType = FileTypeDetector::detect($filePath, $readerType); 180 | 181 | return $this->reader->toCollection($import, $filePath, $readerType, $disk); 182 | } 183 | 184 | /** 185 | * {@inheritdoc} 186 | */ 187 | public function queueImport(ShouldQueue $import, $filePath, ?string $disk = null, ?string $readerType = null) 188 | { 189 | return $this->import($import, $filePath, $disk, $readerType); 190 | } 191 | 192 | /** 193 | * @param object $export 194 | * @param string|null $fileName 195 | * @param string $writerType 196 | * @return TemporaryFile 197 | * 198 | * @throws \PhpOffice\PhpSpreadsheet\Exception 199 | */ 200 | protected function export($export, string $fileName, ?string $writerType = null): TemporaryFile 201 | { 202 | $writerType = FileTypeDetector::detectStrict($fileName, $writerType); 203 | 204 | return $this->writer->export($export, $writerType); 205 | } 206 | } 207 | -------------------------------------------------------------------------------- /src/ExcelServiceProvider.php: -------------------------------------------------------------------------------- 1 | app->runningInConsole()) { 31 | $this->publishes([ 32 | __DIR__ . '/Console/stubs/export.model.stub' => base_path('stubs/export.model.stub'), 33 | __DIR__ . '/Console/stubs/export.plain.stub' => base_path('stubs/export.plain.stub'), 34 | __DIR__ . '/Console/stubs/export.query.stub' => base_path('stubs/export.query.stub'), 35 | __DIR__ . '/Console/stubs/export.query-model.stub' => base_path('stubs/export.query-model.stub'), 36 | __DIR__ . '/Console/stubs/import.collection.stub' => base_path('stubs/import.collection.stub'), 37 | __DIR__ . '/Console/stubs/import.model.stub' => base_path('stubs/import.model.stub'), 38 | ], 'stubs'); 39 | 40 | if ($this->app instanceof LumenApplication) { 41 | $this->app->configure('excel'); 42 | } else { 43 | $this->publishes([ 44 | $this->getConfigFile() => config_path('excel.php'), 45 | ], 'config'); 46 | } 47 | } 48 | 49 | if ($this->app instanceof \Illuminate\Foundation\Application) { 50 | // Laravel 51 | $this->app->booted(function ($app) { 52 | $app->make(SettingsProvider::class)->provide(); 53 | }); 54 | } else { 55 | // Lumen 56 | $this->app->make(SettingsProvider::class)->provide(); 57 | } 58 | } 59 | 60 | /** 61 | * {@inheritdoc} 62 | */ 63 | public function register() 64 | { 65 | $this->mergeConfigFrom( 66 | $this->getConfigFile(), 67 | 'excel' 68 | ); 69 | 70 | $this->app->bind(CacheManager::class, function ($app) { 71 | return new CacheManager($app); 72 | }); 73 | 74 | $this->app->singleton(TransactionManager::class, function ($app) { 75 | return new TransactionManager($app); 76 | }); 77 | 78 | $this->app->bind(TransactionHandler::class, function ($app) { 79 | return $app->make(TransactionManager::class)->driver(); 80 | }); 81 | 82 | $this->app->bind(TemporaryFileFactory::class, function () { 83 | return new TemporaryFileFactory( 84 | config('excel.temporary_files.local_path', config('excel.exports.temp_path', storage_path('framework/laravel-excel'))), 85 | config('excel.temporary_files.remote_disk') 86 | ); 87 | }); 88 | 89 | $this->app->bind(Filesystem::class, function ($app) { 90 | return new Filesystem($app->make('filesystem')); 91 | }); 92 | 93 | $this->app->bind('excel', function ($app) { 94 | return new Excel( 95 | $app->make(Writer::class), 96 | $app->make(QueuedWriter::class), 97 | $app->make(Reader::class), 98 | $app->make(Filesystem::class) 99 | ); 100 | }); 101 | 102 | $this->app->alias('excel', Excel::class); 103 | $this->app->alias('excel', Exporter::class); 104 | $this->app->alias('excel', Importer::class); 105 | 106 | Collection::mixin(new DownloadCollectionMixin); 107 | Collection::mixin(new StoreCollectionMixin); 108 | Builder::macro('downloadExcel', (new DownloadQueryMacro)()); 109 | Builder::macro('storeExcel', (new StoreQueryMacro())()); 110 | Builder::macro('import', (new ImportMacro())()); 111 | Builder::macro('importAs', (new ImportAsMacro())()); 112 | 113 | $this->commands([ 114 | ExportMakeCommand::class, 115 | ImportMakeCommand::class, 116 | ]); 117 | } 118 | 119 | /** 120 | * @return string 121 | */ 122 | protected function getConfigFile(): string 123 | { 124 | return __DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'config' . DIRECTORY_SEPARATOR . 'excel.php'; 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /src/Exceptions/ConcernConflictException.php: -------------------------------------------------------------------------------- 1 | failures = $failures; 22 | 23 | parent::__construct(); 24 | } 25 | 26 | /** 27 | * @return Failure[]|Collection 28 | */ 29 | public function failures(): Collection 30 | { 31 | return new Collection($this->failures); 32 | } 33 | 34 | /** 35 | * @return int[] 36 | */ 37 | public function skippedRows(): array 38 | { 39 | return $this->failures()->map->row()->all(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Exceptions/SheetNotFoundException.php: -------------------------------------------------------------------------------- 1 | setReadDataOnly(config('excel.imports.read_only', true)); 38 | } 39 | 40 | if (method_exists($reader, 'setReadEmptyCells')) { 41 | $reader->setReadEmptyCells(!config('excel.imports.ignore_empty', false)); 42 | } 43 | 44 | if ($reader instanceof Csv) { 45 | static::applyCsvSettings(config('excel.imports.csv', [])); 46 | 47 | if ($import instanceof WithCustomCsvSettings) { 48 | static::applyCsvSettings($import->getCsvSettings()); 49 | } 50 | 51 | $reader->setDelimiter(static::$delimiter); 52 | $reader->setEnclosure(static::$enclosure); 53 | $reader->setEscapeCharacter(static::$escapeCharacter); 54 | $reader->setContiguous(static::$contiguous); 55 | $reader->setInputEncoding(static::$inputEncoding); 56 | if (method_exists($reader, 'setTestAutoDetect')) { 57 | $reader->setTestAutoDetect(static::$testAutoDetect); 58 | } 59 | } 60 | 61 | if ($import instanceof WithReadFilter) { 62 | $reader->setReadFilter($import->readFilter()); 63 | } elseif ($import instanceof WithLimit) { 64 | $reader->setReadFilter(new LimitFilter( 65 | $import instanceof WithStartRow ? $import->startRow() : 1, 66 | $import->limit() 67 | )); 68 | } 69 | 70 | return $reader; 71 | } 72 | 73 | /** 74 | * @param TemporaryFile $temporaryFile 75 | * @return string 76 | * 77 | * @throws NoTypeDetectedException 78 | */ 79 | private static function identify(TemporaryFile $temporaryFile): string 80 | { 81 | try { 82 | return IOFactory::identify($temporaryFile->getLocalPath()); 83 | } catch (Exception $e) { 84 | throw new NoTypeDetectedException('', 0, $e); 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/Factories/WriterFactory.php: -------------------------------------------------------------------------------- 1 | setUseDiskCaching( 34 | config('excel.cache.driver', CacheManager::DRIVER_MEMORY) !== CacheManager::DRIVER_MEMORY 35 | ); 36 | 37 | if (static::includesCharts($export)) { 38 | $writer->setIncludeCharts(true); 39 | } 40 | 41 | if ($writer instanceof Html && $export instanceof WithMultipleSheets) { 42 | $writer->writeAllSheets(); 43 | } 44 | 45 | if ($writer instanceof Csv) { 46 | static::applyCsvSettings(config('excel.exports.csv', [])); 47 | 48 | if ($export instanceof WithCustomCsvSettings) { 49 | static::applyCsvSettings($export->getCsvSettings()); 50 | } 51 | 52 | $writer->setDelimiter(static::$delimiter); 53 | $writer->setEnclosure(static::$enclosure); 54 | $writer->setEnclosureRequired((bool) static::$enclosure); 55 | $writer->setLineEnding(static::$lineEnding); 56 | $writer->setUseBOM(static::$useBom); 57 | $writer->setIncludeSeparatorLine(static::$includeSeparatorLine); 58 | $writer->setExcelCompatibility(static::$excelCompatibility); 59 | $writer->setOutputEncoding(static::$outputEncoding); 60 | } 61 | 62 | // Calculation settings 63 | $writer->setPreCalculateFormulas( 64 | $export instanceof WithPreCalculateFormulas 65 | ? true 66 | : config('excel.exports.pre_calculate_formulas', false) 67 | ); 68 | 69 | return $writer; 70 | } 71 | 72 | /** 73 | * @param $export 74 | * @return bool 75 | */ 76 | private static function includesCharts($export): bool 77 | { 78 | if ($export instanceof WithCharts) { 79 | return true; 80 | } 81 | 82 | if ($export instanceof WithMultipleSheets) { 83 | foreach ($export->sheets() as $sheet) { 84 | if ($sheet instanceof WithCharts) { 85 | return true; 86 | } 87 | } 88 | } 89 | 90 | return false; 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/Fakes/fake_file: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpartnerNL/Laravel-Excel/e25d44a2d91da9179cd2d7fec952313548597a79/src/Fakes/fake_file -------------------------------------------------------------------------------- /src/Files/Disk.php: -------------------------------------------------------------------------------- 1 | disk = $disk; 38 | $this->name = $name; 39 | $this->diskOptions = $diskOptions; 40 | } 41 | 42 | /** 43 | * @param string $name 44 | * @param array $arguments 45 | * @return mixed 46 | */ 47 | public function __call($name, $arguments) 48 | { 49 | return $this->disk->{$name}(...$arguments); 50 | } 51 | 52 | /** 53 | * @param string $destination 54 | * @param string|resource $contents 55 | * @return bool 56 | */ 57 | public function put(string $destination, $contents): bool 58 | { 59 | return $this->disk->put($destination, $contents, $this->diskOptions); 60 | } 61 | 62 | /** 63 | * @param TemporaryFile $source 64 | * @param string $destination 65 | * @return bool 66 | */ 67 | public function copy(TemporaryFile $source, string $destination): bool 68 | { 69 | $readStream = $source->readStream(); 70 | 71 | if (realpath($destination)) { 72 | $tempStream = fopen($destination, 'rb+'); 73 | $success = stream_copy_to_stream($readStream, $tempStream) !== false; 74 | 75 | if (is_resource($tempStream)) { 76 | fclose($tempStream); 77 | } 78 | } else { 79 | $success = $this->put($destination, $readStream); 80 | } 81 | 82 | if (is_resource($readStream)) { 83 | fclose($readStream); 84 | } 85 | 86 | return $success; 87 | } 88 | 89 | /** 90 | * @param string $filename 91 | */ 92 | public function touch(string $filename) 93 | { 94 | $this->disk->put($filename, '', $this->diskOptions); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/Files/Filesystem.php: -------------------------------------------------------------------------------- 1 | filesystem = $filesystem; 20 | } 21 | 22 | /** 23 | * @param string|null $disk 24 | * @param array $diskOptions 25 | * @return Disk 26 | */ 27 | public function disk(?string $disk = null, array $diskOptions = []): Disk 28 | { 29 | return new Disk( 30 | $this->filesystem->disk($disk), 31 | $disk, 32 | $diskOptions 33 | ); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Files/LocalTemporaryFile.php: -------------------------------------------------------------------------------- 1 | filePath = realpath($filePath); 23 | } 24 | 25 | /** 26 | * @return string 27 | */ 28 | public function getLocalPath(): string 29 | { 30 | return $this->filePath; 31 | } 32 | 33 | /** 34 | * @return bool 35 | */ 36 | public function exists(): bool 37 | { 38 | return file_exists($this->filePath); 39 | } 40 | 41 | /** 42 | * @return bool 43 | */ 44 | public function delete(): bool 45 | { 46 | if (@unlink($this->filePath) || !$this->exists()) { 47 | return true; 48 | } 49 | 50 | return unlink($this->filePath); 51 | } 52 | 53 | /** 54 | * @return resource 55 | */ 56 | public function readStream() 57 | { 58 | return fopen($this->getLocalPath(), 'rb+'); 59 | } 60 | 61 | /** 62 | * @return string 63 | */ 64 | public function contents(): string 65 | { 66 | return file_get_contents($this->filePath); 67 | } 68 | 69 | /** 70 | * @param @param string|resource $contents 71 | */ 72 | public function put($contents) 73 | { 74 | file_put_contents($this->filePath, $contents); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/Files/RemoteTemporaryFile.php: -------------------------------------------------------------------------------- 1 | disk = $disk; 37 | $this->filename = $filename; 38 | $this->localTemporaryFile = $localTemporaryFile; 39 | 40 | $this->disk()->touch($filename); 41 | } 42 | 43 | public function __sleep() 44 | { 45 | return ['disk', 'filename', 'localTemporaryFile']; 46 | } 47 | 48 | /** 49 | * @return string 50 | */ 51 | public function getLocalPath(): string 52 | { 53 | return $this->localTemporaryFile->getLocalPath(); 54 | } 55 | 56 | /** 57 | * @return bool 58 | */ 59 | public function existsLocally(): bool 60 | { 61 | return $this->localTemporaryFile->exists(); 62 | } 63 | 64 | /** 65 | * @return bool 66 | */ 67 | public function exists(): bool 68 | { 69 | return $this->disk()->exists($this->filename); 70 | } 71 | 72 | /** 73 | * @return bool 74 | */ 75 | public function deleteLocalCopy(): bool 76 | { 77 | return $this->localTemporaryFile->delete(); 78 | } 79 | 80 | /** 81 | * @return bool 82 | */ 83 | public function delete(): bool 84 | { 85 | // we don't need to delete local copy as it's deleted at end of each chunk 86 | if (!config('excel.temporary_files.force_resync_remote')) { 87 | $this->deleteLocalCopy(); 88 | } 89 | 90 | return $this->disk()->delete($this->filename); 91 | } 92 | 93 | /** 94 | * @return TemporaryFile 95 | */ 96 | public function sync(): TemporaryFile 97 | { 98 | if (!$this->localTemporaryFile->exists()) { 99 | $this->localTemporaryFile = resolve(TemporaryFileFactory::class) 100 | ->makeLocal(Arr::last(explode('/', $this->filename))); 101 | } 102 | 103 | $this->disk()->copy( 104 | $this, 105 | $this->localTemporaryFile->getLocalPath() 106 | ); 107 | 108 | return $this; 109 | } 110 | 111 | /** 112 | * Store on remote disk. 113 | */ 114 | public function updateRemote() 115 | { 116 | $this->disk()->copy( 117 | $this->localTemporaryFile, 118 | $this->filename 119 | ); 120 | } 121 | 122 | /** 123 | * @return resource 124 | */ 125 | public function readStream() 126 | { 127 | return $this->disk()->readStream($this->filename); 128 | } 129 | 130 | /** 131 | * @return string 132 | */ 133 | public function contents(): string 134 | { 135 | return $this->disk()->get($this->filename); 136 | } 137 | 138 | /** 139 | * @param string|resource $contents 140 | */ 141 | public function put($contents) 142 | { 143 | $this->disk()->put($this->filename, $contents); 144 | } 145 | 146 | /** 147 | * @return Disk 148 | */ 149 | public function disk(): Disk 150 | { 151 | return $this->diskInstance ?: $this->diskInstance = app(Filesystem::class)->disk($this->disk); 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /src/Files/TemporaryFile.php: -------------------------------------------------------------------------------- 1 | getRealPath(), 'rb'); 57 | } elseif ($disk === null && realpath($filePath) !== false) { 58 | $readStream = fopen($filePath, 'rb'); 59 | } else { 60 | $diskInstance = app('filesystem')->disk($disk); 61 | 62 | if (!$diskInstance->exists($filePath)) { 63 | $logPath = '[' . $filePath . ']'; 64 | 65 | if ($disk) { 66 | $logPath .= ' (' . $disk . ')'; 67 | } 68 | 69 | throw new FileNotFoundException('File ' . $logPath . ' does not exist and can therefore not be imported.'); 70 | } 71 | 72 | $readStream = $diskInstance->readStream($filePath); 73 | } 74 | 75 | $this->put($readStream); 76 | 77 | if (is_resource($readStream)) { 78 | fclose($readStream); 79 | } 80 | 81 | return $this->sync(); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/Files/TemporaryFileFactory.php: -------------------------------------------------------------------------------- 1 | temporaryPath = $temporaryPath; 26 | $this->temporaryDisk = $temporaryDisk; 27 | } 28 | 29 | /** 30 | * @param string|null $fileExtension 31 | * @return TemporaryFile 32 | */ 33 | public function make(?string $fileExtension = null): TemporaryFile 34 | { 35 | if (null !== $this->temporaryDisk) { 36 | return $this->makeRemote($fileExtension); 37 | } 38 | 39 | return $this->makeLocal(null, $fileExtension); 40 | } 41 | 42 | /** 43 | * @param string|null $fileName 44 | * @param string|null $fileExtension 45 | * @return LocalTemporaryFile 46 | */ 47 | public function makeLocal(?string $fileName = null, ?string $fileExtension = null): LocalTemporaryFile 48 | { 49 | if (!file_exists($this->temporaryPath) && !mkdir($concurrentDirectory = $this->temporaryPath, config('excel.temporary_files.local_permissions.dir', 0777), true) && !is_dir($concurrentDirectory)) { 50 | throw new \RuntimeException(sprintf('Directory "%s" was not created', $concurrentDirectory)); 51 | } 52 | 53 | return new LocalTemporaryFile( 54 | $this->temporaryPath . DIRECTORY_SEPARATOR . ($fileName ?: $this->generateFilename($fileExtension)) 55 | ); 56 | } 57 | 58 | /** 59 | * @param string|null $fileExtension 60 | * @return RemoteTemporaryFile 61 | */ 62 | private function makeRemote(?string $fileExtension = null): RemoteTemporaryFile 63 | { 64 | $filename = $this->generateFilename($fileExtension); 65 | 66 | return new RemoteTemporaryFile( 67 | $this->temporaryDisk, 68 | config('excel.temporary_files.remote_prefix') . $filename, 69 | $this->makeLocal($filename) 70 | ); 71 | } 72 | 73 | /** 74 | * @param string|null $fileExtension 75 | * @return string 76 | */ 77 | private function generateFilename(?string $fileExtension = null): string 78 | { 79 | return 'laravel-excel-' . Str::random(32) . ($fileExtension ? '.' . $fileExtension : ''); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/Filters/ChunkReadFilter.php: -------------------------------------------------------------------------------- 1 | headingRow = $headingRow; 38 | $this->startRow = $startRow; 39 | $this->endRow = $startRow + $chunkSize; 40 | $this->worksheetName = $worksheetName; 41 | } 42 | 43 | /** 44 | * @param string $column 45 | * @param int $row 46 | * @param string $worksheetName 47 | * @return bool 48 | */ 49 | public function readCell($column, $row, $worksheetName = '') 50 | { 51 | // Only read the heading row, and the rows that are configured in $this->_startRow and $this->_endRow 52 | return ($worksheetName === $this->worksheetName || $worksheetName === '') 53 | && ($row === $this->headingRow || ($row >= $this->startRow && $row < $this->endRow)); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/Filters/LimitFilter.php: -------------------------------------------------------------------------------- 1 | startRow = $startRow; 26 | $this->endRow = $startRow + $limit; 27 | } 28 | 29 | /** 30 | * @param string $column 31 | * @param int $row 32 | * @param string $worksheetName 33 | * @return bool 34 | */ 35 | public function readCell($column, $row, $worksheetName = '') 36 | { 37 | return $row >= $this->startRow && $row < $this->endRow; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/HasEventBus.php: -------------------------------------------------------------------------------- 1 | $listener) { 25 | $this->events[$event][] = $listener; 26 | } 27 | } 28 | 29 | public function clearListeners() 30 | { 31 | $this->events = []; 32 | } 33 | 34 | /** 35 | * Register a global event listener. 36 | * 37 | * @param string $event 38 | * @param callable $listener 39 | */ 40 | public static function listen(string $event, callable $listener) 41 | { 42 | static::$globalEvents[$event][] = $listener; 43 | } 44 | 45 | /** 46 | * @param object $event 47 | */ 48 | public function raise($event) 49 | { 50 | foreach ($this->listeners($event) as $listener) { 51 | $listener($event); 52 | } 53 | } 54 | 55 | /** 56 | * @param object $event 57 | * @return callable[] 58 | */ 59 | public function listeners($event): array 60 | { 61 | $name = \get_class($event); 62 | 63 | $localListeners = $this->events[$name] ?? []; 64 | $globalListeners = static::$globalEvents[$name] ?? []; 65 | 66 | return array_merge($globalListeners, $localListeners); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/HeadingRowImport.php: -------------------------------------------------------------------------------- 1 | headingRow = $headingRow; 26 | } 27 | 28 | /** 29 | * @return int 30 | */ 31 | public function startRow(): int 32 | { 33 | return $this->headingRow; 34 | } 35 | 36 | /** 37 | * @return int 38 | */ 39 | public function limit(): int 40 | { 41 | return 1; 42 | } 43 | 44 | /** 45 | * @param mixed $row 46 | * @return array 47 | */ 48 | public function map($row): array 49 | { 50 | return HeadingRowFormatter::format($row); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/Helpers/ArrayHelper.php: -------------------------------------------------------------------------------- 1 | getClientOriginalExtension(); 28 | } 29 | 30 | if (null === $type && trim($extension) === '') { 31 | throw new NoTypeDetectedException(); 32 | } 33 | 34 | return config('excel.extension_detector.' . strtolower($extension)); 35 | } 36 | 37 | /** 38 | * @param string $filePath 39 | * @param string|null $type 40 | * @return string 41 | * 42 | * @throws NoTypeDetectedException 43 | */ 44 | public static function detectStrict(string $filePath, ?string $type = null): string 45 | { 46 | $type = static::detect($filePath, $type); 47 | 48 | if (!$type) { 49 | throw new NoTypeDetectedException(); 50 | } 51 | 52 | return $type; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/Importer.php: -------------------------------------------------------------------------------- 1 | limit(); 22 | 23 | if ($limit > $highestRow) { 24 | return null; 25 | } 26 | 27 | // When no start row given, 28 | // use the first row as start row. 29 | $startRow = $startRow ?? 1; 30 | 31 | // Subtract 1 row from the start row, so a limit 32 | // of 1 row, will have the same start and end row. 33 | return ($startRow - 1) + $limit; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Imports/HeadingRowExtractor.php: -------------------------------------------------------------------------------- 1 | headingRow() 27 | : self::DEFAULT_HEADING_ROW; 28 | } 29 | 30 | /** 31 | * @param WithHeadingRow|mixed $importable 32 | * @return int 33 | */ 34 | public static function determineStartRow($importable): int 35 | { 36 | if ($importable instanceof WithStartRow) { 37 | return $importable->startRow(); 38 | } 39 | 40 | // The start row is the row after the heading row if we have one! 41 | return $importable instanceof WithHeadingRow 42 | ? self::headingRow($importable) + 1 43 | : self::DEFAULT_HEADING_ROW; 44 | } 45 | 46 | /** 47 | * @param Worksheet $worksheet 48 | * @param WithHeadingRow|mixed $importable 49 | * @return array 50 | */ 51 | public static function extract(Worksheet $worksheet, $importable): array 52 | { 53 | if (!$importable instanceof WithHeadingRow) { 54 | return []; 55 | } 56 | 57 | $headingRowNumber = self::headingRow($importable); 58 | $rows = iterator_to_array($worksheet->getRowIterator($headingRowNumber, $headingRowNumber)); 59 | $headingRow = head($rows); 60 | $endColumn = $importable instanceof WithColumnLimit ? $importable->endColumn() : null; 61 | 62 | return HeadingRowFormatter::format((new Row($headingRow))->toArray(null, false, false, $endColumn)); 63 | } 64 | 65 | /** 66 | * @param array $headingRow 67 | * @param WithGroupedHeadingRow|mixed $importable 68 | * @return array 69 | */ 70 | public static function extractGrouping($headingRow, $importable) 71 | { 72 | $headerIsGrouped = array_fill(0, count($headingRow), false); 73 | 74 | if (!$importable instanceof WithGroupedHeadingRow) { 75 | return $headerIsGrouped; 76 | } 77 | 78 | array_walk($headerIsGrouped, function (&$value, $key) use ($headingRow) { 79 | if (array_count_values($headingRow)[$headingRow[$key]] > 1) { 80 | $value = true; 81 | } 82 | }); 83 | 84 | return $headerIsGrouped; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/Imports/HeadingRowFormatter.php: -------------------------------------------------------------------------------- 1 | map(function ($value, $key) { 46 | return static::callFormatter($value, $key); 47 | })->toArray(); 48 | } 49 | 50 | /** 51 | * @param string $name 52 | */ 53 | public static function default(?string $name = null) 54 | { 55 | if (null !== $name && !isset(static::$customFormatters[$name]) && !in_array($name, static::$defaultFormatters, true)) { 56 | throw new InvalidArgumentException(sprintf('Formatter "%s" does not exist', $name)); 57 | } 58 | 59 | static::$formatter = $name; 60 | } 61 | 62 | /** 63 | * @param string $name 64 | * @param callable $formatter 65 | */ 66 | public static function extend(string $name, callable $formatter) 67 | { 68 | static::$customFormatters[$name] = $formatter; 69 | } 70 | 71 | /** 72 | * Reset the formatter. 73 | */ 74 | public static function reset() 75 | { 76 | static::default(); 77 | } 78 | 79 | /** 80 | * @param mixed $value 81 | * @return mixed 82 | */ 83 | protected static function callFormatter($value, $key=null) 84 | { 85 | static::$formatter = static::$formatter ?? config('excel.imports.heading_row.formatter', self::FORMATTER_SLUG); 86 | 87 | // Call custom formatter 88 | if (isset(static::$customFormatters[static::$formatter])) { 89 | $formatter = static::$customFormatters[static::$formatter]; 90 | 91 | return $formatter($value, $key); 92 | } 93 | 94 | if (empty($value)) { 95 | return $key; 96 | } 97 | 98 | if (static::$formatter === self::FORMATTER_SLUG) { 99 | return Str::slug($value, '_'); 100 | } 101 | 102 | // No formatter (FORMATTER_NONE) 103 | return $value; 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/Imports/ModelImporter.php: -------------------------------------------------------------------------------- 1 | manager = $manager; 35 | } 36 | 37 | /** 38 | * @param Worksheet $worksheet 39 | * @param ToModel $import 40 | * @param int|null $startRow 41 | * @param string|null $endColumn 42 | * 43 | * @throws \Maatwebsite\Excel\Validators\ValidationException 44 | */ 45 | public function import(Worksheet $worksheet, ToModel $import, int $startRow = 1) 46 | { 47 | if ($startRow > $worksheet->getHighestRow()) { 48 | return; 49 | } 50 | if ($import instanceof WithEvents) { 51 | $this->registerListeners($import->registerEvents()); 52 | } 53 | 54 | $headingRow = HeadingRowExtractor::extract($worksheet, $import); 55 | $headerIsGrouped = HeadingRowExtractor::extractGrouping($headingRow, $import); 56 | $batchSize = $import instanceof WithBatchInserts ? $import->batchSize() : 1; 57 | $endRow = EndRowFinder::find($import, $startRow, $worksheet->getHighestRow()); 58 | $progessBar = $import instanceof WithProgressBar; 59 | $withMapping = $import instanceof WithMapping; 60 | $withCalcFormulas = $import instanceof WithCalculatedFormulas; 61 | $formatData = $import instanceof WithFormatData; 62 | $withValidation = $import instanceof WithValidation && method_exists($import, 'prepareForValidation'); 63 | $endColumn = $import instanceof WithColumnLimit ? $import->endColumn() : null; 64 | 65 | $this->manager->setRemembersRowNumber(method_exists($import, 'rememberRowNumber')); 66 | 67 | $i = 0; 68 | $batchStartRow = $startRow; 69 | foreach ($worksheet->getRowIterator($startRow, $endRow) as $spreadSheetRow) { 70 | $i++; 71 | 72 | $row = new Row($spreadSheetRow, $headingRow, $headerIsGrouped); 73 | if (!$import instanceof SkipsEmptyRows || !$row->isEmpty($withCalcFormulas)) { 74 | $rowArray = $row->toArray(null, $withCalcFormulas, $formatData, $endColumn); 75 | 76 | if ($import instanceof SkipsEmptyRows && method_exists($import, 'isEmptyWhen') && $import->isEmptyWhen($rowArray)) { 77 | continue; 78 | } 79 | 80 | if ($withValidation) { 81 | $rowArray = $import->prepareForValidation($rowArray, $row->getIndex()); 82 | } 83 | 84 | if ($withMapping) { 85 | $rowArray = $import->map($rowArray); 86 | } 87 | 88 | $this->manager->add( 89 | $row->getIndex(), 90 | $rowArray 91 | ); 92 | 93 | // Flush each batch. 94 | if (($i % $batchSize) === 0) { 95 | $this->flush($import, $batchSize, $batchStartRow); 96 | $batchStartRow += $i; 97 | $i = 0; 98 | 99 | if ($progessBar) { 100 | $import->getConsoleOutput()->progressAdvance($batchSize); 101 | } 102 | } 103 | } 104 | } 105 | 106 | if ($i > 0) { 107 | // Flush left-overs. 108 | $this->flush($import, $batchSize, $batchStartRow); 109 | } 110 | } 111 | 112 | private function flush(ToModel $import, int $batchSize, int $startRow) 113 | { 114 | $this->manager->flush($import, $batchSize > 1); 115 | $this->raise(new AfterBatch($this->manager, $import, $batchSize, $startRow)); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/Imports/Persistence/CascadePersistManager.php: -------------------------------------------------------------------------------- 1 | transaction = $transaction; 25 | } 26 | 27 | /** 28 | * @param Model $model 29 | * @return bool 30 | */ 31 | public function persist(Model $model): bool 32 | { 33 | return ($this->transaction)(function () use ($model) { 34 | return $this->save($model); 35 | }); 36 | } 37 | 38 | /** 39 | * @param Model $model 40 | * @return bool 41 | */ 42 | private function save(Model $model): bool 43 | { 44 | if (!$model->save()) { 45 | return false; 46 | } 47 | 48 | foreach ($model->getRelations() as $relationName => $models) { 49 | $models = array_filter( 50 | $models instanceof Collection ? $models->all() : [$models] 51 | ); 52 | 53 | $relation = $model->{$relationName}(); 54 | 55 | if ($relation instanceof BelongsTo) { 56 | if (!$this->persistBelongsTo($relation, $models)) { 57 | return false; 58 | } 59 | } 60 | 61 | if ($relation instanceof BelongsToMany) { 62 | if (!$this->persistBelongsToMany($relation, $models)) { 63 | return false; 64 | } 65 | } 66 | } 67 | 68 | // We need to save the model again to 69 | // make sure all updates are performed. 70 | $model->save(); 71 | 72 | return true; 73 | } 74 | 75 | /** 76 | * @param BelongsTo $relation 77 | * @param array $models 78 | * @return bool 79 | */ 80 | private function persistBelongsTo(BelongsTo $relation, array $models): bool 81 | { 82 | // With belongs to, we first need to save all relations, 83 | // so we can use their foreign key to attach to the relation. 84 | foreach ($models as $model) { 85 | // Cascade any relations that this child model may have. 86 | if (!$this->save($model)) { 87 | return false; 88 | } 89 | 90 | $relation->associate($model); 91 | } 92 | 93 | return true; 94 | } 95 | 96 | /** 97 | * @param BelongsToMany $relation 98 | * @param array $models 99 | * @return bool 100 | */ 101 | private function persistBelongsToMany(BelongsToMany $relation, array $models): bool 102 | { 103 | foreach ($models as $model) { 104 | $relation->save($model); 105 | 106 | // Cascade any relations that this child model may have. 107 | if (!$this->save($model)) { 108 | return false; 109 | } 110 | } 111 | 112 | return true; 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/Jobs/AfterImportJob.php: -------------------------------------------------------------------------------- 1 | import = $import; 43 | $this->reader = $reader; 44 | } 45 | 46 | public function setInterval(int $interval) 47 | { 48 | $this->interval = $interval; 49 | } 50 | 51 | public function setDependencies(Collection $jobs) 52 | { 53 | $this->dependencyIds = $jobs->map(function (ReadChunk $job) { 54 | return $job->getUniqueId(); 55 | })->all(); 56 | } 57 | 58 | public function handle() 59 | { 60 | foreach ($this->dependencyIds as $id) { 61 | if (!ReadChunk::isComplete($id)) { 62 | // Until there is no jobs left to run we put this job back into the queue every minute 63 | // Note: this will do nothing in a SyncQueue but that's desired, because in a SyncQueue jobs run in order 64 | $this->release($this->interval); 65 | 66 | return; 67 | } 68 | } 69 | 70 | if ($this->import instanceof ShouldQueue && $this->import instanceof WithEvents) { 71 | $this->reader->clearListeners(); 72 | $this->reader->registerListeners($this->import->registerEvents()); 73 | } 74 | 75 | $this->reader->afterImport($this->import); 76 | } 77 | 78 | /** 79 | * @param Throwable $e 80 | */ 81 | public function failed(Throwable $e) 82 | { 83 | if ($this->import instanceof WithEvents) { 84 | $this->registerListeners($this->import->registerEvents()); 85 | $this->raise(new ImportFailed($e)); 86 | 87 | if (method_exists($this->import, 'failed')) { 88 | $this->import->failed($e); 89 | } 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/Jobs/AppendDataToSheet.php: -------------------------------------------------------------------------------- 1 | sheetExport = $sheetExport; 52 | $this->data = $data; 53 | $this->temporaryFile = $temporaryFile; 54 | $this->writerType = $writerType; 55 | $this->sheetIndex = $sheetIndex; 56 | } 57 | 58 | /** 59 | * Get the middleware the job should be dispatched through. 60 | * 61 | * @return array 62 | */ 63 | public function middleware() 64 | { 65 | return (method_exists($this->sheetExport, 'middleware')) ? $this->sheetExport->middleware() : []; 66 | } 67 | 68 | /** 69 | * @param Writer $writer 70 | * 71 | * @throws \PhpOffice\PhpSpreadsheet\Exception 72 | * @throws \PhpOffice\PhpSpreadsheet\Reader\Exception 73 | */ 74 | public function handle(Writer $writer) 75 | { 76 | (new LocalizeJob($this->sheetExport))->handle($this, function () use ($writer) { 77 | $writer = $writer->reopen($this->temporaryFile, $this->writerType); 78 | 79 | $sheet = $writer->getSheetByIndex($this->sheetIndex); 80 | 81 | $sheet->appendRows($this->data, $this->sheetExport); 82 | 83 | $writer->write($this->sheetExport, $this->temporaryFile, $this->writerType); 84 | }); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/Jobs/AppendPaginatedToSheet.php: -------------------------------------------------------------------------------- 1 | sheetExport = $sheetExport; 69 | $this->temporaryFile = $temporaryFile; 70 | $this->writerType = $writerType; 71 | $this->sheetIndex = $sheetIndex; 72 | $this->page = $page; 73 | $this->perPage = $perPage; 74 | } 75 | 76 | /** 77 | * Get the middleware the job should be dispatched through. 78 | * 79 | * @return array 80 | */ 81 | public function middleware() 82 | { 83 | return (method_exists($this->sheetExport, 'middleware')) ? $this->sheetExport->middleware() : []; 84 | } 85 | 86 | /** 87 | * @param Writer $writer 88 | * 89 | * @throws \PhpOffice\PhpSpreadsheet\Exception 90 | * @throws \PhpOffice\PhpSpreadsheet\Reader\Exception 91 | */ 92 | public function handle(Writer $writer) 93 | { 94 | (new LocalizeJob($this->sheetExport))->handle($this, function () use ($writer) { 95 | $writer = $writer->reopen($this->temporaryFile, $this->writerType); 96 | 97 | $sheet = $writer->getSheetByIndex($this->sheetIndex); 98 | 99 | $sheet->appendRows($this->chunk($this->sheetExport->query()), $this->sheetExport); 100 | 101 | $writer->write($this->sheetExport, $this->temporaryFile, $this->writerType); 102 | }); 103 | } 104 | 105 | /** 106 | * @param Builder|Relation|EloquentBuilder|ScoutBuilder $query 107 | */ 108 | protected function chunk($query) 109 | { 110 | if ($query instanceof \Laravel\Scout\Builder) { 111 | return $query->paginate($this->perPage, 'page', $this->page)->items(); 112 | } 113 | 114 | // Fallback 115 | return $query->forPage($this->page, $this->perPage)->get(); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/Jobs/AppendQueryToSheet.php: -------------------------------------------------------------------------------- 1 | sheetExport = $sheetExport; 68 | $this->temporaryFile = $temporaryFile; 69 | $this->writerType = $writerType; 70 | $this->sheetIndex = $sheetIndex; 71 | $this->page = $page; 72 | $this->chunkSize = $chunkSize; 73 | } 74 | 75 | /** 76 | * Get the middleware the job should be dispatched through. 77 | * 78 | * @return array 79 | */ 80 | public function middleware() 81 | { 82 | return (method_exists($this->sheetExport, 'middleware')) ? $this->sheetExport->middleware() : []; 83 | } 84 | 85 | /** 86 | * @param Writer $writer 87 | * 88 | * @throws \PhpOffice\PhpSpreadsheet\Exception 89 | * @throws \PhpOffice\PhpSpreadsheet\Reader\Exception 90 | */ 91 | public function handle(Writer $writer) 92 | { 93 | (new LocalizeJob($this->sheetExport))->handle($this, function () use ($writer) { 94 | if ($this->sheetExport instanceof WithEvents) { 95 | $this->registerListeners($this->sheetExport->registerEvents()); 96 | } 97 | 98 | $writer = $writer->reopen($this->temporaryFile, $this->writerType); 99 | 100 | $sheet = $writer->getSheetByIndex($this->sheetIndex); 101 | 102 | $query = $this->sheetExport->query()->forPage($this->page, $this->chunkSize); 103 | 104 | $sheet->appendRows($query->get(), $this->sheetExport); 105 | 106 | $writer->write($this->sheetExport, $this->temporaryFile, $this->writerType); 107 | 108 | $this->raise(new AfterChunk($sheet, $this->sheetExport, ($this->page - 1) * $this->chunkSize)); 109 | $this->clearListeners(); 110 | }); 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/Jobs/AppendViewToSheet.php: -------------------------------------------------------------------------------- 1 | sheetExport = $sheetExport; 48 | $this->temporaryFile = $temporaryFile; 49 | $this->writerType = $writerType; 50 | $this->sheetIndex = $sheetIndex; 51 | } 52 | 53 | /** 54 | * Get the middleware the job should be dispatched through. 55 | * 56 | * @return array 57 | */ 58 | public function middleware() 59 | { 60 | return (method_exists($this->sheetExport, 'middleware')) ? $this->sheetExport->middleware() : []; 61 | } 62 | 63 | /** 64 | * @param Writer $writer 65 | * 66 | * @throws \PhpOffice\PhpSpreadsheet\Exception 67 | * @throws \PhpOffice\PhpSpreadsheet\Reader\Exception 68 | */ 69 | public function handle(Writer $writer) 70 | { 71 | (new LocalizeJob($this->sheetExport))->handle($this, function () use ($writer) { 72 | $writer = $writer->reopen($this->temporaryFile, $this->writerType); 73 | 74 | $sheet = $writer->getSheetByIndex($this->sheetIndex); 75 | 76 | $sheet->fromView($this->sheetExport, $this->sheetIndex); 77 | 78 | $writer->write($this->sheetExport, $this->temporaryFile, $this->writerType); 79 | }); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/Jobs/CloseSheet.php: -------------------------------------------------------------------------------- 1 | sheetExport = $sheetExport; 44 | $this->temporaryFile = $temporaryFile; 45 | $this->writerType = $writerType; 46 | $this->sheetIndex = $sheetIndex; 47 | } 48 | 49 | /** 50 | * @param Writer $writer 51 | * 52 | * @throws \PhpOffice\PhpSpreadsheet\Exception 53 | * @throws \PhpOffice\PhpSpreadsheet\Reader\Exception 54 | */ 55 | public function handle(Writer $writer) 56 | { 57 | $writer = $writer->reopen( 58 | $this->temporaryFile, 59 | $this->writerType 60 | ); 61 | 62 | $sheet = $writer->getSheetByIndex($this->sheetIndex); 63 | 64 | if ($this->sheetExport instanceof WithEvents) { 65 | $sheet->registerListeners($this->sheetExport->registerEvents()); 66 | } 67 | 68 | $sheet->close($this->sheetExport); 69 | 70 | $writer->write( 71 | $this->sheetExport, 72 | $this->temporaryFile, 73 | $this->writerType 74 | ); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/Jobs/ExtendedQueueable.php: -------------------------------------------------------------------------------- 1 | each(function ($job) { 20 | $serialized = method_exists($this, 'serializeJob') ? $this->serializeJob($job) : serialize($job); 21 | $this->chained[] = $serialized; 22 | }); 23 | 24 | return $this; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Jobs/Middleware/LocalizeJob.php: -------------------------------------------------------------------------------- 1 | localizable = $localizable; 26 | } 27 | 28 | /** 29 | * Handles the job. 30 | * 31 | * @param mixed $job 32 | * @param Closure $next 33 | * @return mixed 34 | */ 35 | public function handle($job, Closure $next) 36 | { 37 | $locale = value(function () { 38 | if ($this->localizable instanceof HasLocalePreference) { 39 | return $this->localizable->preferredLocale(); 40 | } 41 | 42 | return null; 43 | }); 44 | 45 | return $this->withLocale($locale, function () use ($next, $job) { 46 | return $next($job); 47 | }); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/Jobs/ProxyFailures.php: -------------------------------------------------------------------------------- 1 | sheetExport, 'failed')) { 15 | $this->sheetExport->failed($e); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/Jobs/QueueExport.php: -------------------------------------------------------------------------------- 1 | export = $export; 41 | $this->writerType = $writerType; 42 | $this->temporaryFile = $temporaryFile; 43 | } 44 | 45 | /** 46 | * Get the middleware the job should be dispatched through. 47 | * 48 | * @return array 49 | */ 50 | public function middleware() 51 | { 52 | return (method_exists($this->export, 'middleware')) ? $this->export->middleware() : []; 53 | } 54 | 55 | /** 56 | * @param Writer $writer 57 | * 58 | * @throws \PhpOffice\PhpSpreadsheet\Exception 59 | */ 60 | public function handle(Writer $writer) 61 | { 62 | (new LocalizeJob($this->export))->handle($this, function () use ($writer) { 63 | $writer->open($this->export); 64 | 65 | $sheetExports = [$this->export]; 66 | if ($this->export instanceof WithMultipleSheets) { 67 | $sheetExports = $this->export->sheets(); 68 | } 69 | 70 | if (count($sheetExports) === 0) { 71 | throw new NoSheetsFoundException('Your export did not return any sheet export instances, please make sure your sheets() method always at least returns one instance.'); 72 | } 73 | 74 | // Pre-create the worksheets 75 | foreach ($sheetExports as $sheetIndex => $sheetExport) { 76 | $sheet = $writer->addNewSheet($sheetIndex); 77 | $sheet->open($sheetExport); 78 | } 79 | 80 | // Write to temp file with empty sheets. 81 | $writer->write($sheetExport, $this->temporaryFile, $this->writerType); 82 | }); 83 | } 84 | 85 | /** 86 | * @param Throwable $e 87 | */ 88 | public function failed(Throwable $e) 89 | { 90 | if (method_exists($this->export, 'failed')) { 91 | $this->export->failed($e); 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/Jobs/QueueImport.php: -------------------------------------------------------------------------------- 1 | timeout = $import->timeout ?? null; 29 | $this->tries = $import->tries ?? null; 30 | } 31 | } 32 | 33 | public function handle() 34 | { 35 | // 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Jobs/StoreQueuedExport.php: -------------------------------------------------------------------------------- 1 | disk = $disk; 42 | $this->filePath = $filePath; 43 | $this->temporaryFile = $temporaryFile; 44 | $this->diskOptions = $diskOptions; 45 | } 46 | 47 | /** 48 | * @param Filesystem $filesystem 49 | */ 50 | public function handle(Filesystem $filesystem) 51 | { 52 | $filesystem->disk($this->disk, $this->diskOptions)->copy( 53 | $this->temporaryFile, 54 | $this->filePath 55 | ); 56 | 57 | $this->temporaryFile->delete(); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/MappedReader.php: -------------------------------------------------------------------------------- 1 | mapping(); 25 | array_walk_recursive($mapped, function (&$coordinate) use ($import, $worksheet) { 26 | $cell = Cell::make($worksheet, $coordinate); 27 | 28 | $coordinate = $cell->getValue( 29 | null, 30 | $import instanceof WithCalculatedFormulas, 31 | $import instanceof WithFormatData 32 | ); 33 | }); 34 | 35 | if ($import instanceof ToModel) { 36 | $model = $import->model($mapped); 37 | 38 | if ($model) { 39 | $model->saveOrFail(); 40 | } 41 | } 42 | 43 | if ($import instanceof ToCollection) { 44 | $import->collection(new Collection($mapped)); 45 | } 46 | 47 | if ($import instanceof ToArray) { 48 | $import->array($mapped); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/Middleware/CellMiddleware.php: -------------------------------------------------------------------------------- 1 | collection = $collection->toBase(); 41 | $this->withHeadings = $withHeading; 42 | } 43 | 44 | /** 45 | * @return Collection 46 | */ 47 | public function collection() 48 | { 49 | return $this->collection; 50 | } 51 | 52 | /** 53 | * @return array 54 | */ 55 | public function headings(): array 56 | { 57 | if (!$this->withHeadings) { 58 | return []; 59 | } 60 | 61 | $firstRow = $this->collection->first(); 62 | 63 | if ($firstRow instanceof Arrayable || \is_object($firstRow)) { 64 | return array_keys(Sheet::mapArraybleRow($firstRow)); 65 | } 66 | 67 | return $this->collection->collapse()->keys()->all(); 68 | } 69 | }; 70 | 71 | return $export->download($fileName, $writerType, $responseHeaders); 72 | }; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/Mixins/DownloadQueryMacro.php: -------------------------------------------------------------------------------- 1 | query = $query; 37 | $this->withHeadings = $withHeadings; 38 | } 39 | 40 | /** 41 | * @return Builder 42 | */ 43 | public function query() 44 | { 45 | return $this->query; 46 | } 47 | 48 | /** 49 | * @return array 50 | */ 51 | public function headings(): array 52 | { 53 | if (!$this->withHeadings) { 54 | return []; 55 | } 56 | 57 | $firstRow = (clone $this->query)->first(); 58 | 59 | if ($firstRow) { 60 | return array_keys(Sheet::mapArraybleRow($firstRow)); 61 | } 62 | 63 | return []; 64 | } 65 | }; 66 | 67 | return $export->download($fileName, $writerType); 68 | }; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/Mixins/ImportAsMacro.php: -------------------------------------------------------------------------------- 1 | getModel()), $mapping) implements ToModel 15 | { 16 | use Importable; 17 | 18 | /** 19 | * @var string 20 | */ 21 | private $model; 22 | 23 | /** 24 | * @var callable 25 | */ 26 | private $mapping; 27 | 28 | /** 29 | * @param string $model 30 | * @param callable $mapping 31 | */ 32 | public function __construct(string $model, callable $mapping) 33 | { 34 | $this->model = $model; 35 | $this->mapping = $mapping; 36 | } 37 | 38 | /** 39 | * @param array $row 40 | * @return Model|Model[]|null 41 | */ 42 | public function model(array $row) 43 | { 44 | return (new $this->model)->fill( 45 | ($this->mapping)($row) 46 | ); 47 | } 48 | }; 49 | 50 | return $import->import($filename, $disk, $readerType); 51 | }; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/Mixins/ImportMacro.php: -------------------------------------------------------------------------------- 1 | getModel())) implements ToModel, WithHeadingRow 16 | { 17 | use Importable; 18 | 19 | /** 20 | * @var string 21 | */ 22 | private $model; 23 | 24 | /** 25 | * @param string $model 26 | */ 27 | public function __construct(string $model) 28 | { 29 | $this->model = $model; 30 | } 31 | 32 | /** 33 | * @param array $row 34 | * @return Model|Model[]|null 35 | */ 36 | public function model(array $row) 37 | { 38 | return (new $this->model)->fill($row); 39 | } 40 | }; 41 | 42 | return $import->import($filename, $disk, $readerType); 43 | }; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Mixins/StoreCollectionMixin.php: -------------------------------------------------------------------------------- 1 | collection = $collection->toBase(); 39 | $this->withHeadings = $withHeadings; 40 | } 41 | 42 | /** 43 | * @return Collection 44 | */ 45 | public function collection() 46 | { 47 | return $this->collection; 48 | } 49 | 50 | /** 51 | * @return array 52 | */ 53 | public function headings(): array 54 | { 55 | if (!$this->withHeadings) { 56 | return []; 57 | } 58 | 59 | return is_array($first = $this->collection->first()) 60 | ? $this->collection->collapse()->keys()->all() 61 | : array_keys($first->toArray()); 62 | } 63 | }; 64 | 65 | return $export->store($filePath, $disk, $writerType); 66 | }; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/Mixins/StoreQueryMacro.php: -------------------------------------------------------------------------------- 1 | query = $query; 37 | $this->withHeadings = $withHeadings; 38 | } 39 | 40 | /** 41 | * @return Builder 42 | */ 43 | public function query() 44 | { 45 | return $this->query; 46 | } 47 | 48 | /** 49 | * @return array 50 | */ 51 | public function headings(): array 52 | { 53 | if (!$this->withHeadings) { 54 | return []; 55 | } 56 | 57 | $firstRow = (clone $this->query)->first(); 58 | 59 | if ($firstRow) { 60 | return array_keys(Sheet::mapArraybleRow($firstRow)); 61 | } 62 | 63 | return []; 64 | } 65 | }; 66 | 67 | return $export->store($filePath, $disk, $writerType); 68 | }; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/RegistersCustomConcerns.php: -------------------------------------------------------------------------------- 1 | Writer::class, 18 | BeforeExport::class => Writer::class, 19 | BeforeSheet::class => Sheet::class, 20 | AfterSheet::class => Sheet::class, 21 | ]; 22 | 23 | /** 24 | * @param string $concern 25 | * @param callable $handler 26 | * @param string $event 27 | */ 28 | public static function extend(string $concern, callable $handler, string $event = BeforeWriting::class) 29 | { 30 | /** @var HasEventBus $delegate */ 31 | $delegate = static::$eventMap[$event] ?? BeforeWriting::class; 32 | 33 | $delegate::listen($event, function (Event $event) use ($concern, $handler) { 34 | if ($event->appliesToConcern($concern)) { 35 | $handler($event->getConcernable(), $event->getDelegate()); 36 | } 37 | }); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Row.php: -------------------------------------------------------------------------------- 1 | row = $row; 58 | $this->headingRow = $headingRow; 59 | $this->headerIsGrouped = $headerIsGrouped; 60 | } 61 | 62 | /** 63 | * @return SpreadsheetRow 64 | */ 65 | public function getDelegate(): SpreadsheetRow 66 | { 67 | return $this->row; 68 | } 69 | 70 | /** 71 | * @param null $nullValue 72 | * @param bool $calculateFormulas 73 | * @param bool $formatData 74 | * @param string|null $endColumn 75 | * @return Collection 76 | */ 77 | public function toCollection($nullValue = null, $calculateFormulas = false, $formatData = true, ?string $endColumn = null): Collection 78 | { 79 | return new Collection($this->toArray($nullValue, $calculateFormulas, $formatData, $endColumn)); 80 | } 81 | 82 | /** 83 | * @param null $nullValue 84 | * @param bool $calculateFormulas 85 | * @param bool $formatData 86 | * @param string|null $endColumn 87 | * @return array 88 | */ 89 | public function toArray($nullValue = null, $calculateFormulas = false, $formatData = true, ?string $endColumn = null) 90 | { 91 | if (is_array($this->rowCache) && ($this->rowCacheFormatData === $formatData) && ($this->rowCacheEndColumn === $endColumn)) { 92 | return $this->rowCache; 93 | } 94 | 95 | $cells = []; 96 | 97 | $i = 0; 98 | foreach ($this->row->getCellIterator('A', $endColumn) as $cell) { 99 | $value = (new Cell($cell))->getValue($nullValue, $calculateFormulas, $formatData); 100 | 101 | if (isset($this->headingRow[$i])) { 102 | if (!$this->headerIsGrouped[$i]) { 103 | $cells[$this->headingRow[$i]] = $value; 104 | } else { 105 | $cells[$this->headingRow[$i]][] = $value; 106 | } 107 | } else { 108 | $cells[] = $value; 109 | } 110 | 111 | $i++; 112 | } 113 | 114 | if (isset($this->preparationCallback)) { 115 | $cells = ($this->preparationCallback)($cells, $this->row->getRowIndex()); 116 | } 117 | 118 | $this->rowCache = $cells; 119 | $this->rowCacheFormatData = $formatData; 120 | $this->rowCacheEndColumn = $endColumn; 121 | 122 | return $cells; 123 | } 124 | 125 | /** 126 | * @param bool $calculateFormulas 127 | * @param string|null $endColumn 128 | * @return bool 129 | */ 130 | public function isEmpty($calculateFormulas = false, ?string $endColumn = null): bool 131 | { 132 | return count(array_filter($this->toArray(null, $calculateFormulas, false, $endColumn))) === 0; 133 | } 134 | 135 | /** 136 | * @return int 137 | */ 138 | public function getIndex(): int 139 | { 140 | return $this->row->getRowIndex(); 141 | } 142 | 143 | #[\ReturnTypeWillChange] 144 | public function offsetExists($offset) 145 | { 146 | return isset($this->toArray()[$offset]); 147 | } 148 | 149 | #[\ReturnTypeWillChange] 150 | public function offsetGet($offset) 151 | { 152 | return $this->toArray()[$offset]; 153 | } 154 | 155 | #[\ReturnTypeWillChange] 156 | public function offsetSet($offset, $value) 157 | { 158 | // 159 | } 160 | 161 | #[\ReturnTypeWillChange] 162 | public function offsetUnset($offset) 163 | { 164 | // 165 | } 166 | 167 | /** 168 | * @param \Closure $preparationCallback 169 | * 170 | * @internal 171 | */ 172 | public function setPreparationCallback(?Closure $preparationCallback = null) 173 | { 174 | $this->preparationCallback = $preparationCallback; 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /src/SettingsProvider.php: -------------------------------------------------------------------------------- 1 | cache = $cache; 18 | } 19 | 20 | /** 21 | * Provide PhpSpreadsheet settings. 22 | */ 23 | public function provide() 24 | { 25 | $this->configureCellCaching(); 26 | } 27 | 28 | protected function configureCellCaching() 29 | { 30 | Settings::setCache( 31 | $this->cache->driver() 32 | ); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Transactions/DbTransactionHandler.php: -------------------------------------------------------------------------------- 1 | connection = $connection; 20 | } 21 | 22 | /** 23 | * @param callable $callback 24 | * @return mixed 25 | * 26 | * @throws \Throwable 27 | */ 28 | public function __invoke(callable $callback) 29 | { 30 | return $this->connection->transaction($callback); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Transactions/NullTransactionHandler.php: -------------------------------------------------------------------------------- 1 | row = $row; 39 | $this->attribute = $attribute; 40 | $this->errors = $errors; 41 | $this->values = $values; 42 | } 43 | 44 | /** 45 | * @return int 46 | */ 47 | public function row(): int 48 | { 49 | return $this->row; 50 | } 51 | 52 | /** 53 | * @return string 54 | */ 55 | public function attribute(): string 56 | { 57 | return $this->attribute; 58 | } 59 | 60 | /** 61 | * @return array 62 | */ 63 | public function errors(): array 64 | { 65 | return $this->errors; 66 | } 67 | 68 | /** 69 | * @return array 70 | */ 71 | public function values(): array 72 | { 73 | return $this->values; 74 | } 75 | 76 | /** 77 | * @return array 78 | */ 79 | public function toArray() 80 | { 81 | return collect($this->errors)->map(function ($message) { 82 | return __('There was an error on row :row. :message', ['row' => $this->row, 'message' => $message]); 83 | })->all(); 84 | } 85 | 86 | /** 87 | * @return array 88 | */ 89 | #[\ReturnTypeWillChange] 90 | public function jsonSerialize() 91 | { 92 | return [ 93 | 'row' => $this->row(), 94 | 'attribute' => $this->attribute(), 95 | 'errors' => $this->errors(), 96 | 'values' => $this->values(), 97 | ]; 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/Validators/RowValidator.php: -------------------------------------------------------------------------------- 1 | validator = $validator; 25 | } 26 | 27 | /** 28 | * @param array $rows 29 | * @param WithValidation $import 30 | * 31 | * @throws ValidationException 32 | * @throws RowSkippedException 33 | */ 34 | public function validate(array $rows, WithValidation $import) 35 | { 36 | $rules = $this->rules($import); 37 | $messages = $this->messages($import); 38 | $attributes = $this->attributes($import); 39 | 40 | try { 41 | $validator = $this->validator->make($rows, $rules, $messages, $attributes); 42 | 43 | if (method_exists($import, 'withValidator')) { 44 | $import->withValidator($validator); 45 | } 46 | 47 | $validator->validate(); 48 | } catch (IlluminateValidationException $e) { 49 | $failures = []; 50 | foreach ($e->errors() as $attribute => $messages) { 51 | $row = strtok($attribute, '.'); 52 | $attributeName = strtok(''); 53 | $attributeName = $attributes['*.' . $attributeName] ?? $attributeName; 54 | 55 | $failures[] = new Failure( 56 | $row, 57 | $attributeName, 58 | str_replace($attribute, $attributeName, $messages), 59 | $rows[$row] ?? [] 60 | ); 61 | } 62 | 63 | if ($import instanceof SkipsOnFailure) { 64 | $import->onFailure(...$failures); 65 | throw new RowSkippedException(...$failures); 66 | } 67 | 68 | throw new ValidationException( 69 | $e, 70 | $failures 71 | ); 72 | } 73 | } 74 | 75 | /** 76 | * @param WithValidation $import 77 | * @return array 78 | */ 79 | private function messages(WithValidation $import): array 80 | { 81 | return method_exists($import, 'customValidationMessages') 82 | ? $this->formatKey($import->customValidationMessages()) 83 | : []; 84 | } 85 | 86 | /** 87 | * @param WithValidation $import 88 | * @return array 89 | */ 90 | private function attributes(WithValidation $import): array 91 | { 92 | return method_exists($import, 'customValidationAttributes') 93 | ? $this->formatKey($import->customValidationAttributes()) 94 | : []; 95 | } 96 | 97 | /** 98 | * @param WithValidation $import 99 | * @return array 100 | */ 101 | private function rules(WithValidation $import): array 102 | { 103 | return $this->formatKey($import->rules()); 104 | } 105 | 106 | /** 107 | * @param array $elements 108 | * @return array 109 | */ 110 | private function formatKey(array $elements): array 111 | { 112 | return collect($elements)->mapWithKeys(function ($rule, $attribute) { 113 | $attribute = Str::startsWith($attribute, '*.') ? $attribute : '*.' . $attribute; 114 | 115 | return [$attribute => $this->formatRule($rule)]; 116 | })->all(); 117 | } 118 | 119 | /** 120 | * @param string|object|callable|array $rules 121 | * @return string|array 122 | */ 123 | private function formatRule($rules) 124 | { 125 | if (is_array($rules)) { 126 | foreach ($rules as $rule) { 127 | $formatted[] = $this->formatRule($rule); 128 | } 129 | 130 | return $formatted ?? []; 131 | } 132 | 133 | if (is_object($rules) || is_callable($rules)) { 134 | return $rules; 135 | } 136 | 137 | if (Str::contains($rules, 'required_without') && preg_match('/(.*?):(.*)/', $rules, $matches)) { 138 | $column = array_map(function ($match) { 139 | return Str::startsWith($match, '*.') ? $match : '*.' . $match; 140 | }, explode(',', $matches[2])); 141 | 142 | return $matches[1] . ':' . implode(',', $column); 143 | } 144 | 145 | if (Str::contains($rules, 'required_') && preg_match('/(.*?):(.*),(.*)/', $rules, $matches)) { 146 | $column = Str::startsWith($matches[2], '*.') ? $matches[2] : '*.' . $matches[2]; 147 | 148 | return $matches[1] . ':' . $column . ',' . $matches[3]; 149 | } 150 | 151 | return $rules; 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /src/Validators/ValidationException.php: -------------------------------------------------------------------------------- 1 | validator, $previous->response, $previous->errorBag); 21 | $this->failures = $failures; 22 | } 23 | 24 | /** 25 | * @return string[] 26 | */ 27 | public function errors(): array 28 | { 29 | return collect($this->failures)->map->toArray()->all(); 30 | } 31 | 32 | /** 33 | * @return array 34 | */ 35 | public function failures(): array 36 | { 37 | return $this->failures; 38 | } 39 | } 40 | --------------------------------------------------------------------------------