├── .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 |
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 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
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 | 
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 | 
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 |
--------------------------------------------------------------------------------