├── .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 | 11 | 12 |
FastExcelLaravel 5 |

6 | 7 | # FastExcelLaravel 8 | 9 |

10 |
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 | ![import.jpg](import.jpg) 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 | ![import2.jpg](import2.jpg) 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 --------------------------------------------------------------------------------