├── .gitignore
├── LICENSE
├── README.md
├── composer.json
├── import.jpg
├── import2.jpg
├── logo
├── 01-feature-fast.jpg
├── 02-feature-memory.jpg
├── 03-feature-worksheets.jpg
├── 04-feature-protection.jpg
├── 05-feature-charts.jpg
├── 06-feature-styling.jpg
└── logo-laravel.jpg
├── src
├── FastExcelLaravel
│ ├── Excel.php
│ ├── ExcelReader.php
│ ├── ExcelWriter.php
│ ├── Facades
│ │ └── Excel.php
│ ├── Providers
│ │ └── ExcelServiceProvider.php
│ ├── SheetReader.php
│ └── SheetWriter.php
└── autoload.php
└── tests
├── FastExcelLaravelTest.php
└── test_storage
├── test_model.xlsx
└── test_model2.xlsx
/.gitignore:
--------------------------------------------------------------------------------
1 | vendor
2 | composer.lock
3 | .phpunit.result.cache
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 aVadim
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 | # FastExcelLaravel
8 |
9 |
10 | |
11 |
12 |
13 |
14 | Lightweight and very fast XLSX Excel Spreadsheet read/write library for Laravel in pure PHP
15 | (wrapper around [FastExcelWriter](https://packagist.org/packages/avadim/fast-excel-writer)
16 | and [FastExcelReader](https://packagist.org/packages/avadim/fast-excel-reader))
17 |
18 | ## Introduction
19 |
20 | Exporting data from your Laravel application has never been so fast! Importing models into your Laravel application has never been so easy!
21 |
22 | This library is a wrapper around **avadim/fast-excel-writer** and **avadim/fast-excel-reader**, so it's also lightweight, fast, and requires a minimum of memory.
23 | Using this library, you can export arrays, collections and models to a XLSX-file from your Laravel application, and you can import data to Laravel application.
24 |
25 | **Features**
26 |
27 | * Writing
28 | * Easily export models, collections and arrays to Excel
29 | * Export huge datasets very fast, and using a minimum of memory
30 | * Сan create multiple sheets and supports basic column, row and cell styling
31 | * You can set the height of the rows and the width of the columns (including auto width calculation)
32 | * Mapping export data
33 | * You can add active hyperlinks, formulas, notes and images to output XLSX-files
34 | * Supports workbook and sheet protection with/without passwords
35 | * Supports page settings - page margins, page size
36 | * Inserting multiple charts
37 | * Supports data validations and conditional formatting
38 | * Reading
39 | * Import workbooks and worksheets to Eloquent models very quickly and with minimal memory usage
40 | * Automatic field detection from imported table headers
41 | * Import huge files very fast, and using a minimum of memory
42 | * Mapping import data
43 | * Supports auto formatter and custom formatter of datetime values for import data
44 | * The library can define and extract images from XLSX files
45 | * Mapping import/export data
46 |
47 | ## Installation
48 |
49 | Install via composer:
50 |
51 | ```
52 | composer require avadim/fast-excel-laravel
53 | ```
54 | And then you can use facade ```Excel```
55 |
56 | ```php
57 | // Create workbook...
58 | $excel = \Excel::create();
59 |
60 | // export model...
61 | $excel->sheet()->withHeadings()->exportModel(Users::class);
62 |
63 | // and save XLSX-file to default storage
64 | $excel->saveTo('path/file.xlsx');
65 |
66 | // or save file to specified disk
67 | $excel->store('disk', 'path/file.xlsx');
68 |
69 | // Open saved workbook
70 | $excel = \Excel::open(storage_path('path/file.xlsx'));
71 |
72 | // import records to database
73 | $excel->withHeadings()->importModel(User::class);
74 | ```
75 |
76 | Jump To:
77 | * [Export Data](#export-data)
78 | * [Export a Model](#export-a-model)
79 | * [Export Any Collections and Arrays](#export-any-collections-and-arrays)
80 | * [Mapping Export Data](#mapping-export-data)
81 | * [Advanced Usage for Data Export](#advanced-usage-for-data-export)
82 | * [Import Data](#import-data)
83 | * [Import a Model](#import-a-model)
84 | * [Mapping Import Data](#mapping-import-data)
85 | * [Advanced Usage for Data Import](#advanced-usage-for-data-import)
86 | * [More Features](#more-features)
87 | * [Do you want to support FastExcelLaravel?](#do-you-want-to-support-fastexcellaravel)
88 |
89 |
90 | ## Export Data
91 |
92 | ### Export a Model
93 | Easy and fast export of a model. This way you export only model data without headers and without any styling
94 | ```php
95 |
96 | // Create workbook with sheet named 'Users'
97 | $excel = \Excel::create('Users');
98 |
99 | // Export all users to Excel file
100 | $sheet->exportModel(Users::class);
101 |
102 | $excel->saveTo('path/file.xlsx');
103 | ```
104 | The following code will write the field names and styles (font and borders) to the first row, and then export all the data of the User model
105 |
106 | ```php
107 |
108 | // Create workbook with sheet named 'Users'
109 | $excel = \Excel::create('Users');
110 |
111 | // Write users with field names in the first row
112 | $sheet->withHeadings()
113 | ->applyFontStyleBold()
114 | ->applyBorder('thin')
115 | ->exportModel(Users::class);
116 |
117 | $excel->saveTo('path/file.xlsx');
118 | ```
119 |
120 | ### Export Any Collections and Arrays
121 | ```php
122 | // Create workbook with sheet named 'Users'
123 | $excel = \Excel::create('Users');
124 |
125 | $sheet = $excel->sheet();
126 | // Get users as collection
127 | $users = User::where('age', '>', 35)->get();
128 |
129 | // Write attribute names
130 | $sheet->writeRow(array_keys(User::getAttributes()));
131 |
132 | // Write all selected records
133 | $sheet->writeData($users);
134 |
135 | $sheet = $excel->makeSheet('Records');
136 | // Get collection of records using Query Builder
137 | $records = \DB::table('users')->where('age', '>=', 21)->get(['id', 'name', 'birthday']);
138 | $sheet->writeData($records);
139 |
140 | $sheet = $excel->makeSheet('Collection');
141 | // Make custom collection of arrays
142 | $collection = collect([
143 | [ 'id' => 1, 'site' => 'google.com' ],
144 | [ 'id' => 2, 'site.com' => 'youtube.com' ],
145 | ]);
146 | $sheet->writeData($collection);
147 |
148 | $sheet = $excel->makeSheet('Array');
149 | // Make array and write to sheet
150 | $array = [
151 | [ 'id' => 1, 'name' => 'Helen' ],
152 | [ 'id' => 2, 'name' => 'Peter' ],
153 | ];
154 | $sheet->writeData($array);
155 |
156 | $sheet = $excel->makeSheet('Callback');
157 | $sheet->writeData(function () {
158 | foreach (User::cursor() as $user) {
159 | yield $user;
160 | }
161 | });
162 |
163 | ```
164 |
165 | ### Mapping Export Data
166 |
167 | You can map the data that needs to be added as row
168 |
169 | ```php
170 | $sheet = $excel->sheet();
171 | $sheet->mapping(function($model) {
172 | return [
173 | 'id' => $model->id, 'date' => $model->created_at, 'name' => $model->first_name . $model->last_name,
174 | ];
175 | })->exportModel(User::class);
176 | $excel->save($testFileName);
177 |
178 | ```
179 |
180 | ### Advanced Usage for Data Export
181 |
182 | See detailed documentation for avadim/fast-excel-writer here: https://github.com/aVadim483/fast-excel-writer/tree/master#readme
183 |
184 | ```php
185 | $excel = \Excel::create('Users');
186 | $sheet = $excel->sheet();
187 |
188 | // Set column B to 12
189 | $sheet->setColWidth('B', 12);
190 | // Set options for column C
191 | $sheet->setColOptions('C', ['width' => 12, 'text-align' => 'center']);
192 | // Set column width to auto
193 | $sheet->setColWidth('D', 'auto');
194 |
195 | $title = 'This is demo of avadim/fast-excel-laravel';
196 | // Begin area for direct access to cells
197 | $area = $sheet->beginArea();
198 | $area->setValue('A2:D2', $title)
199 | ->applyFontSize(14)
200 | ->applyFontStyleBold()
201 | ->applyTextCenter();
202 |
203 | // Write headers to area, column letters are case independent
204 | $area
205 | ->setValue('a4:a5', '#')
206 | ->setValue('b4:b5', 'Number')
207 | ->setValue('c4:d4', 'Movie Character')
208 | ->setValue('c5', 'Birthday')
209 | ->setValue('d5', 'Name')
210 | ;
211 |
212 | // Apply styles to headers
213 | $area->withRange('a4:d5')
214 | ->applyBgColor('#ccc')
215 | ->applyFontStyleBold()
216 | ->applyOuterBorder('thin')
217 | ->applyInnerBorder('thick')
218 | ->applyTextCenter();
219 |
220 | // Write area to sheet
221 | $sheet->writeAreas();
222 |
223 | // You can set value formats for some fields
224 | $sheet->formatAttributes(['birthday' => '@date', 'number' => '@integer']);
225 |
226 | // Write data to sheet
227 | $sheet->writeData($data);
228 |
229 | // Save XLSX-file
230 | $excel->saveTo($testFileName);
231 |
232 | ```
233 |
234 | ## Import Data
235 |
236 | ### Import a Model
237 | To import models, you can use method ```importModel()```.
238 | If the first row contains the names of the fields you can apply these using method ```withHeadings()```
239 |
240 | 
241 |
242 | ```php
243 | // Open XLSX-file
244 | $excel = Excel::open($file);
245 |
246 | // Import a workbook to User model using the first row as attribute names
247 | $excel->withHeadings()->importModel(User::class);
248 |
249 | // Done!!!
250 | ```
251 | You can define the columns or cells from which you will import
252 |
253 | ```php
254 | // Import row to User model from columns range A:B - only 'name' and 'birthday'
255 | $excel->withHeadings()->importModel(User::class, 'A:B');
256 | ```
257 |
258 | 
259 | ```php
260 | // Import from cells range
261 | $excel->withHeadings()->importModel(User::class, 'B4:D7');
262 |
263 | // Define top left cell only
264 | $excel->withHeadings()->importModel(User::class, 'B4');
265 | ```
266 | In the last two examples, we also assume that the first row of imported data (row 4)
267 | is the names of the attributes.
268 |
269 | ### Mapping Import Data
270 |
271 | However, you can set the correspondence between columns and field names yourself.
272 |
273 | ```php
274 | // Import row to User model from columns range B:E
275 | $excel->mapping(function ($record) {
276 | return [
277 | 'id' => $record['A'], 'name' => $record['B'], 'birthday' => $record['C'], 'random' => $record['D'],
278 | ];
279 | })->importModel(User::class, 'B:D');
280 |
281 | // Define top left cell only
282 | $excel->mapping(['B' => 'name', 'C' => 'birthday', 'D' => 'random'])->importModel(User::class, 'B5');
283 |
284 | // Define top left cell only (shorter way)
285 | $excel->importModel(User::class, 'B5', ['B' => 'name', 'C' => 'birthday', 'D' => 'random']);
286 | ```
287 |
288 | ### Advanced Usage for Data Import
289 | See detailed documentation for avadim/fast-excel-reader here: https://github.com/aVadim483/fast-excel-reader/tree/master#readme
290 | ```php
291 | $excel = Excel::open($file);
292 |
293 | $sheet = $excel->sheet('Articles');
294 | $sheet->setReadArea('B5');
295 | foreach ($sheet->nextRow() as $rowNum => $rowData) {
296 | $user = User::create([
297 | 'name' => $rowData['B'],
298 | 'birthday' => new \Carbon($rowData['C']),
299 | 'password' => bcrypt($rowData['D']),
300 | ]);
301 | Article::create([
302 | 'user_id' => $user->id,
303 | 'title' => $rowData['E'],
304 | 'date' => new \Carbon($rowData['F']),
305 | 'public' => $rowData['G'] === 'yes',
306 | ]);
307 | }
308 | ```
309 |
310 | ## More Features
311 | You can see more features for export in the documentation for [FastExcelWriter](https://packagist.org/packages/avadim/fast-excel-writer).
312 |
313 | You can see more features for import in the documentation for [FastExcelReader](https://packagist.org/packages/avadim/fast-excel-reader))
314 |
315 | ## Do you want to support FastExcelLaravel?
316 |
317 | if you find this package useful you can support and donate to me for a cup of coffee:
318 |
319 | * USDT (TRC20) TSsUFvJehQBJCKeYgNNR1cpswY6JZnbZK7
320 | * USDT (ERC20) 0x5244519D65035aF868a010C2f68a086F473FC82b
321 | * ETH 0x5244519D65035aF868a010C2f68a086F473FC82b
322 |
323 | Or just give me a star on GitHub :)
324 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "avadim/fast-excel-laravel",
3 | "description": "Lightweight and very fast XLSX Excel Spreadsheet Export/Import for Laravel",
4 | "keywords": [
5 | "excel",
6 | "xlsx",
7 | "PHPExcel",
8 | "spreadsheet",
9 | "laravel",
10 | "export",
11 | "import",
12 | "ms office",
13 | "office 2007"
14 | ],
15 | "type": "library",
16 | "homepage": "https://github.com/aVadim483/fast-excel-laravel",
17 | "license": "MIT",
18 | "autoload": {
19 | "psr-4": {
20 | "avadim\\FastExcelLaravel\\": "./src/FastExcelLaravel"
21 | }
22 | },
23 | "autoload-dev": {
24 | "psr-4": {
25 | "avadim\\FastExcelLaravel\\Test\\": "tests/"
26 | }
27 | },
28 | "require": {
29 | "php": ">=7.4",
30 | "ext-json": "*",
31 | "avadim/fast-excel-writer": "^6.6",
32 | "avadim/fast-excel-reader": "^2.26",
33 | "illuminate/support": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0",
34 | "illuminate/contracts": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0"
35 | },
36 | "require-dev": {
37 | "orchestra/testbench": "^4.0|^5.0|^6.0",
38 | "phpunit/phpunit": "^8.0|^9.0"
39 | },
40 | "extra": {
41 | "laravel": {
42 | "providers": [
43 | "avadim\\FastExcelLaravel\\Providers\\ExcelServiceProvider"
44 | ],
45 | "aliases": {
46 | "Excel": "avadim\\FastExcelLaravel\\Facades\\Excel"
47 | }
48 | }
49 | },
50 | "scripts": {
51 | "test": [
52 | "vendor/bin/phpunit"
53 | ]
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/import.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aVadim483/fast-excel-laravel/db4c30ca41110bdf3127a9073651954e1c0d9710/import.jpg
--------------------------------------------------------------------------------
/import2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aVadim483/fast-excel-laravel/db4c30ca41110bdf3127a9073651954e1c0d9710/import2.jpg
--------------------------------------------------------------------------------
/logo/01-feature-fast.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aVadim483/fast-excel-laravel/db4c30ca41110bdf3127a9073651954e1c0d9710/logo/01-feature-fast.jpg
--------------------------------------------------------------------------------
/logo/02-feature-memory.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aVadim483/fast-excel-laravel/db4c30ca41110bdf3127a9073651954e1c0d9710/logo/02-feature-memory.jpg
--------------------------------------------------------------------------------
/logo/03-feature-worksheets.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aVadim483/fast-excel-laravel/db4c30ca41110bdf3127a9073651954e1c0d9710/logo/03-feature-worksheets.jpg
--------------------------------------------------------------------------------
/logo/04-feature-protection.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aVadim483/fast-excel-laravel/db4c30ca41110bdf3127a9073651954e1c0d9710/logo/04-feature-protection.jpg
--------------------------------------------------------------------------------
/logo/05-feature-charts.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aVadim483/fast-excel-laravel/db4c30ca41110bdf3127a9073651954e1c0d9710/logo/05-feature-charts.jpg
--------------------------------------------------------------------------------
/logo/06-feature-styling.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aVadim483/fast-excel-laravel/db4c30ca41110bdf3127a9073651954e1c0d9710/logo/06-feature-styling.jpg
--------------------------------------------------------------------------------
/logo/logo-laravel.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aVadim483/fast-excel-laravel/db4c30ca41110bdf3127a9073651954e1c0d9710/logo/logo-laravel.jpg
--------------------------------------------------------------------------------
/src/FastExcelLaravel/Excel.php:
--------------------------------------------------------------------------------
1 | sheet()->withHeadings($headers);
42 |
43 | return $this;
44 | }
45 |
46 | /**
47 | * @param $callback
48 | *
49 | * @return $this
50 | */
51 | public function mapping($callback): ExcelReader
52 | {
53 | $this->sheet()->mapping($callback);
54 |
55 | return $this;
56 | }
57 |
58 | /**
59 | * @param string $modelClass
60 | * @param string|bool|null $address
61 | * @param array|bool|null $columns
62 | *
63 | * @return $this
64 | */
65 | public function importModel(string $modelClass, $address = null, $columns = null): ExcelReader
66 | {
67 | $this->sheet()->importModel($modelClass, $address, $columns);
68 |
69 | return $this;
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/src/FastExcelLaravel/ExcelWriter.php:
--------------------------------------------------------------------------------
1 | $tempDir];
36 | }
37 | else {
38 | $options['temp_dir'] = $tempDir;
39 | }
40 | }
41 | $excel = new self($options);
42 | if ($sheets) {
43 | if (is_array($sheets)) {
44 | foreach ($sheets as $sheetName) {
45 | $excel->makeSheet($sheetName);
46 | }
47 | }
48 | else {
49 | $excel->makeSheet((string)$sheets);
50 | }
51 | }
52 | else {
53 | $excel->makeSheet();
54 | }
55 |
56 | return $excel;
57 | }
58 |
59 | /**
60 | * Create SheetWriter instance
61 | *
62 | * @param string $sheetName
63 | *
64 | * @return SheetWriter
65 | */
66 | public static function createSheet(string $sheetName): SheetWriter
67 | {
68 | return new SheetWriter($sheetName);
69 | }
70 |
71 | /**
72 | * Returns sheet by number or name of sheet.
73 | * Return the first sheet if number or name omitted
74 | *
75 | * @param int|string|null $index - number or name of sheet
76 | *
77 | * @return SheetWriter
78 | */
79 | public function sheet($index = null): SheetWriter
80 | {
81 | return parent::sheet($index);
82 | }
83 |
84 |
85 | /**
86 | * @param $model
87 | * @param array|null $rowStyle
88 | * @param array|null $cellStyles
89 | *
90 | * @return $this
91 | */
92 | public function exportModel($model, array $rowStyle = null, array $cellStyles = null): ExcelWriter
93 | {
94 | $this->getSheet()->exportModel($model, $rowStyle, $cellStyles);
95 |
96 | return $this;
97 | }
98 |
99 | /**
100 | * @param $data
101 | *
102 | * @return $this
103 | */
104 | public function writeData($data): ExcelWriter
105 | {
106 | $this->getSheet()->writeData($data);
107 |
108 | return $this;
109 | }
110 |
111 | /**
112 | * Save file to local storage
113 | *
114 | * @param string $filePath
115 | *
116 | * @return bool
117 | */
118 | public function saveTo(string $filePath): bool
119 | {
120 | $this->save(storage_path($filePath));
121 |
122 | return true;
123 | }
124 |
125 | /**
126 | * Store file to specified disk
127 | *
128 | * @param $disk
129 | * @param $path
130 | *
131 | * @return bool
132 | *
133 | * @throws \Illuminate\Contracts\Filesystem\FileExistsException
134 | */
135 | public function store($disk, $path): bool
136 | {
137 | $result = false;
138 | $tmpFile = $this->writer->makeTempFile();
139 | if ($this->writer->saveToFile($tmpFile, true, $this->getMetadata())) {
140 | $this->saved = true;
141 |
142 | $handle = fopen($tmpFile, 'rb');
143 | if ($handle) {
144 | $result = \Storage::disk($disk)->writeStream($path, $handle);
145 | fclose($handle);
146 | }
147 | }
148 | $this->writer->removeFiles();
149 |
150 | return $result;
151 | }
152 | }
153 |
--------------------------------------------------------------------------------
/src/FastExcelLaravel/Facades/Excel.php:
--------------------------------------------------------------------------------
1 | app->bind('excel', function ($app, $data = null) {
29 | if (is_array($data)) {
30 | $data = collect($data);
31 | }
32 |
33 | return new \avadim\FastExcelLaravel\Excel();
34 | });
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/FastExcelLaravel/SheetReader.php:
--------------------------------------------------------------------------------
1 | resultMode = \avadim\FastExcelReader\Excel::KEYS_FIRST_ROW;
22 |
23 | return $this;
24 | }
25 |
26 | /**
27 | * @param $callback
28 | *
29 | * @return $this
30 | */
31 | public function mapping($callback): SheetReader
32 | {
33 | if (is_array($callback)) {
34 | $mapArray = $callback;
35 | $callback = function ($row) use($mapArray) {
36 | $record = [];
37 | foreach ($row as $col => $value) {
38 | if (isset($mapArray[$col])) {
39 | $record[$mapArray[$col]] = $value;
40 | }
41 | else {
42 | $record[$col] = $value;
43 | }
44 | }
45 | return $record;
46 | };
47 | }
48 | $this->mappingCallback = $callback;
49 |
50 | return $this;
51 | }
52 |
53 | /**
54 | * Load models from Excel to database
55 | * loadModels(User::class)
56 | * loadModels(User::class, true) -- the first row used as a field names
57 | * loadModels(User::class, 'B:D') -- read data from columns B:D
58 | * loadModels(User::class, 'B3') -- read data from area started at B3
59 | * loadModels(User::class, 'B3', true) -- read data from area started at B3 and the first row used as a field names
60 | *
61 | * @param $modelClass
62 | * @param $address
63 | * @param $columns
64 | *
65 | * @return $this
66 | */
67 | public function importModel($modelClass, $address = null, $columns = null): SheetReader
68 | {
69 | if ($address && is_string($address)) {
70 | $this->setReadArea($address);
71 | }
72 | foreach ($this->nextRow($columns, $this->resultMode) as $rowData) {
73 | /** @var Model $model */
74 | $model = new $modelClass;
75 | if ($this->mappingCallback) {
76 | $rowData = call_user_func($this->mappingCallback, $rowData);
77 | }
78 | $model->fill($rowData);
79 | $model->save();
80 | }
81 | $this->resultMode = 0;
82 |
83 | return $this;
84 | }
85 | }
--------------------------------------------------------------------------------
/src/FastExcelLaravel/SheetWriter.php:
--------------------------------------------------------------------------------
1 | toArray();
29 | }
30 | else {
31 | $result = json_decode(json_encode($record), true);
32 | }
33 | }
34 | else {
35 | $result = (array)$record;
36 | }
37 |
38 | return $result;
39 | }
40 |
41 | /**
42 | * @param $record
43 | *
44 | * @return void
45 | */
46 | protected function _writeHeader($record)
47 | {
48 | if (!$this->headers['header_keys']) {
49 | $this->headers['header_keys'] = array_keys($this->_toArray($record));
50 | }
51 | if (!$this->headers['header_values']) {
52 | $this->headers['header_values'] = $this->headers['header_keys'];
53 | }
54 |
55 | //$row = array_combine($this->headers['header_keys'], $this->headers['header_values']);
56 | $row = $this->headers['header_values'];
57 | $this->writeHeader($row, $this->headers['row_style'], $this->headers['col_styles']);
58 | ++$this->dataRowCount;
59 | }
60 |
61 | /**
62 | * @param array $rowValues
63 | * @param array|null $rowStyle
64 | * @param array|null $cellStyles
65 | *
66 | * @return SheetWriter
67 | */
68 | public function writeRow(array $rowValues = [], array $rowStyle = null, array $cellStyles = null): SheetWriter
69 | {
70 | if ($this->dataRowCount > 0 && !empty($this->headers['header_keys'])) {
71 | $rowData = [];
72 | foreach ($this->headers['header_keys'] as $key) {
73 | if (isset($rowValues[$key])) {
74 | $rowData[$key] = $rowValues[$key];
75 | }
76 | else {
77 | $rowData[] = null;
78 | }
79 | }
80 | }
81 | else {
82 | $rowData = $rowValues;
83 | }
84 | if ($this->attrFormats) {
85 | $cellStyles = (array)$cellStyles;
86 | foreach (array_keys($rowData) as $n => $attribute) {
87 | if (isset($this->attrFormats[$attribute])) {
88 | $cellStyles[$n]['format'] = $this->attrFormats[$attribute];
89 | }
90 | }
91 | }
92 |
93 | return parent::writeRow($rowData, $rowStyle, $cellStyles);
94 | }
95 |
96 | /**
97 | * @param $data
98 | * @param array|null $rowStyle
99 | * @param array|null $colStyles
100 | *
101 | * @return $this
102 | */
103 | public function writeData($data, array $rowStyle = null, array $colStyles = null): SheetWriter
104 | {
105 | if (is_array($data) || ($data instanceof Collection)) {
106 | foreach ($data as $record) {
107 | if ($this->dataRowCount === 0 && $this->headers) {
108 | $this->_writeHeader($record);
109 | }
110 | if ($this->mappingCallback) {
111 | $record = call_user_func($this->mappingCallback, $record);
112 | }
113 | $this->writeRow($this->_toArray($record), $rowStyle, $colStyles);
114 | ++$this->dataRowCount;
115 | }
116 | }
117 | elseif (is_callable($data)) {
118 | foreach ($data() as $record) {
119 | if ($this->dataRowCount === 0 && $this->headers) {
120 | $this->_writeHeader($record);
121 | }
122 | if ($this->mappingCallback) {
123 | $record = call_user_func($this->mappingCallback, $record);
124 | }
125 | $this->writeRow($this->_toArray($record), $rowStyle, $colStyles);
126 | ++$this->dataRowCount;
127 | }
128 | }
129 |
130 | return $this;
131 | }
132 |
133 | /**
134 | * @param $model
135 | * @param array|null $rowStyle
136 | * @param array|null $colStyles
137 | *
138 | * @return $this
139 | */
140 | public function exportModel($model, array $rowStyle = null, array $colStyles = null): SheetWriter
141 | {
142 | $this->writeData(static function() use ($model) {
143 | foreach ($model::cursor() as $user) {
144 | yield $user;
145 | }
146 | }, $rowStyle, $colStyles);
147 | $this->headers = [];
148 |
149 | return $this;
150 | }
151 |
152 | /**
153 | * @param array|null $headers
154 | * @param array|null $rowStyle
155 | * @param array|null $colStyles
156 | *
157 | * @return $this
158 | */
159 | public function withHeadings(?array $headers = [], ?array $rowStyle = [], ?array $colStyles = []): SheetWriter
160 | {
161 | $headerKeys = $headerValues = [];
162 | if ($headers) {
163 | foreach ($headers as $key => $val) {
164 | if (is_string($key)) {
165 | $headerKeys[] = $key;
166 | $headerValues[] = $val;
167 | }
168 | else {
169 | $headerKeys[] = $headerValues[] = $val;
170 | }
171 | }
172 | }
173 |
174 | $this->headers = [
175 | 'header_keys' => $headerKeys,
176 | 'header_values' => $headerValues,
177 | 'row_style' => $rowStyle,
178 | 'col_styles' => $colStyles,
179 | ];
180 | $this->lastTouch['ref'] = 'row';
181 |
182 | return $this;
183 | }
184 |
185 | /**
186 | * @param $callback
187 | *
188 | * @return $this
189 | */
190 | public function mapping($callback): SheetWriter
191 | {
192 | $this->mappingCallback = $callback;
193 |
194 | return $this;
195 | }
196 |
197 | /**
198 | * @param array $formats
199 | *
200 | * @return $this
201 | */
202 | public function formatAttributes(array $formats): SheetWriter
203 | {
204 | $this->attrFormats = array_replace($this->attrFormats, $formats);
205 |
206 | return $this;
207 | }
208 | }
209 |
--------------------------------------------------------------------------------
/src/autoload.php:
--------------------------------------------------------------------------------
1 | testStorage = __DIR__ . '/test_storage';
27 |
28 | $this->app['path.storage'] = $this->testStorage;
29 |
30 | $this->setUpDatabase();
31 | }
32 |
33 | protected function getEnvironmentSetUp($app)
34 | {
35 | $app['config']->set('database.default', 'sqlite');
36 | $app['config']->set('database.connections.sqlite', [
37 | 'driver' => 'sqlite',
38 | 'database' => ':memory:',
39 | 'prefix' => '',
40 | ]);
41 | }
42 |
43 | protected function setUpDatabase()
44 | {
45 | //$res = $this->artisan('migrate')->run();
46 | //$this->loadMigrationsFrom(__DIR__ . '/../database/migrations/000_create_test_models_table.php');
47 | }
48 |
49 |
50 | protected function getValue($cell)
51 | {
52 | preg_match('/^(\w+)(\d+)$/', strtoupper($cell), $m);
53 |
54 | return $this->cells[$m[2]][$m[1]]['v'] ?? null;
55 | }
56 |
57 | protected function getValues(...$cells): array
58 | {
59 | $result = [];
60 | foreach ($cells as $cell) {
61 | $result[] = $this->getValue($cell);
62 | }
63 |
64 | return $result;
65 | }
66 |
67 | protected function getStyle($cell, $flat = false): array
68 | {
69 | preg_match('/^(\w+)(\d+)$/', strtoupper($cell), $m);
70 | $styleIdx = $this->cells[$m[2]][$m[1]]['s'] ?? null;
71 | if ($styleIdx !== null) {
72 | $style = $this->excelReader->getCompleteStyleByIdx($styleIdx);
73 | if ($flat) {
74 | $result = [];
75 | foreach ($style as $key => $val) {
76 | $result = array_merge($result, $val);
77 | }
78 | }
79 | else {
80 | $result = $style;
81 | }
82 |
83 | return $result;
84 | }
85 |
86 | return [];
87 | }
88 |
89 |
90 | protected function getDataArray(): array
91 | {
92 | return FakeModel::getRecords();
93 | }
94 |
95 | protected function getDataCollectionStd(): Collection
96 | {
97 | $data = $this->getDataArray();
98 | $result = [];
99 | foreach ($data as $row) {
100 | $result[] = (object)$row;
101 | }
102 |
103 | return collect($result);
104 | }
105 |
106 | protected function read($testFileName)
107 | {
108 | $this->assertTrue(file_exists($testFileName));
109 |
110 | $this->excelReader = ExcelReader::open($testFileName);
111 | $this->cells = $this->excelReader->readRows(false, null, true);
112 | }
113 |
114 |
115 | protected function startExportTest($testFileName, $sheets = []): ExcelWriter
116 | {
117 | if (file_exists($testFileName)) {
118 | unlink($testFileName);
119 | }
120 | elseif (file_exists(storage_path($testFileName))) {
121 | unlink(storage_path($testFileName));
122 | }
123 | FakeModel::$storage = [];
124 |
125 | return Excel::create($sheets);
126 | }
127 |
128 | protected function endExportTest($testFileName)
129 | {
130 | $this->excelReader = null;
131 | $this->cells = [];
132 |
133 | if (file_exists($testFileName)) {
134 | unlink($testFileName);
135 | }
136 | elseif (file_exists(storage_path($testFileName))) {
137 | unlink(storage_path($testFileName));
138 | }
139 | }
140 |
141 | ///////////////////////////////////////////////////////
142 | ///
143 | public function testExportArray()
144 | {
145 | $testFileName = 'test1.xlsx';
146 | $excel = $this->startExportTest($testFileName);
147 |
148 | /** @var SheetWriter $sheet */
149 | $sheet = $excel->getSheet();
150 |
151 | $data = $this->getDataArray();
152 | $sheet->writeData($data);
153 |
154 | \Config::set('filesystems.disks.dynamic', [
155 | 'driver' => 'local',
156 | 'root' => $this->testStorage . '/dynamic',
157 | ]);
158 |
159 | if (\Storage::disk('dynamic')->exists($testFileName)) {
160 | \Storage::disk('dynamic')->delete($testFileName);
161 | }
162 | $excel->store('dynamic', $testFileName);
163 | $path = \Storage::disk('dynamic')->path($testFileName);
164 | //$excel->save($testFileName);
165 |
166 | $this->read($path);
167 |
168 | $this->assertEquals(array_values($data[0]), $this->getValues('A1', 'B1', 'C1', 'D1'));
169 |
170 | $this->endExportTest($path);
171 | }
172 |
173 | public function testExportArrayWithHeaders()
174 | {
175 | $testFileName = __DIR__ . '/test2.xlsx';
176 | $excel = $this->startExportTest($testFileName);
177 |
178 | /** @var SheetWriter $sheet */
179 | $sheet = $excel->getSheet();
180 |
181 | $data = $this->getDataArray();
182 | $sheet->withHeadings()->writeData($data);
183 | $excel->save($testFileName);
184 |
185 | $this->read($testFileName);
186 | $row = $data[1];
187 |
188 | $this->assertEquals(array_keys($row), $this->getValues('A1', 'B1', 'C1', 'D1'));
189 | $this->assertEquals(array_values($row), $this->getValues('A3', 'B3', 'C3', 'D3'));
190 |
191 | $this->endExportTest($testFileName);
192 | }
193 |
194 | public function testExportCollection()
195 | {
196 | $testFileName = __DIR__ . '/test3.xlsx';
197 | $excel = $this->startExportTest($testFileName);
198 |
199 | /** @var SheetWriter $sheet */
200 | $sheet = $excel->getSheet();
201 |
202 | $data = $this->getDataArray();
203 | $sheet->writeData(collect($this->getDataCollectionStd()));
204 | $excel->save($testFileName);
205 |
206 | $this->read($testFileName);
207 |
208 | $this->assertEquals(array_values($data[0]), $this->getValues('A1', 'B1', 'C1', 'D1'));
209 |
210 | $this->endExportTest($testFileName);
211 | }
212 |
213 | public function testExportCollectionWithHeaders()
214 | {
215 | $testFileName = 'test4.xlsx';
216 | $excel = $this->startExportTest($testFileName);
217 |
218 | /** @var SheetWriter $sheet */
219 | $sheet = $excel->getSheet();
220 |
221 | $sheet->withHeadings(['date', 'name'])
222 | ->applyFontStyleBold()
223 | ->applyBorder('thin')
224 | ->writeData(collect($this->getDataCollectionStd()));
225 | $excel->saveTo($testFileName);
226 |
227 | $this->read(storage_path($testFileName));
228 |
229 | $this->assertEquals(['1753-01-31', 'Captain Jack Sparrow', null, null], $this->getValues('A4', 'B4', 'C4', 'D4'));
230 |
231 | $this->endExportTest($testFileName);
232 | }
233 |
234 | public function testExportMultipleSheets()
235 | {
236 | $testFileName = 'test5.xlsx';
237 | $excel = $this->startExportTest($testFileName);
238 |
239 | $sheet = $excel->makeSheet('Collection');
240 | $collection = collect([
241 | [ 'id' => 1, 'site' => 'google.com' ],
242 | [ 'id' => 2, 'site.com' => 'youtube.com' ],
243 | ]);
244 | $sheet->writeData($collection);
245 |
246 | $sheet = $excel->makeSheet('Array');
247 | $array = [
248 | [ 'id' => 1, 'name' => 'Helen' ],
249 | [ 'id' => 2, 'name' => 'Peter' ],
250 | ];
251 | $sheet->writeData($array);
252 |
253 | $sheet = $excel->makeSheet('Callback');
254 | $sheet->writeData(function () {
255 | for ($i = 1; $i <= 3; $i++) {
256 | yield [$i, $i * 2, $i * 3];
257 | }
258 | });
259 |
260 | $excel->saveTo($testFileName);
261 | $file = storage_path($testFileName);
262 |
263 | $this->assertTrue(file_exists($file));
264 |
265 | $this->excelReader = ExcelReader::open($file);
266 | $this->excelReader->selectSheet('Collection');
267 | $this->cells = $this->excelReader->readRows(false, null, true);
268 | $this->assertEquals('youtube.com', $this->getValue('b2'));
269 |
270 | $this->excelReader->selectSheet('Array');
271 | $this->cells = $this->excelReader->readRows(false, null, true);
272 | $this->assertEquals('Peter', $this->getValue('b2'));
273 |
274 | $this->excelReader->selectSheet('Callback');
275 | $this->cells = $this->excelReader->readRows(false, null, true);
276 | $this->assertEquals(9, $this->getValue('C3'));
277 |
278 | $this->endExportTest($testFileName);
279 | }
280 |
281 | public function testExportAdvanced()
282 | {
283 | $testFileName = 'test6.xlsx';
284 | $excel = $this->startExportTest($testFileName);
285 |
286 | /** @var SheetWriter $sheet */
287 | $sheet = $excel->getSheet();
288 |
289 | $sheet->setColWidth('B', 12);
290 | $sheet->setColOptions('c', ['width' => 12, 'text-align' => 'center']);
291 | $sheet->setColWidth('d', 'auto');
292 |
293 | $title = 'This is demo of avadim/fast-excel-laravel';
294 | $area = $sheet->beginArea();
295 | $area->setValue('A2:D2', $title)
296 | ->applyFontSize(14)
297 | ->applyFontStyleBold()
298 | ->applyTextCenter();
299 |
300 | $area
301 | ->setValue('a4:a5', '#')
302 | ->setValue('b4:b5', 'Number')
303 | ->setValue('c4:d4', 'Movie Character')
304 | ->setValue('c5', 'Birthday')
305 | ->setValue('d5', 'Name')
306 | ;
307 | $area->withRange('a4:d5')
308 | ->applyBgColor('#ccc')
309 | ->applyFontStyleBold()
310 | ->applyOuterBorder('thin')
311 | ->applyInnerBorder('thick')
312 | ->applyTextCenter();
313 | $sheet->writeAreas();
314 |
315 | $sheet->writeData(collect($this->getDataCollectionStd()));
316 | $excel->saveTo($testFileName);
317 |
318 | $this->read(storage_path($testFileName));
319 |
320 | $this->assertEquals([982630, '2179-08-12', 'Ellen Louise Ripley', null], $this->getValues('B7', 'C7', 'D7', 'e7'));
321 |
322 | $this->endExportTest($testFileName);
323 | }
324 |
325 | public function testImportModel()
326 | {
327 | $testFileName = 'test_model.xlsx';
328 | $excel = Excel::open(storage_path($testFileName));
329 | $this->assertEquals('Sheet1', $excel->sheet()->name());
330 |
331 | FakeModel::$storage = [];
332 | $excel->withHeadings()->importModel(FakeModel::class);
333 | $this->assertCount(3, FakeModel::$storage);
334 | $this->assertEquals('James Bond', FakeModel::$storage[0]->name);
335 |
336 | FakeModel::$storage = [];
337 | $excel->setDateFormat('Y-m-d');
338 | $excel->mapping(['A' => 'foo', 'B' => 'bar', 'C' => 'int'])->importModel(FakeModel::class, 'B4');
339 | $this->assertEquals('1753-01-31', FakeModel::$storage[0]->bar);
340 |
341 | $testFileName = 'test_model2.xlsx';
342 | $excel = Excel::open(storage_path($testFileName));
343 |
344 | FakeModel::$storage = [];
345 | $excel->withHeadings()->importModel(FakeModel::class, 'b4');
346 | $this->assertCount(3, FakeModel::$storage);
347 | $this->assertEquals('James Bond', FakeModel::$storage[0]->name);
348 |
349 | FakeModel::$storage = [];
350 | $excel->importModel(FakeModel::class, 'b5:d5', ['B' => 'foo', 'C' => 'bar', 'D' => 'int']);
351 | $this->assertCount(1, FakeModel::$storage);
352 | $this->assertEquals('James Bond', FakeModel::$storage[0]->foo);
353 | $this->assertFalse(isset(FakeModel::$storage[1]));
354 |
355 | FakeModel::$storage = [];
356 | $excel->setDateFormat('Y-m-d');
357 | $excel->importModel(FakeModel::class, 'b5', ['B' => 'foo', 'C' => 'bar', 'D' => 'int']);
358 | $this->assertCount(3, FakeModel::$storage);
359 | $this->assertEquals('Captain Jack Sparrow', FakeModel::$storage[2]->foo);
360 | $this->assertEquals('1753-01-31', FakeModel::$storage[2]->bar);
361 | $this->assertEquals(7239, FakeModel::$storage[2]->int);
362 |
363 | $sheet = $excel->sheet();
364 | $sheet->setReadArea('b5');
365 | $result = [];
366 | foreach ($sheet->nextRow() as $rowNum => $rowData) {
367 | $result[$rowNum] = $rowData;
368 | }
369 | $this->assertCount(3, $result);
370 | $this->assertEquals('James Bond', $result[5]['B']);
371 | $this->assertEquals('Ellen Louise Ripley', $result[6]['B']);
372 | $this->assertEquals('Captain Jack Sparrow', $result[7]['B']);
373 | }
374 |
375 |
376 | public function testExportImport()
377 | {
378 | $data = $this->getDataArray();
379 | $testFileName = __DIR__ . '/test_io.xlsx';
380 |
381 | // ** 1 ** mapping import
382 | FakeModel::$storage = [];
383 | $excel = $this->startExportTest($testFileName);
384 | $sheet = $excel->getSheet();
385 |
386 | $sheet->exportModel(FakeModel::class);
387 | $excel->save($testFileName);
388 |
389 | $this->assertTrue(file_exists($testFileName));
390 |
391 | $excel = Excel::open($testFileName);
392 | $sheet = $excel->getSheet();
393 | $sheet->mapping(function ($record) {
394 | return [
395 | 'id' => $record['A'], 'integer' => $record['B'], 'date' => $record['C'], 'name' => $record['D'],
396 | ];
397 | })->importModel(FakeModel::class);
398 | $this->assertEquals($data, FakeModel::storageArray());
399 |
400 | // ** 2 ** mapping export/import
401 | FakeModel::$storage = [];
402 | $excel = $this->startExportTest($testFileName);
403 | $sheet = $excel->getSheet();
404 |
405 | $sheet->mapping(function($model) {
406 | return [
407 | 'id' => $model->id, 'integer' => $model->integer, 'date' => $model->date, 'name' => $model->name,
408 | ];
409 | })->exportModel(FakeModel::class);
410 | $excel->save($testFileName);
411 |
412 | $this->assertTrue(file_exists($testFileName));
413 |
414 | $excel = Excel::open($testFileName);
415 | $sheet = $excel->getSheet();
416 | $sheet->mapping(function ($record) {
417 | return [
418 | 'id' => $record['A'], 'integer' => $record['B'], 'date' => $record['C'], 'name' => $record['D'],
419 | ];
420 | })->importModel(FakeModel::class);
421 | $this->assertEquals($data, FakeModel::storageArray());
422 | }
423 |
424 | public function testExportImportHead()
425 | {
426 | $data = $this->getDataArray();
427 | $testFileName = __DIR__ . '/test_io.xlsx';
428 |
429 | // ** 3 ** export/import with heading
430 | $excel = $this->startExportTest($testFileName);
431 |
432 | /** @var SheetWriter $sheet */
433 | $sheet = $excel->getSheet();
434 | $sheet->withHeadings()->mapping(function ($model) {
435 | return ['id' => $model->id, 'integer' => $model->integer, 'date' => Carbon::parse($model->date)->getTimestamp(), 'name' => $model->name];
436 | })->exportModel(FakeModel::class);
437 | $sheet->withHeadings()->mapping(function ($model) {
438 | return ['id' => $model->id, 'integer' => $model->integer, 'date' => Carbon::parse($model->date)->getTimestamp(), 'name' => $model->name];
439 | })->exportModel(FakeModel::class);
440 | $excel->save($testFileName);
441 |
442 | $this->assertTrue(file_exists($testFileName));
443 | /*
444 | $excel = Excel::open($testFileName);
445 | $sheet = $excel->getSheet();
446 | $sheet->withHeadings()->importModel(FakeModel::class);
447 | $this->assertEquals($data, FakeModel::storageArray());
448 |
449 | // ** 4 ** format dates
450 | $excel = $this->startExportTest($testFileName);
451 |
452 | $sheet = $excel->getSheet();
453 | $sheet->withHeadings()->setFieldFormats(['date' => '@date'])->exportModel(FakeModel::class);
454 | $excel->save($testFileName);
455 |
456 | $this->assertTrue(file_exists($testFileName));
457 |
458 | $excel = Excel::open($testFileName);
459 | $excel->setDateFormat('Y-m-d');
460 | $sheet = $excel->getSheet();
461 | $sheet->withHeadings()->importModel(FakeModel::class);
462 | $this->assertEquals($data, FakeModel::storageArray());
463 |
464 | $this->endExportTest($testFileName);
465 | */
466 | }
467 | }
--------------------------------------------------------------------------------
/tests/test_storage/test_model.xlsx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aVadim483/fast-excel-laravel/db4c30ca41110bdf3127a9073651954e1c0d9710/tests/test_storage/test_model.xlsx
--------------------------------------------------------------------------------
/tests/test_storage/test_model2.xlsx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aVadim483/fast-excel-laravel/db4c30ca41110bdf3127a9073651954e1c0d9710/tests/test_storage/test_model2.xlsx
--------------------------------------------------------------------------------