Repository: SpartnerNL/Laravel-Excel Branch: 3.1 Commit: 1854739267d8 Files: 311 Total size: 755.5 KB Directory structure: gitextract_hvn1oki5/ ├── .gitattributes ├── .github/ │ ├── FUNDING.yml │ ├── ISSUE_TEMPLATE/ │ │ ├── 1_Bug_report.yml │ │ └── config.yml │ ├── ISSUE_TEMPLATE.md │ ├── PULL_REQUEST_TEMPLATE.md │ ├── SUPPORT.md │ ├── issuecomplete.yml │ ├── stale.yml │ └── workflows/ │ └── run-tests.yml ├── .gitignore ├── .phpunit.cache/ │ └── test-results ├── .styleci.yml ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── SECURITY.md ├── composer.json ├── config/ │ └── excel.php ├── phpunit.xml.dist ├── 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 └── tests/ ├── Cache/ │ └── BatchCacheTest.php ├── CellTest.php ├── Concerns/ │ ├── ExportableTest.php │ ├── FromArrayTest.php │ ├── FromCollectionTest.php │ ├── FromGeneratorTest.php │ ├── FromIteratorTest.php │ ├── FromQueryTest.php │ ├── FromViewTest.php │ ├── ImportableTest.php │ ├── OnEachRowTest.php │ ├── RegistersEventListenersTest.php │ ├── RemembersChunkOffsetTest.php │ ├── RemembersRowNumberTest.php │ ├── ShouldQueueWithoutChainTest.php │ ├── SkipsEmptyRowsTest.php │ ├── SkipsOnErrorTest.php │ ├── SkipsOnFailureTest.php │ ├── ToArrayTest.php │ ├── ToCollectionTest.php │ ├── ToModelTest.php │ ├── WithBackgroundColorTest.php │ ├── WithBatchInsertsTest.php │ ├── WithCalculatedFormulasTest.php │ ├── WithChunkReadingTest.php │ ├── WithColumnFormattingTest.php │ ├── WithColumnLimitTest.php │ ├── WithColumnWidthsTest.php │ ├── WithConditionalSheetsTest.php │ ├── WithCustomCsvSettingsTest.php │ ├── WithCustomQuerySizeTest.php │ ├── WithCustomStartCellTest.php │ ├── WithCustomValueBinderTest.php │ ├── WithDefaultStylesTest.php │ ├── WithEventsTest.php │ ├── WithFormatDataTest.php │ ├── WithGroupedHeadingRowTest.php │ ├── WithHeadingRowTest.php │ ├── WithHeadingsTest.php │ ├── WithLimitTest.php │ ├── WithMappedCellsTest.php │ ├── WithMappingTest.php │ ├── WithMultipleSheetsTest.php │ ├── WithPropertiesTest.php │ ├── WithReadFilterTest.php │ ├── WithSkipDuplicatesTest.php │ ├── WithStartRowTest.php │ ├── WithStrictNullComparisonTest.php │ ├── WithStylesTest.php │ ├── WithTitleTest.php │ ├── WithUpsertsTest.php │ └── WithValidationTest.php ├── Data/ │ ├── Disks/ │ │ └── .gitignore │ └── Stubs/ │ ├── AfterQueueExportJob.php │ ├── AfterQueueImportJob.php │ ├── BeforeExportListener.php │ ├── ChainedJobStub.php │ ├── CustomConcern.php │ ├── CustomSheetConcern.php │ ├── CustomTransactionHandler.php │ ├── Database/ │ │ ├── Factories/ │ │ │ ├── GroupFactory.php │ │ │ └── UserFactory.php │ │ ├── Group.php │ │ ├── Migrations/ │ │ │ ├── 0000_00_00_000000_create_groups_table.php │ │ │ ├── 0000_00_00_000001_create_group_user_table.php │ │ │ ├── 0000_00_00_000002_add_group_id_to_users_table.php │ │ │ └── 0000_00_00_000002_add_options_to_users.php │ │ └── User.php │ ├── EloquentCollectionWithMappingExport.php │ ├── EloquentLazyCollectionExport.php │ ├── EloquentLazyCollectionQueuedExport.php │ ├── EmptyExport.php │ ├── ExportWithEvents.php │ ├── ExportWithEventsChunks.php │ ├── ExportWithRegistersEventListeners.php │ ├── FromGroupUsersQueuedQueryExport.php │ ├── FromNestedArraysQueryExport.php │ ├── FromNonEloquentQueryExport.php │ ├── FromQueryWithCustomQuerySize.php │ ├── FromUsersQueryExport.php │ ├── FromUsersQueryExportWithEagerLoad.php │ ├── FromUsersQueryExportWithMapping.php │ ├── FromUsersQueryExportWithPrepareRows.php │ ├── FromUsersQueryWithJoinExport.php │ ├── FromUsersScoutExport.php │ ├── FromViewExportWithMultipleSheets.php │ ├── ImportWithEvents.php │ ├── ImportWithEventsChunksAndBatches.php │ ├── ImportWithRegistersEventListeners.php │ ├── QueueImportWithoutJobChaining.php │ ├── QueuedExport.php │ ├── QueuedExportWithFailedEvents.php │ ├── QueuedExportWithFailedHook.php │ ├── QueuedExportWithLocalePreferences.php │ ├── QueuedImport.php │ ├── QueuedImportWithFailure.php │ ├── QueuedImportWithMiddleware.php │ ├── QueuedImportWithRetryUntil.php │ ├── SheetForUsersFromView.php │ ├── SheetWith100Rows.php │ ├── ShouldQueueExport.php │ ├── Views/ │ │ └── users.blade.php │ ├── WithMappingExport.php │ └── WithTitleExport.php ├── DelegatedMacroableTest.php ├── ExcelFakeTest.php ├── ExcelServiceProviderTest.php ├── ExcelTest.php ├── HeadingRowImportTest.php ├── Helpers/ │ └── FileHelper.php ├── InteractsWithQueueTest.php ├── Mixins/ │ ├── DownloadCollectionTest.php │ ├── DownloadQueryMacroTest.php │ ├── ImportAsMacroTest.php │ ├── ImportMacroTest.php │ ├── StoreCollectionTest.php │ └── StoreQueryMacroTest.php ├── QueuedExportTest.php ├── QueuedImportTest.php ├── QueuedQueryExportTest.php ├── QueuedViewExportTest.php ├── TemporaryFileTest.php ├── TestCase.php └── Validators/ └── RowValidatorTest.php ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitattributes ================================================ * text eol=lf /.github export-ignore /tests export-ignore .gitattributes export-ignore .gitignore export-ignore .travis.yml export-ignore phpunit.xml.dist export-ignore ================================================ FILE: .github/FUNDING.yml ================================================ # These are supported funding model platforms github: patrickbrouwers custom: https://laravel-excel.com/commercial-support ================================================ FILE: .github/ISSUE_TEMPLATE/1_Bug_report.yml ================================================ name: Bug Report description: Report a general package issue. Filling in the issue template is mandatory, issues without it will be closed. Please ensure your Laravel-Excel version is still supported (Currently ^3.1) title: "[Bug]: " labels: [bug] body: - type: checkboxes id: terms attributes: label: Is the bug applicable and reproducable to the latest version of the package and hasn't it been reported before? description: Currently 3.1.x options: - label: Yes, it's still reproducable required: true - type: input attributes: label: What version of Laravel Excel are you using? description: 'For example: 3.1.30' validations: required: true - type: input attributes: label: What version of Laravel are you using? description: 'For example: 7.1.10' validations: required: true - type: input attributes: label: What version of PHP are you using? description: 'For example: 8.1.0' validations: required: true - type: textarea attributes: label: Describe your issue description: Describe the problem you're seeing, Please be short, but concise. validations: required: true - type: textarea attributes: label: How can the issue be reproduced? description: Please provide easy-to-reproduce steps (repository, simple code example, failing unit test). Please don't paste your entire code, but create a reproducable scenario that can be tested using a simple User model in a blank Laravel installation. validations: required: true - type: textarea attributes: label: What should be the expected behaviour? description: Please describe what the expected outcome should be. Any suggestions to what is wrong? validations: required: true ================================================ FILE: .github/ISSUE_TEMPLATE/config.yml ================================================ blank_issues_enabled: false contact_links: - name: 🧐 Questions url: https://github.com/SpartnerSoftware/Laravel-Excel/discussions about: The issuer tracker is focused on reporting bugs or problems. Please use the Discussions tabs for questions! - name: 💡 Proposals url: https://github.com/SpartnerSoftware/Laravel-Excel/discussions about: If you have a 🔥 idea. Please use the Discussions tab for this! - name: 📚 Documentation improvements url: https://github.com/SpartnerSoftware/laravel-excel-docs about: Our documentation is open-source, you can make a PR with your suggested changes over there. - name: Commercial Support url: https://laravel-excel.com/commercial-support about: If you need urgent help we can offer this on commercial basis. Send us an e-mail to discuss the posibilities and our hourly rate. ================================================ FILE: .github/ISSUE_TEMPLATE.md ================================================ 1️⃣ Is the bug applicable and reproducable to the latest version of the package and hasn't it been reported before? (Currently 3.1.x) 2️⃣ What version of the package, Laravel and PHP do you use? Please provide the FULL version number. - Laravel Excel version: [e.g. 3.1.30] - Laravel version: [e.g. 8.3.1] - PHP version: [e.g. 7.4.0] 3️⃣ Describe your issue. Please be short, but concise. 4️⃣ How can the issue be reproduced? Please provide easy-to-reproduce steps (repository, simple code example, failing unit test). 5️⃣ What should be the expected behaviour? 6️⃣ Have you filled in the entire issue template? If not, your issue will be closed. Thanks for using the package! 🙌 ================================================ FILE: .github/PULL_REQUEST_TEMPLATE.md ================================================ Please take note of our contributing guidelines: https://docs.laravel-excel.com/3.1/getting-started/contributing.html Filling out the template is required. Any pull request that does not include enough information to be reviewed in a timely manner may be closed at the maintainers' discretion. 1️⃣ Why should it be added? What are the benefits of this change? 2️⃣ Does it contain multiple, unrelated changes? Please separate the PRs out. 3️⃣ Does it include tests, if possible? 4️⃣ Any drawbacks? Possible breaking changes? 5️⃣ Mark the following tasks as done: - [ ] Checked the codebase to ensure that your feature doesn't already exist. - [ ] Take note of the contributing guidelines. - [ ] Checked the pull requests to ensure that another person hasn't already submitted a fix. - [ ] Added tests to ensure against regression. 6️⃣ Thanks for contributing! 🙌 ================================================ FILE: .github/SUPPORT.md ================================================ # Support This repository is focused on bugs and issues. We unfortunately can't answer all support questions in a timely manner. Our software is free and open source, meaning that the use of our software is optional. We hold no liability and there is no obligation to support. We will provide support on a best effort basis. If you use the software commercially and need elaborate support or need it urgently, we can offer this on a commercial basis. Please contact info@spartner.nl or via phone +31 (0)10 744 9312 or visit the dedicated page with more information: https://laravel-excel.com/commercial-support Also note that we are a wrapper package around PhpSpreadsheet and thus we cannot answer all questions that are about PhpSpreadsheet functionality. You could ask your question on Stackoverflow or a Laravel-related forum: - https://stackoverflow.com/questions/tagged/laravel-excel - https://laracasts.com/discuss - https://laravel.io/forum More information about support you can find in the documentation: https://docs.laravel-excel.com/3.1/getting-started/support.html Thanks! ================================================ FILE: .github/issuecomplete.yml ================================================ # The name of the label to apply when an issue does not have all tasks checked labelName: more information needed # The text of the comment to add to the issue in addition to the label commentText: > Thanks for submitting the ticket. Unfortunately the information you provided is incomplete. We need to know which version you use and how to reproduce it. Please include code examples. Before we can pick it up, please check (https://github.com/Maatwebsite/Laravel-Excel/blob/3.1/.github/ISSUE_TEMPLATE.md) and add the missing information. To make processing of this ticket a lot easier, please make sure to check (https://laravel-excel.maatwebsite.nl/3.1/getting-started/contributing.html) and double-check if you have filled in the issue template correctly. This will allow us to pick up your ticket more efficiently. Issues that don't have an issue template filled in, will be closed. # Whether or not to ensure all checkboxes are checked checkCheckboxes: false # Keywords to look for in the body of the issue keywords: - Versions - Description ================================================ FILE: .github/stale.yml ================================================ # Number of days of inactivity before an issue becomes stale daysUntilStale: 60 # Number of days of inactivity before a stale issue is closed daysUntilClose: 1 # Issues with these labels will never be considered stale exemptLabels: - pinned - proposal # Label to use when marking an issue as stale staleLabel: stale # Comment to post when marking an issue as stale. Set to `false` to disable markComment: false # Comment to post when closing a stale issue. Set to `false` to disable closeComment: > This bug report has been automatically closed because it has not had recent activity. If this is still an active bug, please comment to reopen. Thank you for your contributions. ================================================ FILE: .github/workflows/run-tests.yml ================================================ name: Run tests on: push: pull_request: schedule: - cron: '0 0 * * *' jobs: tests: if: "!contains(github.event.head_commit.message, 'skip ci')" runs-on: ${{ matrix.os }} services: mysql: image: mysql:5.7 env: MYSQL_DATABASE: laravel_excel MYSQL_ALLOW_EMPTY_PASSWORD: yes ports: - 3306 options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3 strategy: matrix: php: [7.4, 8.0, 8.1, 8.2, 8.3, 8.4, 8.5] laravel: [13, 12, 11, 10, 9, 8, 7, 6, 5.8] dependency-version: [prefer-stable] os: [ubuntu-latest] include: - laravel: 13 scout: 11.* testbench: 11.* - laravel: 12 scout: 10.* testbench: 10.* - laravel: 11 scout: 10.* testbench: 9.* - laravel: 10 scout: 10.* testbench: 8.* - laravel: 9 scout: 9.* testbench: 7.* - laravel: 8 scout: 8.* testbench: 6.* - laravel: 7 scout: 7.2.* testbench: 5.* - laravel: 6 scout: 7.1.* testbench: 4.* - laravel: 5.8 scout: 7.1.* testbench: 3.8.* exclude: - laravel: 13 php: 8.2 - laravel: 13 php: 8.1 - laravel: 13 php: 8.0 - laravel: 13 php: 7.4 - laravel: 12 php: 8.1 - laravel: 12 php: 8.0 - laravel: 12 php: 7.4 - laravel: 11 php: 8.5 - laravel: 11 php: 8.1 - laravel: 11 php: 8.0 - laravel: 11 php: 7.4 - laravel: 10 php: 8.0 - laravel: 10 php: 7.4 - laravel: 10 php: 8.4 - laravel: 10 php: 8.5 - laravel: 9 php: 7.4 - laravel: 9 php: 8.4 - laravel: 9 php: 8.5 - laravel: 8 php: 8.4 - laravel: 8 php: 8.5 - laravel: 7 php: 8.0 - laravel: 7 php: 8.1 - laravel: 7 php: 8.2 - laravel: 7 php: 8.3 - laravel: 7 php: 8.4 - laravel: 7 php: 8.5 - laravel: 6 php: 8.0 - laravel: 6 php: 8.1 - laravel: 6 php: 8.2 - laravel: 6 php: 8.3 - laravel: 6 php: 8.4 - laravel: 6 php: 8.5 - laravel: 5.8 php: 8.0 - laravel: 5.8 php: 8.1 - laravel: 5.8 php: 8.2 - laravel: 5.8 php: 8.3 - laravel: 5.8 php: 8.4 - laravel: 5.8 php: 8.5 name: PHP${{ matrix.php }} - L${{ matrix.laravel }} steps: - name: Checkout code uses: actions/checkout@v4 - name: Cache dependencies uses: actions/cache@v4 with: path: ~/.composer/cache/files key: dependency-cache-laravel-${{ matrix.laravel }}-php-${{ matrix.php }}-composer-${{ hashFiles('composer.json') }} - name: Setup PHP uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php }} extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, mysql, pdo_mysql, bcmath, intl, gd, exif, iconv coverage: none - name: Install dependencies run: | composer require "laravel/framework:${{ matrix.laravel }}.*" "orchestra/testbench:${{ matrix.testbench }}" "laravel/scout:${{ matrix.scout }}" --no-interaction --no-update composer update --${{ matrix.dependency-version }} --no-interaction - name: Install legacy factories run: | composer require "laravel/legacy-factories" -W --no-interaction if: "matrix.laravel >= 8" - name: Execute tests run: vendor/bin/phpunit --testdox --configuration phpunit.xml.dist env: DB_PORT: ${{ job.services.mysql.ports[3306] }} ================================================ FILE: .gitignore ================================================ /vendor composer.phar composer.lock .DS_Store .idea phpunit.xml .phpunit.result.cache ================================================ FILE: .phpunit.cache/test-results ================================================ {"version":1,"defects":{"Maatwebsite\\Excel\\Tests\\QueuedImportTest::can_queue_an_import_with_batch_cache":5,"Maatwebsite\\Excel\\Tests\\QueuedExportTest::can_queue_an_export_with_batch_cache":8,"Maatwebsite\\Excel\\Tests\\QueuedQueryExportTest::can_queue_an_export_with_batch_cache":8,"Maatwebsite\\Excel\\Tests\\Concerns\\FromQueryTest::can_export_from_query":8,"Maatwebsite\\Excel\\Tests\\Concerns\\FromQueryTest::can_export_from_relation_query_queued":8,"Maatwebsite\\Excel\\Tests\\Concerns\\FromQueryTest::can_export_from_query_with_eager_loads":8,"Maatwebsite\\Excel\\Tests\\Concerns\\FromQueryTest::can_export_from_query_with_eager_loads_and_queued":8,"Maatwebsite\\Excel\\Tests\\Concerns\\FromQueryTest::can_export_from_query_builder_without_using_eloquent":8,"Maatwebsite\\Excel\\Tests\\Concerns\\FromQueryTest::can_export_from_query_builder_without_using_eloquent_and_queued":8,"Maatwebsite\\Excel\\Tests\\Concerns\\FromQueryTest::can_export_from_query_builder_with_nested_arrays":8,"Maatwebsite\\Excel\\Tests\\Concerns\\FromQueryTest::can_export_from_query_builder_with_nested_arrays_queued":8,"Maatwebsite\\Excel\\Tests\\Concerns\\FromQueryTest::can_export_from_query_with_batch_caching":8,"Maatwebsite\\Excel\\Tests\\Concerns\\FromQueryTest::can_export_from_query_with_prepare_rows":8,"Maatwebsite\\Excel\\Tests\\Concerns\\FromQueryTest::can_export_from_scout":8,"Maatwebsite\\Excel\\Tests\\Concerns\\FromViewTest::can_export_from_view":8,"Maatwebsite\\Excel\\Tests\\Concerns\\FromViewTest::can_export_multiple_sheets_from_view":8,"Maatwebsite\\Excel\\Tests\\Concerns\\ImportableTest::can_import_a_simple_xlsx_file":8,"Maatwebsite\\Excel\\Tests\\Concerns\\ImportableTest::can_import_a_simple_xlsx_file_from_uploaded_file":8,"Maatwebsite\\Excel\\Tests\\Concerns\\ImportableTest::can_import_a_simple_csv_file_with_html_tags_inside":8,"Maatwebsite\\Excel\\Tests\\Concerns\\ImportableTest::can_import_a_simple_xlsx_file_with_ignore_empty_set_to_true":8,"Maatwebsite\\Excel\\Tests\\Concerns\\ImportableTest::can_import_a_simple_xlsx_file_with_ignore_empty_set_to_false":8,"Maatwebsite\\Excel\\Tests\\Concerns\\OnEachRowTest::can_import_each_row_individually":8,"Maatwebsite\\Excel\\Tests\\Concerns\\OnEachRowTest::it_respects_the_end_column":8,"Maatwebsite\\Excel\\Tests\\Concerns\\RegistersEventListenersTest::events_get_called_when_importing":8,"Maatwebsite\\Excel\\Tests\\Concerns\\RemembersChunkOffsetTest::can_access_chunk_offset_on_import_to_array_in_chunks":8,"Maatwebsite\\Excel\\Tests\\Concerns\\RemembersRowNumberTest::can_access_row_number_on_import_to_model":8,"Maatwebsite\\Excel\\Tests\\Concerns\\RemembersRowNumberTest::can_access_row_number_on_import_to_array_in_chunks":8,"Maatwebsite\\Excel\\Tests\\Concerns\\RemembersRowNumberTest::can_access_row_number_on_import_to_array_in_chunks_with_batch_inserts":8,"ShouldQueueWithoutChainTest::can_import_to_model_in_chunks":8,"ShouldQueueWithoutChainTest::can_import_to_model_without_job_chaining":8,"ShouldQueueWithoutChainTest::a_queue_name_can_be_specified_when_importing":8,"ShouldQueueWithoutChainTest::the_cleanup_only_runs_when_all_jobs_are_done":8,"Maatwebsite\\Excel\\Tests\\Concerns\\SkipsEmptyRowsTest::skips_empty_rows_when_importing_to_collection":8,"Maatwebsite\\Excel\\Tests\\Concerns\\SkipsEmptyRowsTest::skips_empty_rows_when_importing_on_each_row":8,"Maatwebsite\\Excel\\Tests\\Concerns\\SkipsEmptyRowsTest::skips_empty_rows_when_importing_to_model":8,"Maatwebsite\\Excel\\Tests\\Concerns\\SkipsEmptyRowsTest::custom_skips_rows_when_importing_to_collection":8,"Maatwebsite\\Excel\\Tests\\Concerns\\SkipsEmptyRowsTest::custom_skips_rows_when_importing_to_model":8,"Maatwebsite\\Excel\\Tests\\Concerns\\SkipsEmptyRowsTest::custom_skips_rows_when_using_oneachrow":8,"Maatwebsite\\Excel\\Tests\\Concerns\\SkipsOnErrorTest::can_skip_on_error":7,"Maatwebsite\\Excel\\Tests\\Concerns\\SkipsOnErrorTest::can_skip_errors_and_collect_all_errors_at_the_end":7,"Maatwebsite\\Excel\\Tests\\Concerns\\SkipsOnFailureTest::can_skip_on_error":8,"Maatwebsite\\Excel\\Tests\\Concerns\\SkipsOnFailureTest::skips_only_failed_rows_in_batch":8,"Maatwebsite\\Excel\\Tests\\Concerns\\SkipsOnFailureTest::can_skip_failures_and_collect_all_failures_at_the_end":8,"Maatwebsite\\Excel\\Tests\\Concerns\\SkipsOnFailureTest::can_validate_using_oneachrow_and_skipsonfailure":7,"Maatwebsite\\Excel\\Tests\\Concerns\\SkipsOnFailureTest::can_validate_using_tocollection_and_skipsonfailure":7,"Maatwebsite\\Excel\\Tests\\Concerns\\ToArrayTest::can_import_to_array":8,"Maatwebsite\\Excel\\Tests\\Concerns\\ToArrayTest::can_import_multiple_sheets_to_array":8,"Maatwebsite\\Excel\\Tests\\Concerns\\ToCollectionTest::can_import_to_collection":8,"Maatwebsite\\Excel\\Tests\\Concerns\\ToCollectionTest::can_import_multiple_sheets_to_collection":8,"Maatwebsite\\Excel\\Tests\\Concerns\\ToModelTest::can_import_each_row_to_model":8,"Maatwebsite\\Excel\\Tests\\Concerns\\ToModelTest::has_timestamps_when_imported_single_model":8,"Maatwebsite\\Excel\\Tests\\Concerns\\ToModelTest::can_import_multiple_models_in_single_to_model":8,"Maatwebsite\\Excel\\Tests\\Concerns\\ToModelTest::can_import_multiple_different_types_of_models_in_single_to_model":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithBatchInsertsTest::can_import_to_model_in_batches":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithBatchInsertsTest::can_import_to_model_in_batches_bigger_file":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithBatchInsertsTest::can_import_multiple_different_types_of_models_in_single_to_model":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithBatchInsertsTest::has_timestamps_when_imported_in_batches":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithCalculatedFormulasTest::by_default_does_not_calculate_formulas":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithCalculatedFormulasTest::can_import_to_array_with_calculated_formulas":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithCalculatedFormulasTest::can_import_to_model_with_calculated_formulas":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithCalculatedFormulasTest::can_import_to_array_with_calculated_formulas_and_multi_sheet_references":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithCalculatedFormulasTest::can_import_to_array_with_calculated_formulas_and_skips_empty":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithCalculatedFormulasTest::can_import_to_model_with_calculated_formulas_and_skips_empty":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithChunkReadingTest::can_import_to_model_in_chunks_un":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithChunkReadingTest::can_import_to_model_in_chunks_and_insert_in_batches":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithChunkReadingTest::can_import_to_model_in_chunks_and_insert_in_batches_with_heading_row":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithChunkReadingTest::can_import_csv_in_chunks_and_insert_in_batches":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithChunkReadingTest::can_import_to_model_in_chunks_and_insert_in_batches_with_multiple_sheets":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithChunkReadingTest::can_import_to_array_in_chunks":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithChunkReadingTest::can_import_to_model_in_chunks_and_insert_in_batches_with_multiple_sheets_objects_by_index":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithChunkReadingTest::can_import_to_model_in_chunks_and_insert_in_batches_with_multiple_sheets_objects_by_name":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithChunkReadingTest::can_catch_job_failed_in_chunks":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithChunkReadingTest::can_import_to_array_and_format_in_chunks":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithChunkReadingTest::can_import_to_array_in_chunks_without_formatting":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithColumnLimitTest::can_import_to_array_with_column_limit":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithColumnLimitTest::can_import_to_array_with_column_limit_and_skips_empty_rows":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithConditionalSheetsTest::can_select_which_sheets_will_be_imported":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithCustomCsvSettingsTest::can_read_csv_import_with_custom_settings":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithCustomCsvSettingsTest::cannot_read_with_wrong_delimiter":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithCustomQuerySizeTest::can_export_with_custom_count":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithCustomValueBinderTest::can_set_a_value_binder_on_import":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithEventsTest::import_events_get_called":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithEventsTest::import_chunked_events_get_called":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithFormatDataTest::by_default_import_to_array":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithFormatDataTest::can_import_to_array_with_format_data":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithFormatDataTest::can_import_to_array_with_format_data_and_skips_empty_rows":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithFormatDataTest::by_default_import_to_collection":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithFormatDataTest::can_import_to_collection_with_format_data":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithFormatDataTest::by_default_import_to_model":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithFormatDataTest::can_import_to_model_with_format_data":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithGroupedHeadingRowTest::can_import_to_array_with_grouped_headers":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithGroupedHeadingRowTest::can_import_oneachrow_with_grouped_headers":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithGroupedHeadingRowTest::can_import_to_collection_with_grouped_headers":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithGroupedHeadingRowTest::can_import_each_row_to_model_with_grouped_headers":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithHeadingRowTest::can_import_each_row_to_model_with_heading_row":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithHeadingRowTest::can_import_each_row_to_model_with_different_heading_row":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithHeadingRowTest::can_import_to_array_with_heading_row":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithHeadingRowTest::can_import_empty_rows_with_header":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithHeadingRowTest::can_import_empty_models_with_header":7,"Maatwebsite\\Excel\\Tests\\Concerns\\WithHeadingRowTest::can_cast_empty_headers_to_indexed_int":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithLimitTest::can_import_a_limited_section_of_rows_to_model_with_different_start_row":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithLimitTest::can_import_to_array_with_limit":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithLimitTest::can_set_limit_bigger_than_row_size":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithMappedCellsTest::can_import_with_references_to_cells":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithMappedCellsTest::can_import_with_nested_references_to_cells":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithMappedCellsTest::can_import_with_references_to_cells_to_model":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithMultipleSheetsTest::can_export_with_multiple_sheets_using_collections":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithMultipleSheetsTest::can_export_multiple_sheets_from_view":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithMultipleSheetsTest::unknown_sheet_index_will_throw_sheet_not_found_exception":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithMultipleSheetsTest::unknown_sheet_name_will_throw_sheet_not_found_exception":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithMultipleSheetsTest::unknown_sheet_name_can_be_ignored":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithMultipleSheetsTest::unknown_sheet_indices_can_be_ignored_per_name":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithMultipleSheetsTest::unknown_sheet_indices_can_be_ignored":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithMultipleSheetsTest::unknown_sheet_indices_can_be_ignored_per_sheet":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithMultipleSheetsTest::can_import_multiple_sheets":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithMultipleSheetsTest::can_import_multiple_sheets_by_sheet_name":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithMultipleSheetsTest::can_import_multiple_sheets_by_sheet_index_and_name":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithMultipleSheetsTest::can_import_multiple_sheets_by_sheet_name_and_index":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithStartRowTest::can_import_each_row_to_model_with_different_start_row":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithStartRowTest::can_import_to_array_with_start_row":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithUpsertsTest::can_upsert_models_in_batches":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithUpsertsTest::can_upsert_models_in_rows":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithUpsertsTest::can_upsert_models_in_batches_with_defined_upsert_columns":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithUpsertsTest::can_upsert_models_in_rows_with_defined_upsert_columns":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithValidationTest::can_validate_rows":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithValidationTest::can_validate_rows_with_closure_validation_rules":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithValidationTest::can_validate_rows_with_custom_validation_rule_objects":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithValidationTest::can_validate_rows_with_conditionality":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithValidationTest::can_validate_rows_with_unless_conditionality":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithValidationTest::can_validate_rows_with_combined_rules_with_colons":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithValidationTest::can_validate_with_custom_attributes":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithValidationTest::can_validate_with_custom_message":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithValidationTest::can_validate_rows_with_headings":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithValidationTest::can_validate_rows_with_grouped_headings":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithValidationTest::can_validate_rows_in_batches":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithValidationTest::can_validate_using_oneachrow":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithValidationTest::can_validate_using_collection":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithValidationTest::can_validate_using_array":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithValidationTest::can_configure_validator":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithValidationTest::can_prepare_using_toarray":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithValidationTest::can_prepare_using_tocollection":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithValidationTest::can_prepare_using_tomodel":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithValidationTest::can_prepare_using_oneachrow":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithValidationTest::can_prepare_using_skipsemptyrows":8,"Maatwebsite\\Excel\\Tests\\ExcelTest::can_import_a_simple_xlsx_file":8,"Maatwebsite\\Excel\\Tests\\ExcelTest::can_import_a_tsv_file":8,"Maatwebsite\\Excel\\Tests\\ExcelTest::can_chain_imports":8,"Maatwebsite\\Excel\\Tests\\ExcelTest::can_import_a_simple_xlsx_file_from_uploaded_file":8,"Maatwebsite\\Excel\\Tests\\ExcelTest::can_import_a_simple_xlsx_file_from_real_path":8,"Maatwebsite\\Excel\\Tests\\ExcelTest::can_import_without_extension_with_explicit_reader_type":8,"Maatwebsite\\Excel\\Tests\\Mixins\\StoreCollectionTest::can_store_a_model_collection_with_headings_as_excel":8,"Maatwebsite\\Excel\\Tests\\QueuedImportTest::cannot_queue_import_that_does_not_implement_should_queue":8,"Maatwebsite\\Excel\\Tests\\QueuedImportTest::can_queue_an_import":7,"Maatwebsite\\Excel\\Tests\\QueuedImportTest::can_queue_an_import_with_batch_cache_and_file_store":8,"Maatwebsite\\Excel\\Tests\\QueuedImportTest::can_queue_import_with_remote_temp_disk":7,"Maatwebsite\\Excel\\Tests\\QueuedImportTest::can_keep_extension_for_temp_file_on_remote_disk":8,"Maatwebsite\\Excel\\Tests\\QueuedImportTest::can_queue_import_with_remote_temp_disk_and_prefix":7,"Maatwebsite\\Excel\\Tests\\QueuedImportTest::can_automatically_delete_temp_file_on_failure_when_using_remote_disk":8,"Maatwebsite\\Excel\\Tests\\QueuedImportTest::cannot_automatically_delete_temp_file_on_failure_when_using_local_disk":8,"Maatwebsite\\Excel\\Tests\\QueuedImportTest::can_force_remote_download_and_deletion_for_each_chunk_on_queue":8,"Maatwebsite\\Excel\\Tests\\QueuedImportTest::can_define_middleware_method_on_queued_import":8,"Maatwebsite\\Excel\\Tests\\QueuedImportTest::can_define_retry_until_method_on_queued_import":8,"Maatwebsite\\Excel\\Tests\\QueuedImportTest::can_define_max_exceptions_property_on_queued_import":8,"Maatwebsite\\Excel\\Tests\\QueuedQueryExportTest::can_queue_an_export":8,"Maatwebsite\\Excel\\Tests\\QueuedQueryExportTest::can_queue_an_export_with_batch_cache_and_file_store":8,"Maatwebsite\\Excel\\Tests\\QueuedQueryExportTest::can_queue_an_export_with_mapping":8,"Maatwebsite\\Excel\\Tests\\QueuedQueryExportTest::can_queue_scout_export":8,"Maatwebsite\\Excel\\Tests\\QueuedViewExportTest::can_queue_an_export":8,"Maatwebsite\\Excel\\Tests\\QueuedViewExportTest::can_export_multiple_sheets_from_view":8,"Maatwebsite\\Excel\\Tests\\Concerns\\FromCollectionTest::can_export_from_lazy_collection":8,"Maatwebsite\\Excel\\Tests\\CellTest::can_get_cell_value":7,"Maatwebsite\\Excel\\Tests\\Concerns\\WithEventsTest::export_chunked_events_get_called":8,"Maatwebsite\\Excel\\Tests\\Concerns\\FromArrayTest::can_export_from_array":8,"Maatwebsite\\Excel\\Tests\\Concerns\\FromCollectionTest::can_export_from_collection":8,"Maatwebsite\\Excel\\Tests\\Concerns\\FromCollectionTest::can_export_with_multiple_sheets_from_collection":8,"Maatwebsite\\Excel\\Tests\\Concerns\\FromCollectionTest::can_export_from_lazy_collection_with_queue":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithColumnFormattingTest::can_export_with_column_formatting":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithColumnWidthsTest::can_set_column_width":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithCustomCsvSettingsTest::can_store_csv_export_with_custom_settings":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithCustomCsvSettingsTest::can_store_csv_export_with_custom_encoding":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithCustomStartCellTest::can_store_collection_with_custom_start_cell":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithCustomValueBinderTest::can_set_a_value_binder_on_export":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithDefaultStylesTest::can_configure_default_styles":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithHeadingsTest::can_export_from_collection_with_heading_row":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithHeadingsTest::can_export_from_collection_with_multiple_heading_rows":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithHeadingsTest::can_export_from_collection_with_heading_row_with_custom_start_cell":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithMappingTest::can_export_with_heading":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithMappingTest::can_return_multiple_rows_in_map":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithMappingTest::json_array_columns_shouldnt_be_detected_as_multiple_rows":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithStrictNullComparisonTest::exported_zero_values_are_not_null_when_exporting_with_strict_null_comparison":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithStrictNullComparisonTest::exported_zero_values_are_null_when_not_exporting_with_strict_null_comparison":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithStrictNullComparisonTest::exports_trailing_empty_cells":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithStrictNullComparisonTest::exports_trailing_empty_cells_by_setting_config_strict_null_comparison":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithStylesTest::can_configure_styles":8,"Maatwebsite\\Excel\\Tests\\ExcelTest::can_store_csv_export_with_custom_settings":8,"Maatwebsite\\Excel\\Tests\\Mixins\\DownloadCollectionTest::can_download_a_collection_as_excel":8,"Maatwebsite\\Excel\\Tests\\Mixins\\DownloadCollectionTest::can_download_a_collection_with_headers_as_excel":8,"Maatwebsite\\Excel\\Tests\\Mixins\\DownloadCollectionTest::can_download_collection_with_headers_with_hidden_eloquent_attributes":8,"Maatwebsite\\Excel\\Tests\\Mixins\\DownloadCollectionTest::can_download_collection_with_headers_when_making_attributes_visible":8,"Maatwebsite\\Excel\\Tests\\Mixins\\DownloadCollectionTest::can_set_custom_response_headers":8,"Maatwebsite\\Excel\\Tests\\Mixins\\DownloadQueryMacroTest::can_download_a_query_as_excel":8,"Maatwebsite\\Excel\\Tests\\Mixins\\DownloadQueryMacroTest::can_download_a_collection_with_headers_as_excel":8,"Maatwebsite\\Excel\\Tests\\Mixins\\ImportAsMacroTest::can_import_directly_into_a_model_with_mapping":8,"Maatwebsite\\Excel\\Tests\\Mixins\\ImportMacroTest::can_import_directly_into_a_model":8,"Maatwebsite\\Excel\\Tests\\Mixins\\StoreCollectionTest::can_store_a_collection_as_excel":8,"Maatwebsite\\Excel\\Tests\\Mixins\\StoreCollectionTest::can_store_a_collection_as_excel_on_non_default_disk":8,"Maatwebsite\\Excel\\Tests\\Mixins\\StoreCollectionTest::can_store_a_collection_with_headings_as_excel":8,"Maatwebsite\\Excel\\Tests\\Mixins\\StoreQueryMacroTest::can_download_a_query_as_excel":8,"Maatwebsite\\Excel\\Tests\\Mixins\\StoreQueryMacroTest::can_download_a_query_as_excel_on_different_disk":8,"Maatwebsite\\Excel\\Tests\\Mixins\\StoreQueryMacroTest::can_store_a_query_with_headers_as_excel":8,"Maatwebsite\\Excel\\Tests\\QueuedExportTest::can_queue_an_export":8,"Maatwebsite\\Excel\\Tests\\QueuedExportTest::can_queue_an_export_and_store_on_different_disk":8,"Maatwebsite\\Excel\\Tests\\QueuedExportTest::can_queue_export_with_remote_temp_disk":8,"Maatwebsite\\Excel\\Tests\\QueuedExportTest::can_queue_export_with_remote_temp_disk_and_prefix":8,"Maatwebsite\\Excel\\Tests\\QueuedExportTest::can_implicitly_queue_an_export":8,"Maatwebsite\\Excel\\Tests\\QueuedExportTest::can_queue_export_with_mapping_on_eloquent_models":8,"Maatwebsite\\Excel\\Tests\\QueuedExportTest::can_set_locale_on_queue_export_job":8,"Maatwebsite\\Excel\\Tests\\QueuedExportTest::can_queue_export_not_flushing_the_cache":8,"Maatwebsite\\Excel\\Tests\\Concerns\\ToModelTest::can_import_models_with_belongs_to_relations":7,"Maatwebsite\\Excel\\Tests\\Concerns\\ToModelTest::can_import_models_with_belongs_to_many_relations":7,"Maatwebsite\\Excel\\Tests\\Cache\\BatchCacheTest::it_writes_to_cache_with_a_dateinterval_ttl":7,"Maatwebsite\\Excel\\Tests\\HeadingRowImportTest::can_import_only_heading_row":5,"Maatwebsite\\Excel\\Tests\\HeadingRowImportTest::can_import_only_heading_row_with_custom_heading_row_formatter":5,"Maatwebsite\\Excel\\Tests\\HeadingRowImportTest::can_import_only_heading_row_with_custom_heading_row_formatter_with_key":5,"Maatwebsite\\Excel\\Tests\\HeadingRowImportTest::can_import_only_heading_row_with_custom_row_number":5,"Maatwebsite\\Excel\\Tests\\HeadingRowImportTest::can_import_only_heading_row_for_multiple_sheets":5,"Maatwebsite\\Excel\\Tests\\HeadingRowImportTest::can_import_only_heading_row_for_multiple_sheets_with_key":5,"Maatwebsite\\Excel\\Tests\\HeadingRowImportTest::can_import_only_heading_row_for_multiple_sheets_with_custom_row_number":5,"Maatwebsite\\Excel\\Tests\\HeadingRowImportTest::can_import_heading_row_with_custom_formatter_defined_in_config":5,"Maatwebsite\\Excel\\Tests\\Concerns\\ImportableTest::test_cannot_import_a_non_existing_xlsx_file":8,"Maatwebsite\\Excel\\Tests\\Concerns\\FromQueryTest::test_can_export_from_query":8,"Maatwebsite\\Excel\\Tests\\Concerns\\FromQueryTest::test_can_export_from_relation_query_queued":8,"Maatwebsite\\Excel\\Tests\\Concerns\\FromQueryTest::test_can_export_from_query_with_eager_loads":8,"Maatwebsite\\Excel\\Tests\\Concerns\\FromQueryTest::test_can_export_from_query_with_eager_loads_and_queued":8,"Maatwebsite\\Excel\\Tests\\Concerns\\FromQueryTest::test_can_export_from_query_builder_without_using_eloquent":8,"Maatwebsite\\Excel\\Tests\\Concerns\\FromQueryTest::test_can_export_from_query_builder_without_using_eloquent_and_queued":8,"Maatwebsite\\Excel\\Tests\\Concerns\\FromQueryTest::test_can_export_from_query_builder_with_nested_arrays":8,"Maatwebsite\\Excel\\Tests\\Concerns\\FromQueryTest::test_can_export_from_query_builder_with_nested_arrays_queued":8,"Maatwebsite\\Excel\\Tests\\Concerns\\FromQueryTest::test_can_export_from_query_with_batch_caching":8,"Maatwebsite\\Excel\\Tests\\Concerns\\FromQueryTest::test_can_export_from_query_with_prepare_rows":8,"Maatwebsite\\Excel\\Tests\\Concerns\\FromQueryTest::test_can_export_from_scout":8,"Maatwebsite\\Excel\\Tests\\Concerns\\FromViewTest::test_can_export_from_view":8,"Maatwebsite\\Excel\\Tests\\Concerns\\FromViewTest::test_can_export_multiple_sheets_from_view":8,"Maatwebsite\\Excel\\Tests\\Concerns\\ImportableTest::test_can_import_a_simple_xlsx_file":8,"Maatwebsite\\Excel\\Tests\\Concerns\\ImportableTest::test_can_import_a_simple_xlsx_file_from_uploaded_file":8,"Maatwebsite\\Excel\\Tests\\Concerns\\ImportableTest::test_can_import_a_simple_csv_file_with_html_tags_inside":8,"Maatwebsite\\Excel\\Tests\\Concerns\\ImportableTest::test_can_import_a_simple_xlsx_file_with_ignore_empty_set_to_true":8,"Maatwebsite\\Excel\\Tests\\Concerns\\ImportableTest::test_can_import_a_simple_xlsx_file_with_ignore_empty_set_to_false":8,"Maatwebsite\\Excel\\Tests\\Concerns\\OnEachRowTest::test_can_import_each_row_individually":8,"Maatwebsite\\Excel\\Tests\\Concerns\\OnEachRowTest::test_it_respects_the_end_column":8,"Maatwebsite\\Excel\\Tests\\Concerns\\RegistersEventListenersTest::test_events_get_called_when_importing":8,"Maatwebsite\\Excel\\Tests\\Concerns\\RemembersChunkOffsetTest::test_can_access_chunk_offset_on_import_to_array_in_chunks":8,"Maatwebsite\\Excel\\Tests\\Concerns\\RemembersRowNumberTest::test_can_access_row_number_on_import_to_model":8,"Maatwebsite\\Excel\\Tests\\Concerns\\RemembersRowNumberTest::test_can_access_row_number_on_import_to_array_in_chunks":8,"Maatwebsite\\Excel\\Tests\\Concerns\\RemembersRowNumberTest::test_can_access_row_number_on_import_to_array_in_chunks_with_batch_inserts":8,"ShouldQueueWithoutChainTest::test_can_import_to_model_in_chunks":8,"ShouldQueueWithoutChainTest::test_can_import_to_model_without_job_chaining":8,"ShouldQueueWithoutChainTest::test_a_queue_name_can_be_specified_when_importing":8,"ShouldQueueWithoutChainTest::test_the_cleanup_only_runs_when_all_jobs_are_done":8,"Maatwebsite\\Excel\\Tests\\Concerns\\SkipsEmptyRowsTest::test_skips_empty_rows_when_importing_to_collection":8,"Maatwebsite\\Excel\\Tests\\Concerns\\SkipsEmptyRowsTest::test_skips_empty_rows_when_importing_on_each_row":8,"Maatwebsite\\Excel\\Tests\\Concerns\\SkipsEmptyRowsTest::test_skips_empty_rows_when_importing_to_model":8,"Maatwebsite\\Excel\\Tests\\Concerns\\SkipsEmptyRowsTest::test_custom_skips_rows_when_importing_to_collection":8,"Maatwebsite\\Excel\\Tests\\Concerns\\SkipsEmptyRowsTest::test_custom_skips_rows_when_importing_to_model":8,"Maatwebsite\\Excel\\Tests\\Concerns\\SkipsEmptyRowsTest::test_custom_skips_rows_when_using_oneachrow":8,"Maatwebsite\\Excel\\Tests\\Concerns\\SkipsOnErrorTest::test_can_skip_on_error":8,"Maatwebsite\\Excel\\Tests\\Concerns\\SkipsOnErrorTest::test_can_skip_errors_and_collect_all_errors_at_the_end":8,"Maatwebsite\\Excel\\Tests\\Concerns\\SkipsOnFailureTest::test_can_skip_on_error":8,"Maatwebsite\\Excel\\Tests\\Concerns\\SkipsOnFailureTest::test_skips_only_failed_rows_in_batch":8,"Maatwebsite\\Excel\\Tests\\Concerns\\SkipsOnFailureTest::test_can_skip_failures_and_collect_all_failures_at_the_end":8,"Maatwebsite\\Excel\\Tests\\Concerns\\SkipsOnFailureTest::test_can_validate_using_oneachrow_and_skipsonfailure":8,"Maatwebsite\\Excel\\Tests\\Concerns\\SkipsOnFailureTest::test_can_validate_using_tocollection_and_skipsonfailure":8,"Maatwebsite\\Excel\\Tests\\Concerns\\ToArrayTest::test_can_import_to_array":8,"Maatwebsite\\Excel\\Tests\\Concerns\\ToArrayTest::test_can_import_multiple_sheets_to_array":8,"Maatwebsite\\Excel\\Tests\\Concerns\\ToCollectionTest::test_can_import_to_collection":8,"Maatwebsite\\Excel\\Tests\\Concerns\\ToCollectionTest::test_can_import_multiple_sheets_to_collection":8,"Maatwebsite\\Excel\\Tests\\Concerns\\ToModelTest::test_can_import_each_row_to_model":8,"Maatwebsite\\Excel\\Tests\\Concerns\\ToModelTest::test_has_timestamps_when_imported_single_model":8,"Maatwebsite\\Excel\\Tests\\Concerns\\ToModelTest::test_can_import_multiple_models_in_single_to_model":8,"Maatwebsite\\Excel\\Tests\\Concerns\\ToModelTest::test_can_import_multiple_different_types_of_models_in_single_to_model":8,"Maatwebsite\\Excel\\Tests\\Concerns\\ToModelTest::test_can_import_models_with_belongs_to_relations":8,"Maatwebsite\\Excel\\Tests\\Concerns\\ToModelTest::test_can_import_models_with_belongs_to_many_relations":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithBatchInsertsTest::test_can_import_to_model_in_batches":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithBatchInsertsTest::test_can_import_to_model_in_batches_bigger_file":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithBatchInsertsTest::test_can_import_multiple_different_types_of_models_in_single_to_model":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithBatchInsertsTest::test_has_timestamps_when_imported_in_batches":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithCalculatedFormulasTest::test_by_default_does_not_calculate_formulas":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithCalculatedFormulasTest::test_can_import_to_array_with_calculated_formulas":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithCalculatedFormulasTest::test_can_import_to_model_with_calculated_formulas":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithCalculatedFormulasTest::test_can_import_to_array_with_calculated_formulas_and_multi_sheet_references":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithCalculatedFormulasTest::test_can_import_to_array_with_calculated_formulas_and_skips_empty":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithCalculatedFormulasTest::test_can_import_to_model_with_calculated_formulas_and_skips_empty":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithChunkReadingTest::test_can_import_to_model_in_chunks_un":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithChunkReadingTest::test_can_import_to_model_in_chunks_and_insert_in_batches":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithChunkReadingTest::test_can_import_to_model_in_chunks_and_insert_in_batches_with_heading_row":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithChunkReadingTest::test_can_import_csv_in_chunks_and_insert_in_batches":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithChunkReadingTest::test_can_import_to_model_in_chunks_and_insert_in_batches_with_multiple_sheets":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithChunkReadingTest::test_can_import_to_array_in_chunks":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithChunkReadingTest::test_can_import_to_model_in_chunks_and_insert_in_batches_with_multiple_sheets_objects_by_index":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithChunkReadingTest::test_can_import_to_model_in_chunks_and_insert_in_batches_with_multiple_sheets_objects_by_name":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithChunkReadingTest::test_can_catch_job_failed_in_chunks":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithChunkReadingTest::test_can_import_to_array_and_format_in_chunks":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithChunkReadingTest::test_can_import_to_array_in_chunks_without_formatting":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithColumnLimitTest::test_can_import_to_array_with_column_limit":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithColumnLimitTest::test_can_import_to_array_with_column_limit_and_skips_empty_rows":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithConditionalSheetsTest::test_can_select_which_sheets_will_be_imported":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithCustomCsvSettingsTest::test_can_read_csv_import_with_custom_settings":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithCustomCsvSettingsTest::test_cannot_read_with_wrong_delimiter":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithCustomQuerySizeTest::test_can_export_with_custom_count":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithCustomValueBinderTest::test_can_set_a_value_binder_on_import":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithEventsTest::test_import_events_get_called":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithEventsTest::test_import_chunked_events_get_called":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithEventsTest::test_export_chunked_events_get_called":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithFormatDataTest::test_by_default_import_to_array":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithFormatDataTest::test_can_import_to_array_with_format_data":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithFormatDataTest::test_can_import_to_array_with_format_data_and_skips_empty_rows":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithFormatDataTest::test_by_default_import_to_collection":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithFormatDataTest::test_can_import_to_collection_with_format_data":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithFormatDataTest::test_by_default_import_to_model":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithFormatDataTest::test_can_import_to_model_with_format_data":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithGroupedHeadingRowTest::test_can_import_to_array_with_grouped_headers":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithGroupedHeadingRowTest::test_can_import_oneachrow_with_grouped_headers":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithGroupedHeadingRowTest::test_can_import_to_collection_with_grouped_headers":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithGroupedHeadingRowTest::test_can_import_each_row_to_model_with_grouped_headers":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithHeadingRowTest::test_can_import_each_row_to_model_with_heading_row":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithHeadingRowTest::test_can_import_each_row_to_model_with_different_heading_row":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithHeadingRowTest::test_can_import_to_array_with_heading_row":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithHeadingRowTest::test_can_import_empty_rows_with_header":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithHeadingRowTest::test_can_import_empty_models_with_header":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithHeadingRowTest::test_can_cast_empty_headers_to_indexed_int":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithLimitTest::test_can_import_a_limited_section_of_rows_to_model_with_different_start_row":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithLimitTest::test_can_import_to_array_with_limit":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithLimitTest::test_can_set_limit_bigger_than_row_size":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithMappedCellsTest::test_can_import_with_references_to_cells":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithMappedCellsTest::test_can_import_with_nested_references_to_cells":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithMappedCellsTest::test_can_import_with_references_to_cells_to_model":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithMultipleSheetsTest::test_can_export_with_multiple_sheets_using_collections":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithMultipleSheetsTest::test_can_export_multiple_sheets_from_view":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithMultipleSheetsTest::test_unknown_sheet_index_will_throw_sheet_not_found_exception":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithMultipleSheetsTest::test_unknown_sheet_name_will_throw_sheet_not_found_exception":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithMultipleSheetsTest::test_unknown_sheet_name_can_be_ignored":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithMultipleSheetsTest::test_unknown_sheet_indices_can_be_ignored_per_name":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithMultipleSheetsTest::test_unknown_sheet_indices_can_be_ignored":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithMultipleSheetsTest::test_unknown_sheet_indices_can_be_ignored_per_sheet":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithMultipleSheetsTest::test_can_import_multiple_sheets":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithMultipleSheetsTest::test_can_import_multiple_sheets_by_sheet_name":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithMultipleSheetsTest::test_can_import_multiple_sheets_by_sheet_index_and_name":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithMultipleSheetsTest::test_can_import_multiple_sheets_by_sheet_name_and_index":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithStartRowTest::test_can_import_each_row_to_model_with_different_start_row":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithStartRowTest::test_can_import_to_array_with_start_row":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithUpsertsTest::test_can_upsert_models_in_batches":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithUpsertsTest::test_can_upsert_models_in_rows":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithUpsertsTest::test_can_upsert_models_in_batches_with_defined_upsert_columns":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithUpsertsTest::test_can_upsert_models_in_rows_with_defined_upsert_columns":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithValidationTest::test_can_validate_rows":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithValidationTest::test_can_validate_rows_with_closure_validation_rules":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithValidationTest::test_can_validate_rows_with_custom_validation_rule_objects":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithValidationTest::test_can_validate_rows_with_conditionality":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithValidationTest::test_can_validate_rows_with_unless_conditionality":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithValidationTest::test_can_validate_rows_with_combined_rules_with_colons":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithValidationTest::test_can_validate_with_custom_attributes":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithValidationTest::test_can_validate_with_custom_attributes_pointing_to_another_attribute":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithValidationTest::test_can_validate_with_custom_message":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithValidationTest::test_can_validate_rows_with_headings":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithValidationTest::test_can_validate_rows_with_grouped_headings":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithValidationTest::test_can_validate_rows_in_batches":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithValidationTest::test_can_validate_using_oneachrow":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithValidationTest::test_can_validate_using_collection":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithValidationTest::test_can_validate_using_array":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithValidationTest::test_can_configure_validator":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithValidationTest::test_can_prepare_using_toarray":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithValidationTest::test_can_prepare_using_tocollection":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithValidationTest::test_can_prepare_using_tomodel":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithValidationTest::test_can_prepare_using_oneachrow":8,"Maatwebsite\\Excel\\Tests\\Concerns\\WithValidationTest::test_can_prepare_using_skipsemptyrows":8,"Maatwebsite\\Excel\\Tests\\ExcelTest::test_can_import_a_simple_xlsx_file":8,"Maatwebsite\\Excel\\Tests\\ExcelTest::test_can_import_a_tsv_file":8,"Maatwebsite\\Excel\\Tests\\ExcelTest::test_can_chain_imports":8,"Maatwebsite\\Excel\\Tests\\ExcelTest::test_can_import_a_simple_xlsx_file_from_uploaded_file":8,"Maatwebsite\\Excel\\Tests\\ExcelTest::test_can_import_a_simple_xlsx_file_from_real_path":8,"Maatwebsite\\Excel\\Tests\\ExcelTest::test_can_import_without_extension_with_explicit_reader_type":8,"Maatwebsite\\Excel\\Tests\\Mixins\\DownloadQueryMacroTest::test_can_download_a_query_as_excel":8,"Maatwebsite\\Excel\\Tests\\Mixins\\DownloadQueryMacroTest::test_can_download_a_collection_with_headers_as_excel":8,"Maatwebsite\\Excel\\Tests\\Mixins\\ImportAsMacroTest::test_can_import_directly_into_a_model_with_mapping":8,"Maatwebsite\\Excel\\Tests\\Mixins\\ImportMacroTest::test_can_import_directly_into_a_model":8,"Maatwebsite\\Excel\\Tests\\Mixins\\StoreCollectionTest::test_can_store_a_model_collection_with_headings_as_excel":8,"Maatwebsite\\Excel\\Tests\\Mixins\\StoreQueryMacroTest::test_can_download_a_query_as_excel":8,"Maatwebsite\\Excel\\Tests\\Mixins\\StoreQueryMacroTest::test_can_download_a_query_as_excel_on_different_disk":8,"Maatwebsite\\Excel\\Tests\\Mixins\\StoreQueryMacroTest::test_can_store_a_query_with_headers_as_excel":8,"Maatwebsite\\Excel\\Tests\\QueuedImportTest::test_cannot_queue_import_that_does_not_implement_should_queue":8,"Maatwebsite\\Excel\\Tests\\QueuedImportTest::test_can_queue_an_import":8,"Maatwebsite\\Excel\\Tests\\QueuedImportTest::test_can_queue_an_import_with_batch_cache_and_file_store":8,"Maatwebsite\\Excel\\Tests\\QueuedImportTest::test_can_queue_import_with_remote_temp_disk":8,"Maatwebsite\\Excel\\Tests\\QueuedImportTest::test_can_keep_extension_for_temp_file_on_remote_disk":8,"Maatwebsite\\Excel\\Tests\\QueuedImportTest::test_can_queue_import_with_remote_temp_disk_and_prefix":8,"Maatwebsite\\Excel\\Tests\\QueuedImportTest::test_can_automatically_delete_temp_file_on_failure_when_using_remote_disk":8,"Maatwebsite\\Excel\\Tests\\QueuedImportTest::test_cannot_automatically_delete_temp_file_on_failure_when_using_local_disk":8,"Maatwebsite\\Excel\\Tests\\QueuedImportTest::test_can_force_remote_download_and_deletion_for_each_chunk_on_queue":8,"Maatwebsite\\Excel\\Tests\\QueuedImportTest::test_can_define_middleware_method_on_queued_import":8,"Maatwebsite\\Excel\\Tests\\QueuedImportTest::test_can_define_retry_until_method_on_queued_import":8,"Maatwebsite\\Excel\\Tests\\QueuedImportTest::test_can_define_max_exceptions_property_on_queued_import":8,"Maatwebsite\\Excel\\Tests\\QueuedQueryExportTest::test_can_queue_an_export":8,"Maatwebsite\\Excel\\Tests\\QueuedQueryExportTest::test_can_queue_an_export_with_batch_cache_and_file_store":8,"Maatwebsite\\Excel\\Tests\\QueuedQueryExportTest::test_can_queue_an_export_with_mapping":8,"Maatwebsite\\Excel\\Tests\\QueuedQueryExportTest::test_can_queue_scout_export":8,"Maatwebsite\\Excel\\Tests\\QueuedViewExportTest::test_can_queue_an_export":8,"Maatwebsite\\Excel\\Tests\\QueuedViewExportTest::test_can_export_multiple_sheets_from_view":8,"Maatwebsite\\Excel\\Tests\\Validators\\RowValidatorTest::test_format_rule_with_string_input_matching_pattern":7,"Maatwebsite\\Excel\\Tests\\Validators\\RowValidatorTest::test_format_rule_with_required_without_all":7,"Maatwebsite\\Excel\\Tests\\Validators\\RowValidatorTest::test_format_rule_with_required_without":7},"times":{"Maatwebsite\\Excel\\Tests\\QueuedImportTest::can_queue_an_import_with_batch_cache":2.346,"Maatwebsite\\Excel\\Tests\\QueuedExportTest::can_queue_an_export":0.241,"Maatwebsite\\Excel\\Tests\\QueuedExportTest::can_queue_an_export_with_batch_cache":0.067,"Maatwebsite\\Excel\\Tests\\QueuedQueryExportTest::can_queue_an_export_with_batch_cache":0.374,"Maatwebsite\\Excel\\Tests\\Cache\\BatchCacheTest::will_get_multiple_from_memory_if_cells_hold_in_memory":0.02,"Maatwebsite\\Excel\\Tests\\Cache\\BatchCacheTest::will_get_multiple_from_cache_if_cells_are_persisted":0.002,"Maatwebsite\\Excel\\Tests\\Cache\\BatchCacheTest::will_get_multiple_from_cache_and_persisted":0.001,"Maatwebsite\\Excel\\Tests\\Cache\\BatchCacheTest::it_persists_to_cache_when_memory_limit_reached_on_setting_a_value":0,"Maatwebsite\\Excel\\Tests\\Cache\\BatchCacheTest::it_persists_to_cache_when_memory_limit_reached_on_setting_multiple_values":0,"Maatwebsite\\Excel\\Tests\\Cache\\BatchCacheTest::it_writes_to_cache_with_default_ttl#null (forever)":0.001,"Maatwebsite\\Excel\\Tests\\Cache\\BatchCacheTest::it_writes_to_cache_with_default_ttl#int value":0,"Maatwebsite\\Excel\\Tests\\Cache\\BatchCacheTest::it_writes_to_cache_with_default_ttl#callable":0,"Maatwebsite\\Excel\\Tests\\Cache\\BatchCacheTest::it_writes_to_cache_with_a_dateinterval_ttl":0,"Maatwebsite\\Excel\\Tests\\Cache\\BatchCacheTest::it_can_override_default_ttl":0,"Maatwebsite\\Excel\\Tests\\Concerns\\ExportableTest::needs_to_have_a_file_name_when_downloading":0.002,"Maatwebsite\\Excel\\Tests\\Concerns\\ExportableTest::needs_to_have_a_file_name_when_storing":0,"Maatwebsite\\Excel\\Tests\\Concerns\\ExportableTest::needs_to_have_a_file_name_when_queuing":0,"Maatwebsite\\Excel\\Tests\\Concerns\\ExportableTest::responsable_needs_to_have_file_name_configured_inside_the_export":0.001,"Maatwebsite\\Excel\\Tests\\Concerns\\ExportableTest::is_responsable":0.037,"Maatwebsite\\Excel\\Tests\\Concerns\\ExportableTest::can_have_customized_header":0.002,"Maatwebsite\\Excel\\Tests\\Concerns\\ExportableTest::can_set_custom_headers_in_export_class":0.001,"Maatwebsite\\Excel\\Tests\\Concerns\\ExportableTest::can_get_raw_export_contents":0.005,"Maatwebsite\\Excel\\Tests\\Concerns\\ExportableTest::can_have_customized_disk_options_when_storing":0.007,"Maatwebsite\\Excel\\Tests\\Concerns\\ExportableTest::can_have_customized_disk_options_when_queueing":0,"Maatwebsite\\Excel\\Tests\\Concerns\\ExportableTest::can_set_disk_options_in_export_class_when_storing":0,"Maatwebsite\\Excel\\Tests\\Concerns\\ExportableTest::can_set_disk_options_in_export_class_when_queuing":0,"Maatwebsite\\Excel\\Tests\\Concerns\\ExportableTest::can_override_export_class_disk_options_when_calling_store":0,"Maatwebsite\\Excel\\Tests\\Concerns\\ExportableTest::can_override_export_class_disk_options_when_calling_queue":0,"Maatwebsite\\Excel\\Tests\\Concerns\\ExportableTest::can_have_empty_disk_options_when_storing":0,"Maatwebsite\\Excel\\Tests\\Concerns\\ExportableTest::can_have_empty_disk_options_when_queueing":0,"Maatwebsite\\Excel\\Tests\\Concerns\\FromArrayTest::can_export_from_array":0.011,"Maatwebsite\\Excel\\Tests\\Concerns\\FromCollectionTest::can_export_from_collection":0.036,"Maatwebsite\\Excel\\Tests\\Concerns\\FromCollectionTest::can_export_with_multiple_sheets_from_collection":0.139,"Maatwebsite\\Excel\\Tests\\Concerns\\FromCollectionTest::can_export_from_lazy_collection":0.007,"Maatwebsite\\Excel\\Tests\\Concerns\\FromGeneratorTest::can_export_from_generator":0.007,"Maatwebsite\\Excel\\Tests\\Concerns\\FromIteratorTest::can_export_from_iterator":0.007,"Maatwebsite\\Excel\\Tests\\Concerns\\ImportableTest::can_import_a_simple_xlsx_file":0.006,"Maatwebsite\\Excel\\Tests\\Concerns\\ImportableTest::can_import_a_simple_xlsx_file_from_uploaded_file":0.008,"Maatwebsite\\Excel\\Tests\\Concerns\\ImportableTest::can_import_a_simple_csv_file_with_html_tags_inside":0.006,"Maatwebsite\\Excel\\Tests\\Concerns\\ImportableTest::can_import_a_simple_xlsx_file_with_ignore_empty_set_to_true":0.004,"Maatwebsite\\Excel\\Tests\\Concerns\\ImportableTest::can_import_a_simple_xlsx_file_with_ignore_empty_set_to_false":0.004,"Maatwebsite\\Excel\\Tests\\Concerns\\OnEachRowTest::can_import_each_row_individually":0.004,"Maatwebsite\\Excel\\Tests\\Concerns\\OnEachRowTest::it_respects_the_end_column":0.004,"Maatwebsite\\Excel\\Tests\\Concerns\\RegistersEventListenersTest::events_get_called_when_exporting":0.004,"Maatwebsite\\Excel\\Tests\\Concerns\\RegistersEventListenersTest::events_get_called_when_importing":0.01,"Maatwebsite\\Excel\\Tests\\Concerns\\RegistersEventListenersTest::can_have_invokable_class_as_listener":0.004,"Maatwebsite\\Excel\\Tests\\Concerns\\RemembersChunkOffsetTest::can_set_and_get_chunk_offset":0,"Maatwebsite\\Excel\\Tests\\Concerns\\RemembersChunkOffsetTest::can_access_chunk_offset_on_import_to_array_in_chunks":0.249,"Maatwebsite\\Excel\\Tests\\Concerns\\RemembersRowNumberTest::can_set_and_get_row_number":0.001,"Maatwebsite\\Excel\\Tests\\Concerns\\RemembersRowNumberTest::can_access_row_number_on_import_to_model":0.185,"Maatwebsite\\Excel\\Tests\\Concerns\\RemembersRowNumberTest::can_access_row_number_on_import_to_array_in_chunks":3.444,"Maatwebsite\\Excel\\Tests\\Concerns\\RemembersRowNumberTest::can_access_row_number_on_import_to_array_in_chunks_with_batch_inserts":3.44,"Maatwebsite\\Excel\\Tests\\Concerns\\SkipsEmptyRowsTest::skips_empty_rows_when_importing_to_collection":0.005,"Maatwebsite\\Excel\\Tests\\Concerns\\SkipsEmptyRowsTest::skips_empty_rows_when_importing_on_each_row":0.004,"Maatwebsite\\Excel\\Tests\\Concerns\\SkipsEmptyRowsTest::skips_empty_rows_when_importing_to_model":0.004,"Maatwebsite\\Excel\\Tests\\Concerns\\SkipsEmptyRowsTest::custom_skips_rows_when_importing_to_collection":0.004,"Maatwebsite\\Excel\\Tests\\Concerns\\SkipsEmptyRowsTest::custom_skips_rows_when_importing_to_model":0.005,"Maatwebsite\\Excel\\Tests\\Concerns\\SkipsEmptyRowsTest::custom_skips_rows_when_using_oneachrow":0.005,"Maatwebsite\\Excel\\Tests\\Concerns\\ToArrayTest::can_import_to_array":0.004,"Maatwebsite\\Excel\\Tests\\Concerns\\ToArrayTest::can_import_multiple_sheets_to_array":0.005,"Maatwebsite\\Excel\\Tests\\Concerns\\ToCollectionTest::can_import_to_collection":0.004,"Maatwebsite\\Excel\\Tests\\Concerns\\ToCollectionTest::can_import_multiple_sheets_to_collection":0.004,"Maatwebsite\\Excel\\Tests\\Concerns\\WithBackgroundColorTest::can_configure_background_color_from_rgb_string":0.007,"Maatwebsite\\Excel\\Tests\\Concerns\\WithBackgroundColorTest::can_configure_background_color_as_array":0.006,"Maatwebsite\\Excel\\Tests\\Concerns\\WithBackgroundColorTest::can_configure_background_color_with_color_instance":0.006,"Maatwebsite\\Excel\\Tests\\Concerns\\WithCalculatedFormulasTest::by_default_does_not_calculate_formulas":0.004,"Maatwebsite\\Excel\\Tests\\Concerns\\WithCalculatedFormulasTest::can_import_to_array_with_calculated_formulas":0.005,"Maatwebsite\\Excel\\Tests\\Concerns\\WithCalculatedFormulasTest::can_import_to_model_with_calculated_formulas":0.004,"Maatwebsite\\Excel\\Tests\\Concerns\\WithCalculatedFormulasTest::can_import_to_array_with_calculated_formulas_and_multi_sheet_references":0.006,"Maatwebsite\\Excel\\Tests\\Concerns\\WithCalculatedFormulasTest::can_import_to_array_with_calculated_formulas_and_skips_empty":0.018,"Maatwebsite\\Excel\\Tests\\Concerns\\WithCalculatedFormulasTest::can_import_to_model_with_calculated_formulas_and_skips_empty":0.004,"Maatwebsite\\Excel\\Tests\\Concerns\\WithColumnFormattingTest::can_export_with_column_formatting":0.008,"Maatwebsite\\Excel\\Tests\\Concerns\\WithColumnWidthsTest::can_set_column_width":0.007,"Maatwebsite\\Excel\\Tests\\Concerns\\WithCustomCsvSettingsTest::can_store_csv_export_with_custom_settings":0.002,"Maatwebsite\\Excel\\Tests\\Concerns\\WithCustomCsvSettingsTest::can_store_csv_export_with_custom_encoding":0.001,"Maatwebsite\\Excel\\Tests\\Concerns\\WithCustomCsvSettingsTest::can_read_csv_with_auto_detecting_delimiter_semicolon":0.002,"Maatwebsite\\Excel\\Tests\\Concerns\\WithCustomCsvSettingsTest::can_read_csv_with_auto_detecting_delimiter_comma":0.002,"Maatwebsite\\Excel\\Tests\\Concerns\\WithCustomCsvSettingsTest::can_read_csv_import_with_custom_settings":0.003,"Maatwebsite\\Excel\\Tests\\Concerns\\WithCustomCsvSettingsTest::cannot_read_with_wrong_delimiter":0.002,"Maatwebsite\\Excel\\Tests\\Concerns\\WithCustomStartCellTest::can_store_collection_with_custom_start_cell":0.003,"Maatwebsite\\Excel\\Tests\\Concerns\\WithCustomValueBinderTest::can_set_a_value_binder_on_export":0.011,"Maatwebsite\\Excel\\Tests\\Concerns\\WithCustomValueBinderTest::can_set_a_value_binder_on_import":0.005,"Maatwebsite\\Excel\\Tests\\Concerns\\WithDefaultStylesTest::can_configure_default_styles":0.007,"Maatwebsite\\Excel\\Tests\\Concerns\\WithEventsTest::export_events_get_called":0.004,"Maatwebsite\\Excel\\Tests\\Concerns\\WithEventsTest::import_events_get_called":0.004,"Maatwebsite\\Excel\\Tests\\Concerns\\WithEventsTest::import_chunked_events_get_called":0.499,"Maatwebsite\\Excel\\Tests\\Concerns\\WithEventsTest::can_have_invokable_class_as_listener":0.004,"Maatwebsite\\Excel\\Tests\\Concerns\\WithEventsTest::can_have_global_event_listeners":0.004,"Maatwebsite\\Excel\\Tests\\Concerns\\WithEventsTest::can_have_custom_concern_handlers":0.012,"Maatwebsite\\Excel\\Tests\\Concerns\\WithEventsTest::can_have_custom_sheet_concern_handlers":0.011,"Maatwebsite\\Excel\\Tests\\Concerns\\WithFormatDataTest::by_default_import_to_array":0.008,"Maatwebsite\\Excel\\Tests\\Concerns\\WithFormatDataTest::can_import_to_array_with_format_data":0.007,"Maatwebsite\\Excel\\Tests\\Concerns\\WithFormatDataTest::can_import_to_array_with_format_data_and_skips_empty_rows":0.006,"Maatwebsite\\Excel\\Tests\\Concerns\\WithFormatDataTest::by_default_import_to_collection":0.004,"Maatwebsite\\Excel\\Tests\\Concerns\\WithFormatDataTest::can_import_to_collection_with_format_data":0.005,"Maatwebsite\\Excel\\Tests\\Concerns\\WithFormatDataTest::by_default_import_to_model":0.006,"Maatwebsite\\Excel\\Tests\\Concerns\\WithFormatDataTest::can_import_to_model_with_format_data":0.005,"Maatwebsite\\Excel\\Tests\\Concerns\\WithHeadingsTest::can_export_from_collection_with_heading_row":0.007,"Maatwebsite\\Excel\\Tests\\Concerns\\WithHeadingsTest::can_export_from_collection_with_multiple_heading_rows":0.021,"Maatwebsite\\Excel\\Tests\\Concerns\\WithHeadingsTest::can_export_from_collection_with_heading_row_with_custom_start_cell":0.007,"Maatwebsite\\Excel\\Tests\\Concerns\\WithMappingTest::can_export_with_heading":0.007,"Maatwebsite\\Excel\\Tests\\Concerns\\WithMappingTest::can_return_multiple_rows_in_map":0.007,"Maatwebsite\\Excel\\Tests\\Concerns\\WithMappingTest::json_array_columns_shouldnt_be_detected_as_multiple_rows":0.007,"Maatwebsite\\Excel\\Tests\\Concerns\\WithPropertiesTest::can_set_custom_document_properties":0.006,"Maatwebsite\\Excel\\Tests\\Concerns\\WithPropertiesTest::it_merges_with_default_properties":0.006,"Maatwebsite\\Excel\\Tests\\Concerns\\WithPropertiesTest::it_ignores_empty_properties":0.006,"Maatwebsite\\Excel\\Tests\\Concerns\\WithReadFilterTest::can_register_custom_read_filter":0.003,"Maatwebsite\\Excel\\Tests\\Concerns\\WithStrictNullComparisonTest::exported_zero_values_are_not_null_when_exporting_with_strict_null_comparison":0.007,"Maatwebsite\\Excel\\Tests\\Concerns\\WithStrictNullComparisonTest::exported_zero_values_are_null_when_not_exporting_with_strict_null_comparison":0.006,"Maatwebsite\\Excel\\Tests\\Concerns\\WithStrictNullComparisonTest::exports_trailing_empty_cells":0.003,"Maatwebsite\\Excel\\Tests\\Concerns\\WithStrictNullComparisonTest::exports_trailing_empty_cells_by_setting_config_strict_null_comparison":0.003,"Maatwebsite\\Excel\\Tests\\Concerns\\WithStylesTest::can_configure_styles":0.009,"Maatwebsite\\Excel\\Tests\\Concerns\\WithTitleTest::can_export_with_title":0.007,"Maatwebsite\\Excel\\Tests\\Concerns\\WithTitleTest::can_export_sheet_title_when_longer_than_max_length":0.006,"Maatwebsite\\Excel\\Tests\\DelegatedMacroableTest::can_call_methods_from_delegate":0.004,"Maatwebsite\\Excel\\Tests\\DelegatedMacroableTest::can_use_writer_macros":0.004,"Maatwebsite\\Excel\\Tests\\DelegatedMacroableTest::can_use_sheet_macros":0.004,"Maatwebsite\\Excel\\Tests\\ExcelFakeTest::can_fake_an_export":0.001,"Maatwebsite\\Excel\\Tests\\ExcelFakeTest::can_assert_against_a_fake_downloaded_export":0.001,"Maatwebsite\\Excel\\Tests\\ExcelFakeTest::can_assert_against_a_fake_stored_export":0,"Maatwebsite\\Excel\\Tests\\ExcelFakeTest::can_assert_regex_against_a_fake_stored_export_with_multiple_files":0,"Maatwebsite\\Excel\\Tests\\ExcelFakeTest::a_callback_can_be_passed_as_the_second_argument_when_asserting_against_a_faked_stored_export":0,"Maatwebsite\\Excel\\Tests\\ExcelFakeTest::can_assert_against_a_fake_queued_export":0,"Maatwebsite\\Excel\\Tests\\ExcelFakeTest::can_assert_against_a_fake_implicitly_queued_export":0,"Maatwebsite\\Excel\\Tests\\ExcelFakeTest::can_assert_against_a_fake_queued_export_with_chain":0.001,"Maatwebsite\\Excel\\Tests\\ExcelFakeTest::can_assert_against_a_fake_raw_export":0,"Maatwebsite\\Excel\\Tests\\ExcelFakeTest::can_assert_against_a_fake_import":0,"Maatwebsite\\Excel\\Tests\\ExcelFakeTest::can_assert_against_a_fake_import_with_uploaded_file":0.001,"Maatwebsite\\Excel\\Tests\\ExcelFakeTest::can_assert_against_a_fake_queued_import":0,"Maatwebsite\\Excel\\Tests\\ExcelFakeTest::can_assert_against_a_fake_implicitly_queued_import":0,"Maatwebsite\\Excel\\Tests\\ExcelFakeTest::can_assert_against_a_fake_queued_import_with_chain":0,"Maatwebsite\\Excel\\Tests\\ExcelFakeTest::a_callback_can_be_passed_as_the_second_argument_when_asserting_against_a_faked_queued_export":0,"Maatwebsite\\Excel\\Tests\\ExcelServiceProviderTest::custom_transaction_handler_is_bound":0.001,"Maatwebsite\\Excel\\Tests\\ExcelServiceProviderTest::is_bound":0,"Maatwebsite\\Excel\\Tests\\ExcelServiceProviderTest::has_aliased":0,"Maatwebsite\\Excel\\Tests\\ExcelServiceProviderTest::registers_console_commands":0.006,"Maatwebsite\\Excel\\Tests\\ExcelServiceProviderTest::sets_php_spreadsheet_settings":0,"Maatwebsite\\Excel\\Tests\\ExcelTest::can_download_an_export_object_with_facade":0.004,"Maatwebsite\\Excel\\Tests\\ExcelTest::can_download_an_export_object":0.004,"Maatwebsite\\Excel\\Tests\\ExcelTest::can_store_an_export_object_on_default_disk":0.004,"Maatwebsite\\Excel\\Tests\\ExcelTest::can_store_an_export_object_on_another_disk":0.004,"Maatwebsite\\Excel\\Tests\\ExcelTest::can_store_csv_export_with_default_settings":0.001,"Maatwebsite\\Excel\\Tests\\ExcelTest::can_get_raw_export_contents":0.004,"Maatwebsite\\Excel\\Tests\\ExcelTest::can_store_tsv_export_with_default_settings":0.001,"Maatwebsite\\Excel\\Tests\\ExcelTest::can_store_csv_export_with_custom_settings":0.001,"Maatwebsite\\Excel\\Tests\\ExcelTest::cannot_use_from_collection_and_from_view_on_same_export":0.001,"Maatwebsite\\Excel\\Tests\\ExcelTest::can_import_a_simple_xlsx_file_to_array":0.003,"Maatwebsite\\Excel\\Tests\\ExcelTest::can_import_a_simple_xlsx_file_to_collection":0.039,"Maatwebsite\\Excel\\Tests\\ExcelTest::can_import_a_simple_xlsx_file_to_collection_without_import_object":0.031,"Maatwebsite\\Excel\\Tests\\ExcelTest::can_import_a_simple_xlsx_file":0.004,"Maatwebsite\\Excel\\Tests\\ExcelTest::can_import_a_tsv_file":0.005,"Maatwebsite\\Excel\\Tests\\ExcelTest::can_chain_imports":0.006,"Maatwebsite\\Excel\\Tests\\ExcelTest::can_import_a_simple_xlsx_file_from_uploaded_file":0.004,"Maatwebsite\\Excel\\Tests\\ExcelTest::can_import_a_simple_xlsx_file_from_real_path":0.003,"Maatwebsite\\Excel\\Tests\\ExcelTest::import_will_throw_error_when_no_reader_type_could_be_detected_when_no_extension":0.001,"Maatwebsite\\Excel\\Tests\\ExcelTest::import_will_throw_error_when_no_reader_type_could_be_detected_with_unknown_extension":0.013,"Maatwebsite\\Excel\\Tests\\ExcelTest::can_import_without_extension_with_explicit_reader_type":0.004,"Maatwebsite\\Excel\\Tests\\HeadingRowImportTest::can_import_only_heading_row":0.059,"Maatwebsite\\Excel\\Tests\\HeadingRowImportTest::can_import_only_heading_row_with_custom_heading_row_formatter":0.003,"Maatwebsite\\Excel\\Tests\\HeadingRowImportTest::can_import_only_heading_row_with_custom_heading_row_formatter_with_key":0.003,"Maatwebsite\\Excel\\Tests\\HeadingRowImportTest::can_import_only_heading_row_with_custom_row_number":0.008,"Maatwebsite\\Excel\\Tests\\HeadingRowImportTest::can_import_only_heading_row_for_multiple_sheets":0.003,"Maatwebsite\\Excel\\Tests\\HeadingRowImportTest::can_import_only_heading_row_for_multiple_sheets_with_key":0.003,"Maatwebsite\\Excel\\Tests\\HeadingRowImportTest::can_import_only_heading_row_for_multiple_sheets_with_custom_row_number":0.003,"Maatwebsite\\Excel\\Tests\\HeadingRowImportTest::can_import_heading_row_with_custom_formatter_defined_in_config":0.003,"Maatwebsite\\Excel\\Tests\\InteractsWithQueueTest::read_chunk_job_can_interact_with_queue":0.001,"Maatwebsite\\Excel\\Tests\\InteractsWithQueueTest::append_data_to_sheet_job_can_interact_with_queue":0,"Maatwebsite\\Excel\\Tests\\InteractsWithQueueTest::append_query_to_sheet_job_can_interact_with_queue":0,"Maatwebsite\\Excel\\Tests\\InteractsWithQueueTest::append_view_to_sheet_job_can_interact_with_queue":0,"Maatwebsite\\Excel\\Tests\\Mixins\\DownloadCollectionTest::can_download_a_collection_as_excel":0.008,"Maatwebsite\\Excel\\Tests\\Mixins\\DownloadCollectionTest::can_download_a_collection_with_headers_as_excel":0.009,"Maatwebsite\\Excel\\Tests\\Mixins\\DownloadCollectionTest::can_download_collection_with_headers_with_hidden_eloquent_attributes":0.007,"Maatwebsite\\Excel\\Tests\\Mixins\\DownloadCollectionTest::can_download_collection_with_headers_when_making_attributes_visible":0.007,"Maatwebsite\\Excel\\Tests\\Mixins\\DownloadCollectionTest::can_set_custom_response_headers":0.005,"Maatwebsite\\Excel\\Tests\\Mixins\\StoreCollectionTest::can_store_a_collection_as_excel":0.011,"Maatwebsite\\Excel\\Tests\\Mixins\\StoreCollectionTest::can_store_a_collection_as_excel_on_non_default_disk":0.006,"Maatwebsite\\Excel\\Tests\\Mixins\\StoreCollectionTest::can_store_a_collection_with_headings_as_excel":0.007,"Maatwebsite\\Excel\\Tests\\Mixins\\StoreCollectionTest::can_store_a_model_collection_with_headings_as_excel":0.008,"Maatwebsite\\Excel\\Tests\\QueuedExportTest::can_queue_an_export_and_store_on_different_disk":0.234,"Maatwebsite\\Excel\\Tests\\QueuedExportTest::can_queue_export_with_remote_temp_disk":0.276,"Maatwebsite\\Excel\\Tests\\QueuedExportTest::can_queue_export_with_remote_temp_disk_and_prefix":0.24,"Maatwebsite\\Excel\\Tests\\QueuedExportTest::can_implicitly_queue_an_export":0.241,"Maatwebsite\\Excel\\Tests\\QueuedExportTest::can_queue_export_with_mapping_on_eloquent_models":0.017,"Maatwebsite\\Excel\\Tests\\QueuedExportTest::can_catch_failures":0.007,"Maatwebsite\\Excel\\Tests\\QueuedExportTest::can_catch_failures_on_queue_export_job":0.003,"Maatwebsite\\Excel\\Tests\\QueuedExportTest::can_set_locale_on_queue_export_job":0.015,"Maatwebsite\\Excel\\Tests\\QueuedExportTest::can_queue_export_not_flushing_the_cache":0.294,"Maatwebsite\\Excel\\Tests\\TemporaryFileTest::can_use_default_rights":0.001,"Maatwebsite\\Excel\\Tests\\TemporaryFileTest::can_use_dir_rights":0.001,"Maatwebsite\\Excel\\Tests\\TemporaryFileTest::can_use_file_rights":0.001,"ShouldQueueWithoutChainTest::can_import_to_model_in_chunks":0.065,"ShouldQueueWithoutChainTest::can_import_to_model_without_job_chaining":0.053,"ShouldQueueWithoutChainTest::a_queue_name_can_be_specified_when_importing":0.045,"ShouldQueueWithoutChainTest::the_cleanup_only_runs_when_all_jobs_are_done":0.067,"Maatwebsite\\Excel\\Tests\\Concerns\\SkipsOnErrorTest::can_skip_on_error":0.039,"Maatwebsite\\Excel\\Tests\\Concerns\\SkipsOnErrorTest::can_skip_errors_and_collect_all_errors_at_the_end":0.036,"Maatwebsite\\Excel\\Tests\\Concerns\\SkipsOnFailureTest::can_skip_on_error":0.037,"Maatwebsite\\Excel\\Tests\\Concerns\\SkipsOnFailureTest::skips_only_failed_rows_in_batch":0.029,"Maatwebsite\\Excel\\Tests\\Concerns\\SkipsOnFailureTest::can_skip_failures_and_collect_all_failures_at_the_end":0.028,"Maatwebsite\\Excel\\Tests\\Concerns\\SkipsOnFailureTest::can_validate_using_oneachrow_and_skipsonfailure":0.028,"Maatwebsite\\Excel\\Tests\\Concerns\\SkipsOnFailureTest::can_validate_using_tocollection_and_skipsonfailure":0.028,"Maatwebsite\\Excel\\Tests\\Concerns\\ToModelTest::can_import_each_row_to_model":0.058,"Maatwebsite\\Excel\\Tests\\Concerns\\ToModelTest::has_timestamps_when_imported_single_model":0.052,"Maatwebsite\\Excel\\Tests\\Concerns\\ToModelTest::can_import_multiple_models_in_single_to_model":0.06,"Maatwebsite\\Excel\\Tests\\Concerns\\ToModelTest::can_import_multiple_different_types_of_models_in_single_to_model":0.063,"Maatwebsite\\Excel\\Tests\\Concerns\\WithBatchInsertsTest::can_import_to_model_in_batches":0.074,"Maatwebsite\\Excel\\Tests\\Concerns\\WithBatchInsertsTest::can_import_to_model_in_batches_bigger_file":0.278,"Maatwebsite\\Excel\\Tests\\Concerns\\WithBatchInsertsTest::can_import_multiple_different_types_of_models_in_single_to_model":0.054,"Maatwebsite\\Excel\\Tests\\Concerns\\WithBatchInsertsTest::has_timestamps_when_imported_in_batches":0.056,"Maatwebsite\\Excel\\Tests\\Concerns\\WithChunkReadingTest::can_import_to_model_in_chunks_un":0.065,"Maatwebsite\\Excel\\Tests\\Concerns\\WithChunkReadingTest::can_import_to_model_in_chunks_and_insert_in_batches":0.436,"Maatwebsite\\Excel\\Tests\\Concerns\\WithChunkReadingTest::can_import_to_model_in_chunks_and_insert_in_batches_with_heading_row":0.429,"Maatwebsite\\Excel\\Tests\\Concerns\\WithChunkReadingTest::can_import_csv_in_chunks_and_insert_in_batches":1.206,"Maatwebsite\\Excel\\Tests\\Concerns\\WithChunkReadingTest::can_import_to_model_in_chunks_and_insert_in_batches_with_multiple_sheets":1.223,"Maatwebsite\\Excel\\Tests\\Concerns\\WithChunkReadingTest::can_import_to_array_in_chunks":1.854,"Maatwebsite\\Excel\\Tests\\Concerns\\WithChunkReadingTest::can_import_to_model_in_chunks_and_insert_in_batches_with_multiple_sheets_objects_by_index":1.216,"Maatwebsite\\Excel\\Tests\\Concerns\\WithChunkReadingTest::can_import_to_model_in_chunks_and_insert_in_batches_with_multiple_sheets_objects_by_name":1.211,"Maatwebsite\\Excel\\Tests\\Concerns\\WithChunkReadingTest::can_catch_job_failed_in_chunks":0.048,"Maatwebsite\\Excel\\Tests\\Concerns\\WithChunkReadingTest::can_import_to_array_and_format_in_chunks":0.054,"Maatwebsite\\Excel\\Tests\\Concerns\\WithChunkReadingTest::can_import_to_array_in_chunks_without_formatting":0.048,"Maatwebsite\\Excel\\Tests\\Concerns\\WithColumnLimitTest::can_import_to_array_with_column_limit":0.026,"Maatwebsite\\Excel\\Tests\\Concerns\\WithColumnLimitTest::can_import_to_array_with_column_limit_and_skips_empty_rows":0.033,"Maatwebsite\\Excel\\Tests\\Concerns\\WithGroupedHeadingRowTest::can_import_to_array_with_grouped_headers":0.055,"Maatwebsite\\Excel\\Tests\\Concerns\\WithGroupedHeadingRowTest::can_import_oneachrow_with_grouped_headers":0.053,"Maatwebsite\\Excel\\Tests\\Concerns\\WithGroupedHeadingRowTest::can_import_to_collection_with_grouped_headers":0.05,"Maatwebsite\\Excel\\Tests\\Concerns\\WithGroupedHeadingRowTest::can_import_each_row_to_model_with_grouped_headers":0.061,"Maatwebsite\\Excel\\Tests\\Concerns\\WithHeadingRowTest::can_import_each_row_to_model_with_heading_row":0.029,"Maatwebsite\\Excel\\Tests\\Concerns\\WithHeadingRowTest::can_import_each_row_to_model_with_different_heading_row":0.03,"Maatwebsite\\Excel\\Tests\\Concerns\\WithHeadingRowTest::can_import_to_array_with_heading_row":0.028,"Maatwebsite\\Excel\\Tests\\Concerns\\WithHeadingRowTest::can_import_empty_rows_with_header":0.029,"Maatwebsite\\Excel\\Tests\\Concerns\\WithHeadingRowTest::can_import_empty_models_with_header":0.03,"Maatwebsite\\Excel\\Tests\\Concerns\\WithHeadingRowTest::can_cast_empty_headers_to_indexed_int":0.028,"Maatwebsite\\Excel\\Tests\\Concerns\\WithLimitTest::can_import_a_limited_section_of_rows_to_model_with_different_start_row":0.057,"Maatwebsite\\Excel\\Tests\\Concerns\\WithLimitTest::can_import_to_array_with_limit":0.03,"Maatwebsite\\Excel\\Tests\\Concerns\\WithLimitTest::can_set_limit_bigger_than_row_size":0.028,"Maatwebsite\\Excel\\Tests\\Concerns\\WithMappedCellsTest::can_import_with_references_to_cells":0.034,"Maatwebsite\\Excel\\Tests\\Concerns\\WithMappedCellsTest::can_import_with_nested_references_to_cells":0.031,"Maatwebsite\\Excel\\Tests\\Concerns\\WithMappedCellsTest::can_import_with_references_to_cells_to_model":0.041,"Maatwebsite\\Excel\\Tests\\Concerns\\WithStartRowTest::can_import_each_row_to_model_with_different_start_row":0.027,"Maatwebsite\\Excel\\Tests\\Concerns\\WithStartRowTest::can_import_to_array_with_start_row":0.027,"Maatwebsite\\Excel\\Tests\\Concerns\\WithUpsertsTest::can_upsert_models_in_batches":0.03,"Maatwebsite\\Excel\\Tests\\Concerns\\WithUpsertsTest::can_upsert_models_in_rows":0.03,"Maatwebsite\\Excel\\Tests\\Concerns\\WithUpsertsTest::can_upsert_models_in_batches_with_defined_upsert_columns":0.03,"Maatwebsite\\Excel\\Tests\\Concerns\\WithUpsertsTest::can_upsert_models_in_rows_with_defined_upsert_columns":0.029,"Maatwebsite\\Excel\\Tests\\Concerns\\WithValidationTest::can_validate_rows":0.068,"Maatwebsite\\Excel\\Tests\\Concerns\\WithValidationTest::can_validate_rows_with_closure_validation_rules":0.054,"Maatwebsite\\Excel\\Tests\\Concerns\\WithValidationTest::can_validate_rows_with_custom_validation_rule_objects":0.07,"Maatwebsite\\Excel\\Tests\\Concerns\\WithValidationTest::can_validate_rows_with_conditionality":0.053,"Maatwebsite\\Excel\\Tests\\Concerns\\WithValidationTest::can_validate_rows_with_unless_conditionality":0.056,"Maatwebsite\\Excel\\Tests\\Concerns\\WithValidationTest::can_validate_rows_with_combined_rules_with_colons":0.067,"Maatwebsite\\Excel\\Tests\\Concerns\\WithValidationTest::can_validate_with_custom_attributes":0.049,"Maatwebsite\\Excel\\Tests\\Concerns\\WithValidationTest::can_validate_with_custom_message":0.051,"Maatwebsite\\Excel\\Tests\\Concerns\\WithValidationTest::can_validate_rows_with_headings":0.049,"Maatwebsite\\Excel\\Tests\\Concerns\\WithValidationTest::can_validate_rows_with_grouped_headings":0.049,"Maatwebsite\\Excel\\Tests\\Concerns\\WithValidationTest::can_validate_rows_in_batches":0.052,"Maatwebsite\\Excel\\Tests\\Concerns\\WithValidationTest::can_validate_using_oneachrow":0.05,"Maatwebsite\\Excel\\Tests\\Concerns\\WithValidationTest::can_validate_using_collection":0.053,"Maatwebsite\\Excel\\Tests\\Concerns\\WithValidationTest::can_validate_using_array":0.129,"Maatwebsite\\Excel\\Tests\\Concerns\\WithValidationTest::can_configure_validator":0.059,"Maatwebsite\\Excel\\Tests\\Concerns\\WithValidationTest::can_prepare_using_toarray":0.057,"Maatwebsite\\Excel\\Tests\\Concerns\\WithValidationTest::can_prepare_using_tocollection":0.051,"Maatwebsite\\Excel\\Tests\\Concerns\\WithValidationTest::can_prepare_using_tomodel":0.053,"Maatwebsite\\Excel\\Tests\\Concerns\\WithValidationTest::can_prepare_using_oneachrow":0.052,"Maatwebsite\\Excel\\Tests\\Concerns\\WithValidationTest::can_prepare_using_skipsemptyrows":0.06,"Maatwebsite\\Excel\\Tests\\QueuedImportTest::cannot_queue_import_that_does_not_implement_should_queue":0.053,"Maatwebsite\\Excel\\Tests\\QueuedImportTest::can_queue_an_import":2.07,"Maatwebsite\\Excel\\Tests\\QueuedImportTest::can_queue_an_import_with_batch_cache_and_file_store":2.129,"Maatwebsite\\Excel\\Tests\\QueuedImportTest::can_queue_import_with_remote_temp_disk":2.209,"Maatwebsite\\Excel\\Tests\\QueuedImportTest::can_keep_extension_for_temp_file_on_remote_disk":2.125,"Maatwebsite\\Excel\\Tests\\QueuedImportTest::can_queue_import_with_remote_temp_disk_and_prefix":2.08,"Maatwebsite\\Excel\\Tests\\QueuedImportTest::can_automatically_delete_temp_file_on_failure_when_using_remote_disk":0.106,"Maatwebsite\\Excel\\Tests\\QueuedImportTest::cannot_automatically_delete_temp_file_on_failure_when_using_local_disk":0.12,"Maatwebsite\\Excel\\Tests\\QueuedImportTest::can_force_remote_download_and_deletion_for_each_chunk_on_queue":2.13,"Maatwebsite\\Excel\\Tests\\QueuedImportTest::can_define_middleware_method_on_queued_import":0.065,"Maatwebsite\\Excel\\Tests\\QueuedImportTest::can_define_retry_until_method_on_queued_import":0.064,"Maatwebsite\\Excel\\Tests\\QueuedImportTest::can_define_max_exceptions_property_on_queued_import":0.105,"Maatwebsite\\Excel\\Tests\\Concerns\\FromQueryTest::can_export_from_query":0.116,"Maatwebsite\\Excel\\Tests\\Concerns\\FromQueryTest::can_export_from_relation_query_queued":0.181,"Maatwebsite\\Excel\\Tests\\Concerns\\FromQueryTest::can_export_from_query_with_eager_loads":0.085,"Maatwebsite\\Excel\\Tests\\Concerns\\FromQueryTest::can_export_from_query_with_eager_loads_and_queued":0.106,"Maatwebsite\\Excel\\Tests\\Concerns\\FromQueryTest::can_export_from_query_builder_without_using_eloquent":0.063,"Maatwebsite\\Excel\\Tests\\Concerns\\FromQueryTest::can_export_from_query_builder_without_using_eloquent_and_queued":0.149,"Maatwebsite\\Excel\\Tests\\Concerns\\FromQueryTest::can_export_from_query_builder_with_nested_arrays":0.069,"Maatwebsite\\Excel\\Tests\\Concerns\\FromQueryTest::can_export_from_query_builder_with_nested_arrays_queued":0.079,"Maatwebsite\\Excel\\Tests\\Concerns\\FromQueryTest::can_export_from_query_with_batch_caching":0.114,"Maatwebsite\\Excel\\Tests\\Concerns\\FromQueryTest::can_export_from_query_with_prepare_rows":0.123,"Maatwebsite\\Excel\\Tests\\Concerns\\FromQueryTest::can_export_from_scout":0.117,"Maatwebsite\\Excel\\Tests\\Concerns\\FromViewTest::can_export_from_view":0.041,"Maatwebsite\\Excel\\Tests\\Concerns\\FromViewTest::can_export_multiple_sheets_from_view":0.094,"Maatwebsite\\Excel\\Tests\\Concerns\\WithConditionalSheetsTest::can_select_which_sheets_will_be_imported":0.013,"Maatwebsite\\Excel\\Tests\\Concerns\\WithCustomQuerySizeTest::can_export_with_custom_count":0.09,"Maatwebsite\\Excel\\Tests\\Concerns\\WithMultipleSheetsTest::can_export_with_multiple_sheets_using_collections":0.151,"Maatwebsite\\Excel\\Tests\\Concerns\\WithMultipleSheetsTest::can_export_multiple_sheets_from_view":0.11,"Maatwebsite\\Excel\\Tests\\Concerns\\WithMultipleSheetsTest::unknown_sheet_index_will_throw_sheet_not_found_exception":0.005,"Maatwebsite\\Excel\\Tests\\Concerns\\WithMultipleSheetsTest::unknown_sheet_name_will_throw_sheet_not_found_exception":0.004,"Maatwebsite\\Excel\\Tests\\Concerns\\WithMultipleSheetsTest::unknown_sheet_name_can_be_ignored":0.004,"Maatwebsite\\Excel\\Tests\\Concerns\\WithMultipleSheetsTest::unknown_sheet_indices_can_be_ignored_per_name":0.004,"Maatwebsite\\Excel\\Tests\\Concerns\\WithMultipleSheetsTest::unknown_sheet_indices_can_be_ignored":0.004,"Maatwebsite\\Excel\\Tests\\Concerns\\WithMultipleSheetsTest::unknown_sheet_indices_can_be_ignored_per_sheet":0.004,"Maatwebsite\\Excel\\Tests\\Concerns\\WithMultipleSheetsTest::can_import_multiple_sheets":0.013,"Maatwebsite\\Excel\\Tests\\Concerns\\WithMultipleSheetsTest::can_import_multiple_sheets_by_sheet_name":0.004,"Maatwebsite\\Excel\\Tests\\Concerns\\WithMultipleSheetsTest::can_import_multiple_sheets_by_sheet_index_and_name":0.004,"Maatwebsite\\Excel\\Tests\\Concerns\\WithMultipleSheetsTest::can_import_multiple_sheets_by_sheet_name_and_index":0.004,"Maatwebsite\\Excel\\Tests\\QueuedQueryExportTest::can_queue_an_export":0.208,"Maatwebsite\\Excel\\Tests\\QueuedQueryExportTest::can_queue_an_export_with_batch_cache_and_file_store":0.262,"Maatwebsite\\Excel\\Tests\\QueuedQueryExportTest::can_queue_an_export_with_mapping":0.049,"Maatwebsite\\Excel\\Tests\\QueuedQueryExportTest::can_queue_scout_export":0.228,"Maatwebsite\\Excel\\Tests\\QueuedViewExportTest::can_queue_an_export":0.145,"Maatwebsite\\Excel\\Tests\\QueuedViewExportTest::can_export_multiple_sheets_from_view":0.26,"Maatwebsite\\Excel\\Tests\\Concerns\\FromCollectionTest::can_export_from_lazy_collection_with_queue":0.026,"Maatwebsite\\Excel\\Tests\\CellTest::can_get_cell_value":0.029,"Maatwebsite\\Excel\\Tests\\CellTest::can_trim_empty_cells":0.004,"Maatwebsite\\Excel\\Tests\\CellTest::convert_empty_cells_to_null":0.003,"Maatwebsite\\Excel\\Tests\\Concerns\\WithEventsTest::export_chunked_events_get_called":0.113,"Maatwebsite\\Excel\\Tests\\Mixins\\ImportAsMacroTest::can_import_directly_into_a_model_with_mapping":0.036,"Maatwebsite\\Excel\\Tests\\Mixins\\ImportMacroTest::can_import_directly_into_a_model":0.036,"Maatwebsite\\Excel\\Tests\\Mixins\\DownloadQueryMacroTest::can_download_a_query_as_excel":0.062,"Maatwebsite\\Excel\\Tests\\Mixins\\DownloadQueryMacroTest::can_download_a_collection_with_headers_as_excel":0.061,"Maatwebsite\\Excel\\Tests\\Mixins\\StoreQueryMacroTest::can_download_a_query_as_excel":0.063,"Maatwebsite\\Excel\\Tests\\Mixins\\StoreQueryMacroTest::can_download_a_query_as_excel_on_different_disk":0.061,"Maatwebsite\\Excel\\Tests\\Mixins\\StoreQueryMacroTest::can_store_a_query_with_headers_as_excel":0.07,"Maatwebsite\\Excel\\Tests\\Concerns\\ToModelTest::can_import_models_with_belongs_to_relations":0.067,"Maatwebsite\\Excel\\Tests\\Concerns\\ToModelTest::can_import_models_with_belongs_to_many_relations":0.07,"Maatwebsite\\Excel\\Tests\\HeadingRowImportTest::test_can_import_only_heading_row":0.005,"Maatwebsite\\Excel\\Tests\\Cache\\BatchCacheTest::test_will_get_multiple_from_memory_if_cells_hold_in_memory":0.032,"Maatwebsite\\Excel\\Tests\\Cache\\BatchCacheTest::test_will_get_multiple_from_cache_if_cells_are_persisted":0.001,"Maatwebsite\\Excel\\Tests\\Cache\\BatchCacheTest::test_will_get_multiple_from_cache_and_persisted":0.001,"Maatwebsite\\Excel\\Tests\\Cache\\BatchCacheTest::test_it_persists_to_cache_when_memory_limit_reached_on_setting_a_value":0.001,"Maatwebsite\\Excel\\Tests\\Cache\\BatchCacheTest::test_it_persists_to_cache_when_memory_limit_reached_on_setting_multiple_values":0.001,"Maatwebsite\\Excel\\Tests\\Cache\\BatchCacheTest::test_it_writes_to_cache_with_default_ttl#null (forever)":0.002,"Maatwebsite\\Excel\\Tests\\Cache\\BatchCacheTest::test_it_writes_to_cache_with_default_ttl#int value":0.001,"Maatwebsite\\Excel\\Tests\\Cache\\BatchCacheTest::test_it_writes_to_cache_with_default_ttl#callable":0.001,"Maatwebsite\\Excel\\Tests\\Cache\\BatchCacheTest::test_it_writes_to_cache_with_a_dateinterval_ttl":0.001,"Maatwebsite\\Excel\\Tests\\Cache\\BatchCacheTest::test_it_can_override_default_ttl":0,"Maatwebsite\\Excel\\Tests\\CellTest::test_can_get_cell_value":0.043,"Maatwebsite\\Excel\\Tests\\CellTest::test_can_trim_empty_cells":0.004,"Maatwebsite\\Excel\\Tests\\CellTest::test_convert_empty_cells_to_null":0.005,"Maatwebsite\\Excel\\Tests\\Concerns\\ExportableTest::test_needs_to_have_a_file_name_when_downloading":0.001,"Maatwebsite\\Excel\\Tests\\Concerns\\ExportableTest::test_needs_to_have_a_file_name_when_storing":0,"Maatwebsite\\Excel\\Tests\\Concerns\\ExportableTest::test_needs_to_have_a_file_name_when_queuing":0,"Maatwebsite\\Excel\\Tests\\Concerns\\ExportableTest::test_responsable_needs_to_have_file_name_configured_inside_the_export":0,"Maatwebsite\\Excel\\Tests\\Concerns\\ExportableTest::test_is_responsable":0.037,"Maatwebsite\\Excel\\Tests\\Concerns\\ExportableTest::test_can_have_customized_header":0.002,"Maatwebsite\\Excel\\Tests\\Concerns\\ExportableTest::test_can_set_custom_headers_in_export_class":0.002,"Maatwebsite\\Excel\\Tests\\Concerns\\ExportableTest::test_can_get_raw_export_contents":0.008,"Maatwebsite\\Excel\\Tests\\Concerns\\ExportableTest::test_can_have_customized_disk_options_when_storing":0.005,"Maatwebsite\\Excel\\Tests\\Concerns\\ExportableTest::test_can_have_customized_disk_options_when_queueing":0,"Maatwebsite\\Excel\\Tests\\Concerns\\ExportableTest::test_can_set_disk_options_in_export_class_when_storing":0.001,"Maatwebsite\\Excel\\Tests\\Concerns\\ExportableTest::test_can_set_disk_options_in_export_class_when_queuing":0.001,"Maatwebsite\\Excel\\Tests\\Concerns\\ExportableTest::test_can_override_export_class_disk_options_when_calling_store":0,"Maatwebsite\\Excel\\Tests\\Concerns\\ExportableTest::test_can_override_export_class_disk_options_when_calling_queue":0,"Maatwebsite\\Excel\\Tests\\Concerns\\ExportableTest::test_can_have_empty_disk_options_when_storing":0,"Maatwebsite\\Excel\\Tests\\Concerns\\ExportableTest::test_can_have_empty_disk_options_when_queueing":0.001,"Maatwebsite\\Excel\\Tests\\Concerns\\FromArrayTest::test_can_export_from_array":0.029,"Maatwebsite\\Excel\\Tests\\Concerns\\FromCollectionTest::test_can_export_from_collection":0.049,"Maatwebsite\\Excel\\Tests\\Concerns\\FromCollectionTest::test_can_export_with_multiple_sheets_from_collection":0.196,"Maatwebsite\\Excel\\Tests\\Concerns\\FromCollectionTest::test_can_export_from_lazy_collection":0.01,"Maatwebsite\\Excel\\Tests\\Concerns\\FromCollectionTest::test_can_export_from_lazy_collection_with_queue":0.04,"Maatwebsite\\Excel\\Tests\\Concerns\\FromGeneratorTest::test_can_export_from_generator":0.01,"Maatwebsite\\Excel\\Tests\\Concerns\\FromIteratorTest::test_can_export_from_iterator":0.01,"Maatwebsite\\Excel\\Tests\\Concerns\\FromQueryTest::test_can_export_from_query":0.115,"Maatwebsite\\Excel\\Tests\\Concerns\\FromQueryTest::test_can_export_from_relation_query_queued":0.183,"Maatwebsite\\Excel\\Tests\\Concerns\\FromQueryTest::test_can_export_from_query_with_eager_loads":0.085,"Maatwebsite\\Excel\\Tests\\Concerns\\FromQueryTest::test_can_export_from_query_with_eager_loads_and_queued":0.106,"Maatwebsite\\Excel\\Tests\\Concerns\\FromQueryTest::test_can_export_from_query_builder_without_using_eloquent":0.07,"Maatwebsite\\Excel\\Tests\\Concerns\\FromQueryTest::test_can_export_from_query_builder_without_using_eloquent_and_queued":0.149,"Maatwebsite\\Excel\\Tests\\Concerns\\FromQueryTest::test_can_export_from_query_builder_with_nested_arrays":0.07,"Maatwebsite\\Excel\\Tests\\Concerns\\FromQueryTest::test_can_export_from_query_builder_with_nested_arrays_queued":0.09,"Maatwebsite\\Excel\\Tests\\Concerns\\FromQueryTest::test_can_export_from_query_with_batch_caching":0.116,"Maatwebsite\\Excel\\Tests\\Concerns\\FromQueryTest::test_can_export_from_query_with_prepare_rows":0.117,"Maatwebsite\\Excel\\Tests\\Concerns\\FromQueryTest::test_can_export_from_scout":0.144,"Maatwebsite\\Excel\\Tests\\Concerns\\FromViewTest::test_can_export_from_view":0.04,"Maatwebsite\\Excel\\Tests\\Concerns\\FromViewTest::test_can_export_multiple_sheets_from_view":0.101,"Maatwebsite\\Excel\\Tests\\Concerns\\ImportableTest::test_can_import_a_simple_xlsx_file":0.008,"Maatwebsite\\Excel\\Tests\\Concerns\\ImportableTest::test_can_import_a_simple_xlsx_file_from_uploaded_file":0.01,"Maatwebsite\\Excel\\Tests\\Concerns\\ImportableTest::test_can_import_a_simple_csv_file_with_html_tags_inside":0.011,"Maatwebsite\\Excel\\Tests\\Concerns\\ImportableTest::test_can_import_a_simple_xlsx_file_with_ignore_empty_set_to_true":0.005,"Maatwebsite\\Excel\\Tests\\Concerns\\ImportableTest::test_can_import_a_simple_xlsx_file_with_ignore_empty_set_to_false":0.005,"Maatwebsite\\Excel\\Tests\\Concerns\\OnEachRowTest::test_can_import_each_row_individually":0.007,"Maatwebsite\\Excel\\Tests\\Concerns\\OnEachRowTest::test_it_respects_the_end_column":0.004,"Maatwebsite\\Excel\\Tests\\Concerns\\RegistersEventListenersTest::test_events_get_called_when_exporting":0.016,"Maatwebsite\\Excel\\Tests\\Concerns\\RegistersEventListenersTest::test_events_get_called_when_importing":0.004,"Maatwebsite\\Excel\\Tests\\Concerns\\RegistersEventListenersTest::test_can_have_invokable_class_as_listener":0.01,"Maatwebsite\\Excel\\Tests\\Concerns\\RemembersChunkOffsetTest::test_can_set_and_get_chunk_offset":0,"Maatwebsite\\Excel\\Tests\\Concerns\\RemembersChunkOffsetTest::test_can_access_chunk_offset_on_import_to_array_in_chunks":0.121,"Maatwebsite\\Excel\\Tests\\Concerns\\RemembersRowNumberTest::test_can_set_and_get_row_number":0,"Maatwebsite\\Excel\\Tests\\Concerns\\RemembersRowNumberTest::test_can_access_row_number_on_import_to_model":0.145,"Maatwebsite\\Excel\\Tests\\Concerns\\RemembersRowNumberTest::test_can_access_row_number_on_import_to_array_in_chunks":0.086,"Maatwebsite\\Excel\\Tests\\Concerns\\RemembersRowNumberTest::test_can_access_row_number_on_import_to_array_in_chunks_with_batch_inserts":0.078,"ShouldQueueWithoutChainTest::test_can_import_to_model_in_chunks":0.065,"ShouldQueueWithoutChainTest::test_can_import_to_model_without_job_chaining":0.046,"ShouldQueueWithoutChainTest::test_a_queue_name_can_be_specified_when_importing":0.051,"ShouldQueueWithoutChainTest::test_the_cleanup_only_runs_when_all_jobs_are_done":0.061,"Maatwebsite\\Excel\\Tests\\Concerns\\SkipsEmptyRowsTest::test_skips_empty_rows_when_importing_to_collection":0.006,"Maatwebsite\\Excel\\Tests\\Concerns\\SkipsEmptyRowsTest::test_skips_empty_rows_when_importing_on_each_row":0.003,"Maatwebsite\\Excel\\Tests\\Concerns\\SkipsEmptyRowsTest::test_skips_empty_rows_when_importing_to_model":0.004,"Maatwebsite\\Excel\\Tests\\Concerns\\SkipsEmptyRowsTest::test_custom_skips_rows_when_importing_to_collection":0.004,"Maatwebsite\\Excel\\Tests\\Concerns\\SkipsEmptyRowsTest::test_custom_skips_rows_when_importing_to_model":0.004,"Maatwebsite\\Excel\\Tests\\Concerns\\SkipsEmptyRowsTest::test_custom_skips_rows_when_using_oneachrow":0.005,"Maatwebsite\\Excel\\Tests\\Concerns\\SkipsOnErrorTest::test_can_skip_on_error":0.042,"Maatwebsite\\Excel\\Tests\\Concerns\\SkipsOnErrorTest::test_can_skip_errors_and_collect_all_errors_at_the_end":0.037,"Maatwebsite\\Excel\\Tests\\Concerns\\SkipsOnFailureTest::test_can_skip_on_error":0.038,"Maatwebsite\\Excel\\Tests\\Concerns\\SkipsOnFailureTest::test_skips_only_failed_rows_in_batch":0.027,"Maatwebsite\\Excel\\Tests\\Concerns\\SkipsOnFailureTest::test_can_skip_failures_and_collect_all_failures_at_the_end":0.028,"Maatwebsite\\Excel\\Tests\\Concerns\\SkipsOnFailureTest::test_can_validate_using_oneachrow_and_skipsonfailure":0.028,"Maatwebsite\\Excel\\Tests\\Concerns\\SkipsOnFailureTest::test_can_validate_using_tocollection_and_skipsonfailure":0.028,"Maatwebsite\\Excel\\Tests\\Concerns\\ToArrayTest::test_can_import_to_array":0.004,"Maatwebsite\\Excel\\Tests\\Concerns\\ToArrayTest::test_can_import_multiple_sheets_to_array":0.004,"Maatwebsite\\Excel\\Tests\\Concerns\\ToCollectionTest::test_can_import_to_collection":0.004,"Maatwebsite\\Excel\\Tests\\Concerns\\ToCollectionTest::test_can_import_multiple_sheets_to_collection":0.003,"Maatwebsite\\Excel\\Tests\\Concerns\\ToModelTest::test_can_import_each_row_to_model":0.064,"Maatwebsite\\Excel\\Tests\\Concerns\\ToModelTest::test_has_timestamps_when_imported_single_model":0.055,"Maatwebsite\\Excel\\Tests\\Concerns\\ToModelTest::test_can_import_multiple_models_in_single_to_model":0.059,"Maatwebsite\\Excel\\Tests\\Concerns\\ToModelTest::test_can_import_multiple_different_types_of_models_in_single_to_model":0.055,"Maatwebsite\\Excel\\Tests\\Concerns\\ToModelTest::test_can_import_models_with_belongs_to_relations":0.068,"Maatwebsite\\Excel\\Tests\\Concerns\\ToModelTest::test_can_import_models_with_belongs_to_many_relations":0.076,"Maatwebsite\\Excel\\Tests\\Concerns\\WithBackgroundColorTest::test_can_configure_background_color_from_rgb_string":0.017,"Maatwebsite\\Excel\\Tests\\Concerns\\WithBackgroundColorTest::test_can_configure_background_color_as_array":0.009,"Maatwebsite\\Excel\\Tests\\Concerns\\WithBackgroundColorTest::test_can_configure_background_color_with_color_instance":0.011,"Maatwebsite\\Excel\\Tests\\Concerns\\WithBatchInsertsTest::test_can_import_to_model_in_batches":0.059,"Maatwebsite\\Excel\\Tests\\Concerns\\WithBatchInsertsTest::test_can_import_to_model_in_batches_bigger_file":0.278,"Maatwebsite\\Excel\\Tests\\Concerns\\WithBatchInsertsTest::test_can_import_multiple_different_types_of_models_in_single_to_model":0.054,"Maatwebsite\\Excel\\Tests\\Concerns\\WithBatchInsertsTest::test_has_timestamps_when_imported_in_batches":0.058,"Maatwebsite\\Excel\\Tests\\Concerns\\WithCalculatedFormulasTest::test_by_default_does_not_calculate_formulas":0.007,"Maatwebsite\\Excel\\Tests\\Concerns\\WithCalculatedFormulasTest::test_can_import_to_array_with_calculated_formulas":0.006,"Maatwebsite\\Excel\\Tests\\Concerns\\WithCalculatedFormulasTest::test_can_import_to_model_with_calculated_formulas":0.006,"Maatwebsite\\Excel\\Tests\\Concerns\\WithCalculatedFormulasTest::test_can_import_to_array_with_calculated_formulas_and_multi_sheet_references":0.009,"Maatwebsite\\Excel\\Tests\\Concerns\\WithCalculatedFormulasTest::test_can_import_to_array_with_calculated_formulas_and_skips_empty":0.004,"Maatwebsite\\Excel\\Tests\\Concerns\\WithCalculatedFormulasTest::test_can_import_to_model_with_calculated_formulas_and_skips_empty":0.005,"Maatwebsite\\Excel\\Tests\\Concerns\\WithChunkReadingTest::test_can_import_to_model_in_chunks_un":0.064,"Maatwebsite\\Excel\\Tests\\Concerns\\WithChunkReadingTest::test_can_import_to_model_in_chunks_and_insert_in_batches":0.438,"Maatwebsite\\Excel\\Tests\\Concerns\\WithChunkReadingTest::test_can_import_to_model_in_chunks_and_insert_in_batches_with_heading_row":0.441,"Maatwebsite\\Excel\\Tests\\Concerns\\WithChunkReadingTest::test_can_import_csv_in_chunks_and_insert_in_batches":1.218,"Maatwebsite\\Excel\\Tests\\Concerns\\WithChunkReadingTest::test_can_import_to_model_in_chunks_and_insert_in_batches_with_multiple_sheets":1.254,"Maatwebsite\\Excel\\Tests\\Concerns\\WithChunkReadingTest::test_can_import_to_array_in_chunks":1.882,"Maatwebsite\\Excel\\Tests\\Concerns\\WithChunkReadingTest::test_can_import_to_model_in_chunks_and_insert_in_batches_with_multiple_sheets_objects_by_index":1.222,"Maatwebsite\\Excel\\Tests\\Concerns\\WithChunkReadingTest::test_can_import_to_model_in_chunks_and_insert_in_batches_with_multiple_sheets_objects_by_name":1.219,"Maatwebsite\\Excel\\Tests\\Concerns\\WithChunkReadingTest::test_can_catch_job_failed_in_chunks":0.048,"Maatwebsite\\Excel\\Tests\\Concerns\\WithChunkReadingTest::test_can_import_to_array_and_format_in_chunks":0.051,"Maatwebsite\\Excel\\Tests\\Concerns\\WithChunkReadingTest::test_can_import_to_array_in_chunks_without_formatting":0.088,"Maatwebsite\\Excel\\Tests\\Concerns\\WithColumnFormattingTest::test_can_export_with_column_formatting":0.018,"Maatwebsite\\Excel\\Tests\\Concerns\\WithColumnLimitTest::test_can_import_to_array_with_column_limit":0.026,"Maatwebsite\\Excel\\Tests\\Concerns\\WithColumnLimitTest::test_can_import_to_array_with_column_limit_and_skips_empty_rows":0.032,"Maatwebsite\\Excel\\Tests\\Concerns\\WithColumnWidthsTest::test_can_set_column_width":0.016,"Maatwebsite\\Excel\\Tests\\Concerns\\WithConditionalSheetsTest::test_can_select_which_sheets_will_be_imported":0.013,"Maatwebsite\\Excel\\Tests\\Concerns\\WithCustomCsvSettingsTest::test_can_store_csv_export_with_custom_settings":0.003,"Maatwebsite\\Excel\\Tests\\Concerns\\WithCustomCsvSettingsTest::test_can_store_csv_export_with_custom_encoding":0.003,"Maatwebsite\\Excel\\Tests\\Concerns\\WithCustomCsvSettingsTest::test_can_read_csv_with_auto_detecting_delimiter_semicolon":0.005,"Maatwebsite\\Excel\\Tests\\Concerns\\WithCustomCsvSettingsTest::test_can_read_csv_with_auto_detecting_delimiter_comma":0.003,"Maatwebsite\\Excel\\Tests\\Concerns\\WithCustomCsvSettingsTest::test_can_read_csv_import_with_custom_settings":0.003,"Maatwebsite\\Excel\\Tests\\Concerns\\WithCustomCsvSettingsTest::test_cannot_read_with_wrong_delimiter":0.003,"Maatwebsite\\Excel\\Tests\\Concerns\\WithCustomQuerySizeTest::test_can_export_with_custom_count":0.09,"Maatwebsite\\Excel\\Tests\\Concerns\\WithCustomStartCellTest::test_can_store_collection_with_custom_start_cell":0.005,"Maatwebsite\\Excel\\Tests\\Concerns\\WithCustomValueBinderTest::test_can_set_a_value_binder_on_export":0.017,"Maatwebsite\\Excel\\Tests\\Concerns\\WithCustomValueBinderTest::test_can_set_a_value_binder_on_import":0.005,"Maatwebsite\\Excel\\Tests\\Concerns\\WithDefaultStylesTest::test_can_configure_default_styles":0.015,"Maatwebsite\\Excel\\Tests\\Concerns\\WithEventsTest::test_export_events_get_called":0.011,"Maatwebsite\\Excel\\Tests\\Concerns\\WithEventsTest::test_import_events_get_called":0.005,"Maatwebsite\\Excel\\Tests\\Concerns\\WithEventsTest::test_import_chunked_events_get_called":0.093,"Maatwebsite\\Excel\\Tests\\Concerns\\WithEventsTest::test_can_have_invokable_class_as_listener":0.01,"Maatwebsite\\Excel\\Tests\\Concerns\\WithEventsTest::test_can_have_global_event_listeners":0.009,"Maatwebsite\\Excel\\Tests\\Concerns\\WithEventsTest::test_can_have_custom_concern_handlers":0.036,"Maatwebsite\\Excel\\Tests\\Concerns\\WithEventsTest::test_can_have_custom_sheet_concern_handlers":0.019,"Maatwebsite\\Excel\\Tests\\Concerns\\WithEventsTest::test_export_chunked_events_get_called":0.002,"Maatwebsite\\Excel\\Tests\\Concerns\\WithFormatDataTest::test_by_default_import_to_array":0.004,"Maatwebsite\\Excel\\Tests\\Concerns\\WithFormatDataTest::test_can_import_to_array_with_format_data":0.006,"Maatwebsite\\Excel\\Tests\\Concerns\\WithFormatDataTest::test_can_import_to_array_with_format_data_and_skips_empty_rows":0.004,"Maatwebsite\\Excel\\Tests\\Concerns\\WithFormatDataTest::test_by_default_import_to_collection":0.004,"Maatwebsite\\Excel\\Tests\\Concerns\\WithFormatDataTest::test_can_import_to_collection_with_format_data":0.005,"Maatwebsite\\Excel\\Tests\\Concerns\\WithFormatDataTest::test_by_default_import_to_model":0.005,"Maatwebsite\\Excel\\Tests\\Concerns\\WithFormatDataTest::test_can_import_to_model_with_format_data":0.005,"Maatwebsite\\Excel\\Tests\\Concerns\\WithGroupedHeadingRowTest::test_can_import_to_array_with_grouped_headers":0.049,"Maatwebsite\\Excel\\Tests\\Concerns\\WithGroupedHeadingRowTest::test_can_import_oneachrow_with_grouped_headers":0.047,"Maatwebsite\\Excel\\Tests\\Concerns\\WithGroupedHeadingRowTest::test_can_import_to_collection_with_grouped_headers":0.049,"Maatwebsite\\Excel\\Tests\\Concerns\\WithGroupedHeadingRowTest::test_can_import_each_row_to_model_with_grouped_headers":0.068,"Maatwebsite\\Excel\\Tests\\Concerns\\WithHeadingRowTest::test_can_import_each_row_to_model_with_heading_row":0.029,"Maatwebsite\\Excel\\Tests\\Concerns\\WithHeadingRowTest::test_can_import_each_row_to_model_with_different_heading_row":0.032,"Maatwebsite\\Excel\\Tests\\Concerns\\WithHeadingRowTest::test_can_import_to_array_with_heading_row":0.026,"Maatwebsite\\Excel\\Tests\\Concerns\\WithHeadingRowTest::test_can_import_empty_rows_with_header":0.03,"Maatwebsite\\Excel\\Tests\\Concerns\\WithHeadingRowTest::test_can_import_empty_models_with_header":0.029,"Maatwebsite\\Excel\\Tests\\Concerns\\WithHeadingRowTest::test_can_cast_empty_headers_to_indexed_int":0.025,"Maatwebsite\\Excel\\Tests\\Concerns\\WithHeadingsTest::test_can_export_from_collection_with_heading_row":0.017,"Maatwebsite\\Excel\\Tests\\Concerns\\WithHeadingsTest::test_can_export_from_collection_with_multiple_heading_rows":0.011,"Maatwebsite\\Excel\\Tests\\Concerns\\WithHeadingsTest::test_can_export_from_collection_with_heading_row_with_custom_start_cell":0.01,"Maatwebsite\\Excel\\Tests\\Concerns\\WithLimitTest::test_can_import_a_limited_section_of_rows_to_model_with_different_start_row":0.033,"Maatwebsite\\Excel\\Tests\\Concerns\\WithLimitTest::test_can_import_to_array_with_limit":0.026,"Maatwebsite\\Excel\\Tests\\Concerns\\WithLimitTest::test_can_set_limit_bigger_than_row_size":0.025,"Maatwebsite\\Excel\\Tests\\Concerns\\WithMappedCellsTest::test_can_import_with_references_to_cells":0.026,"Maatwebsite\\Excel\\Tests\\Concerns\\WithMappedCellsTest::test_can_import_with_nested_references_to_cells":0.026,"Maatwebsite\\Excel\\Tests\\Concerns\\WithMappedCellsTest::test_can_import_with_references_to_cells_to_model":0.028,"Maatwebsite\\Excel\\Tests\\Concerns\\WithMappingTest::test_can_export_with_heading":0.018,"Maatwebsite\\Excel\\Tests\\Concerns\\WithMappingTest::test_can_return_multiple_rows_in_map":0.017,"Maatwebsite\\Excel\\Tests\\Concerns\\WithMappingTest::test_json_array_columns_shouldnt_be_detected_as_multiple_rows":0.011,"Maatwebsite\\Excel\\Tests\\Concerns\\WithMultipleSheetsTest::test_can_export_with_multiple_sheets_using_collections":0.143,"Maatwebsite\\Excel\\Tests\\Concerns\\WithMultipleSheetsTest::test_can_export_multiple_sheets_from_view":0.11,"Maatwebsite\\Excel\\Tests\\Concerns\\WithMultipleSheetsTest::test_unknown_sheet_index_will_throw_sheet_not_found_exception":0.004,"Maatwebsite\\Excel\\Tests\\Concerns\\WithMultipleSheetsTest::test_unknown_sheet_name_will_throw_sheet_not_found_exception":0.003,"Maatwebsite\\Excel\\Tests\\Concerns\\WithMultipleSheetsTest::test_unknown_sheet_name_can_be_ignored":0.003,"Maatwebsite\\Excel\\Tests\\Concerns\\WithMultipleSheetsTest::test_unknown_sheet_indices_can_be_ignored_per_name":0.003,"Maatwebsite\\Excel\\Tests\\Concerns\\WithMultipleSheetsTest::test_unknown_sheet_indices_can_be_ignored":0.004,"Maatwebsite\\Excel\\Tests\\Concerns\\WithMultipleSheetsTest::test_unknown_sheet_indices_can_be_ignored_per_sheet":0.004,"Maatwebsite\\Excel\\Tests\\Concerns\\WithMultipleSheetsTest::test_can_import_multiple_sheets":0.004,"Maatwebsite\\Excel\\Tests\\Concerns\\WithMultipleSheetsTest::test_can_import_multiple_sheets_by_sheet_name":0.013,"Maatwebsite\\Excel\\Tests\\Concerns\\WithMultipleSheetsTest::test_can_import_multiple_sheets_by_sheet_index_and_name":0.004,"Maatwebsite\\Excel\\Tests\\Concerns\\WithMultipleSheetsTest::test_can_import_multiple_sheets_by_sheet_name_and_index":0.004,"Maatwebsite\\Excel\\Tests\\Concerns\\WithPropertiesTest::test_can_set_custom_document_properties":0.009,"Maatwebsite\\Excel\\Tests\\Concerns\\WithPropertiesTest::test_it_merges_with_default_properties":0.009,"Maatwebsite\\Excel\\Tests\\Concerns\\WithPropertiesTest::test_it_ignores_empty_properties":0.011,"Maatwebsite\\Excel\\Tests\\Concerns\\WithReadFilterTest::test_can_register_custom_read_filter":0.006,"Maatwebsite\\Excel\\Tests\\Concerns\\WithStartRowTest::test_can_import_each_row_to_model_with_different_start_row":0.027,"Maatwebsite\\Excel\\Tests\\Concerns\\WithStartRowTest::test_can_import_to_array_with_start_row":0.04,"Maatwebsite\\Excel\\Tests\\Concerns\\WithStrictNullComparisonTest::test_exported_zero_values_are_not_null_when_exporting_with_strict_null_comparison":0.015,"Maatwebsite\\Excel\\Tests\\Concerns\\WithStrictNullComparisonTest::test_exported_zero_values_are_null_when_not_exporting_with_strict_null_comparison":0.012,"Maatwebsite\\Excel\\Tests\\Concerns\\WithStrictNullComparisonTest::test_exports_trailing_empty_cells":0.004,"Maatwebsite\\Excel\\Tests\\Concerns\\WithStrictNullComparisonTest::test_exports_trailing_empty_cells_by_setting_config_strict_null_comparison":0.004,"Maatwebsite\\Excel\\Tests\\Concerns\\WithStylesTest::test_can_configure_styles":0.017,"Maatwebsite\\Excel\\Tests\\Concerns\\WithTitleTest::test_can_export_with_title":0.01,"Maatwebsite\\Excel\\Tests\\Concerns\\WithTitleTest::test_can_export_sheet_title_when_longer_than_max_length":0.011,"Maatwebsite\\Excel\\Tests\\Concerns\\WithUpsertsTest::test_can_upsert_models_in_batches":0.029,"Maatwebsite\\Excel\\Tests\\Concerns\\WithUpsertsTest::test_can_upsert_models_in_rows":0.041,"Maatwebsite\\Excel\\Tests\\Concerns\\WithUpsertsTest::test_can_upsert_models_in_batches_with_defined_upsert_columns":0.029,"Maatwebsite\\Excel\\Tests\\Concerns\\WithUpsertsTest::test_can_upsert_models_in_rows_with_defined_upsert_columns":0.028,"Maatwebsite\\Excel\\Tests\\Concerns\\WithValidationTest::test_can_validate_rows":0.051,"Maatwebsite\\Excel\\Tests\\Concerns\\WithValidationTest::test_can_validate_rows_with_closure_validation_rules":0.051,"Maatwebsite\\Excel\\Tests\\Concerns\\WithValidationTest::test_can_validate_rows_with_custom_validation_rule_objects":0.052,"Maatwebsite\\Excel\\Tests\\Concerns\\WithValidationTest::test_can_validate_rows_with_conditionality":0.049,"Maatwebsite\\Excel\\Tests\\Concerns\\WithValidationTest::test_can_validate_rows_with_unless_conditionality":0.052,"Maatwebsite\\Excel\\Tests\\Concerns\\WithValidationTest::test_can_validate_rows_with_combined_rules_with_colons":0.069,"Maatwebsite\\Excel\\Tests\\Concerns\\WithValidationTest::test_can_validate_with_custom_attributes":0.049,"Maatwebsite\\Excel\\Tests\\Concerns\\WithValidationTest::test_can_validate_with_custom_message":0.051,"Maatwebsite\\Excel\\Tests\\Concerns\\WithValidationTest::test_can_validate_rows_with_headings":0.055,"Maatwebsite\\Excel\\Tests\\Concerns\\WithValidationTest::test_can_validate_rows_with_grouped_headings":0.049,"Maatwebsite\\Excel\\Tests\\Concerns\\WithValidationTest::test_can_validate_rows_in_batches":0.05,"Maatwebsite\\Excel\\Tests\\Concerns\\WithValidationTest::test_can_validate_using_oneachrow":0.051,"Maatwebsite\\Excel\\Tests\\Concerns\\WithValidationTest::test_can_validate_using_collection":0.051,"Maatwebsite\\Excel\\Tests\\Concerns\\WithValidationTest::test_can_validate_using_array":0.048,"Maatwebsite\\Excel\\Tests\\Concerns\\WithValidationTest::test_can_configure_validator":0.057,"Maatwebsite\\Excel\\Tests\\Concerns\\WithValidationTest::test_can_prepare_using_toarray":0.05,"Maatwebsite\\Excel\\Tests\\Concerns\\WithValidationTest::test_can_prepare_using_tocollection":0.051,"Maatwebsite\\Excel\\Tests\\Concerns\\WithValidationTest::test_can_prepare_using_tomodel":0.051,"Maatwebsite\\Excel\\Tests\\Concerns\\WithValidationTest::test_can_prepare_using_oneachrow":0.05,"Maatwebsite\\Excel\\Tests\\Concerns\\WithValidationTest::test_can_prepare_using_skipsemptyrows":0.049,"Maatwebsite\\Excel\\Tests\\DelegatedMacroableTest::test_can_call_methods_from_delegate":0.009,"Maatwebsite\\Excel\\Tests\\DelegatedMacroableTest::test_can_use_writer_macros":0.01,"Maatwebsite\\Excel\\Tests\\DelegatedMacroableTest::test_can_use_sheet_macros":0.008,"Maatwebsite\\Excel\\Tests\\ExcelFakeTest::test_can_fake_an_export":0.001,"Maatwebsite\\Excel\\Tests\\ExcelFakeTest::test_can_assert_against_a_fake_downloaded_export":0.001,"Maatwebsite\\Excel\\Tests\\ExcelFakeTest::test_can_assert_against_a_fake_stored_export":0.001,"Maatwebsite\\Excel\\Tests\\ExcelFakeTest::test_can_assert_regex_against_a_fake_stored_export_with_multiple_files":0.001,"Maatwebsite\\Excel\\Tests\\ExcelFakeTest::test_a_callback_can_be_passed_as_the_second_argument_when_asserting_against_a_faked_stored_export":0,"Maatwebsite\\Excel\\Tests\\ExcelFakeTest::test_can_assert_against_a_fake_queued_export":0.001,"Maatwebsite\\Excel\\Tests\\ExcelFakeTest::test_can_assert_against_a_fake_implicitly_queued_export":0,"Maatwebsite\\Excel\\Tests\\ExcelFakeTest::test_can_assert_against_a_fake_queued_export_with_chain":0.001,"Maatwebsite\\Excel\\Tests\\ExcelFakeTest::test_can_assert_against_a_fake_raw_export":0.001,"Maatwebsite\\Excel\\Tests\\ExcelFakeTest::test_can_assert_against_a_fake_import":0.004,"Maatwebsite\\Excel\\Tests\\ExcelFakeTest::test_can_assert_against_a_fake_import_with_uploaded_file":0.001,"Maatwebsite\\Excel\\Tests\\ExcelFakeTest::test_can_assert_against_a_fake_queued_import":0.001,"Maatwebsite\\Excel\\Tests\\ExcelFakeTest::test_can_assert_against_a_fake_implicitly_queued_import":0.001,"Maatwebsite\\Excel\\Tests\\ExcelFakeTest::test_can_assert_against_a_fake_queued_import_with_chain":0.001,"Maatwebsite\\Excel\\Tests\\ExcelFakeTest::test_a_callback_can_be_passed_as_the_second_argument_when_asserting_against_a_faked_queued_export":0,"Maatwebsite\\Excel\\Tests\\ExcelServiceProviderTest::test_custom_transaction_handler_is_bound":0,"Maatwebsite\\Excel\\Tests\\ExcelServiceProviderTest::test_is_bound":0,"Maatwebsite\\Excel\\Tests\\ExcelServiceProviderTest::test_has_aliased":0,"Maatwebsite\\Excel\\Tests\\ExcelServiceProviderTest::test_registers_console_commands":0.008,"Maatwebsite\\Excel\\Tests\\ExcelServiceProviderTest::test_sets_php_spreadsheet_settings":0.001,"Maatwebsite\\Excel\\Tests\\ExcelTest::test_can_download_an_export_object_with_facade":0.008,"Maatwebsite\\Excel\\Tests\\ExcelTest::test_can_download_an_export_object":0.007,"Maatwebsite\\Excel\\Tests\\ExcelTest::test_can_store_an_export_object_on_default_disk":0.01,"Maatwebsite\\Excel\\Tests\\ExcelTest::test_can_store_an_export_object_on_another_disk":0.012,"Maatwebsite\\Excel\\Tests\\ExcelTest::test_can_store_csv_export_with_default_settings":0.001,"Maatwebsite\\Excel\\Tests\\ExcelTest::test_can_get_raw_export_contents":0.012,"Maatwebsite\\Excel\\Tests\\ExcelTest::test_can_store_tsv_export_with_default_settings":0.002,"Maatwebsite\\Excel\\Tests\\ExcelTest::test_can_store_csv_export_with_custom_settings":0.002,"Maatwebsite\\Excel\\Tests\\ExcelTest::test_cannot_use_from_collection_and_from_view_on_same_export":0.002,"Maatwebsite\\Excel\\Tests\\ExcelTest::test_can_import_a_simple_xlsx_file_to_array":0.003,"Maatwebsite\\Excel\\Tests\\ExcelTest::test_can_import_a_simple_xlsx_file_to_collection":0.003,"Maatwebsite\\Excel\\Tests\\ExcelTest::test_can_import_a_simple_xlsx_file_to_collection_without_import_object":0.003,"Maatwebsite\\Excel\\Tests\\ExcelTest::test_can_import_a_simple_xlsx_file":0.003,"Maatwebsite\\Excel\\Tests\\ExcelTest::test_can_import_a_tsv_file":0.009,"Maatwebsite\\Excel\\Tests\\ExcelTest::test_can_chain_imports":0.003,"Maatwebsite\\Excel\\Tests\\ExcelTest::test_can_import_a_simple_xlsx_file_from_uploaded_file":0.005,"Maatwebsite\\Excel\\Tests\\ExcelTest::test_can_import_a_simple_xlsx_file_from_real_path":0.005,"Maatwebsite\\Excel\\Tests\\ExcelTest::test_import_will_throw_error_when_no_reader_type_could_be_detected_when_no_extension":0.001,"Maatwebsite\\Excel\\Tests\\ExcelTest::test_import_will_throw_error_when_no_reader_type_could_be_detected_with_unknown_extension":0.035,"Maatwebsite\\Excel\\Tests\\ExcelTest::test_can_import_without_extension_with_explicit_reader_type":0.004,"Maatwebsite\\Excel\\Tests\\HeadingRowImportTest::test_can_import_only_heading_row_with_custom_heading_row_formatter":0.003,"Maatwebsite\\Excel\\Tests\\HeadingRowImportTest::test_can_import_only_heading_row_with_custom_heading_row_formatter_with_key":0.003,"Maatwebsite\\Excel\\Tests\\HeadingRowImportTest::test_can_import_only_heading_row_with_custom_row_number":0.005,"Maatwebsite\\Excel\\Tests\\HeadingRowImportTest::test_can_import_only_heading_row_for_multiple_sheets":0.003,"Maatwebsite\\Excel\\Tests\\HeadingRowImportTest::test_can_import_only_heading_row_for_multiple_sheets_with_key":0.005,"Maatwebsite\\Excel\\Tests\\HeadingRowImportTest::test_can_import_only_heading_row_for_multiple_sheets_with_custom_row_number":0.004,"Maatwebsite\\Excel\\Tests\\HeadingRowImportTest::test_can_import_heading_row_with_custom_formatter_defined_in_config":0.004,"Maatwebsite\\Excel\\Tests\\InteractsWithQueueTest::test_read_chunk_job_can_interact_with_queue":0.001,"Maatwebsite\\Excel\\Tests\\InteractsWithQueueTest::test_append_data_to_sheet_job_can_interact_with_queue":0,"Maatwebsite\\Excel\\Tests\\InteractsWithQueueTest::test_append_query_to_sheet_job_can_interact_with_queue":0.001,"Maatwebsite\\Excel\\Tests\\InteractsWithQueueTest::test_append_view_to_sheet_job_can_interact_with_queue":0.001,"Maatwebsite\\Excel\\Tests\\Mixins\\DownloadCollectionTest::test_can_download_a_collection_as_excel":0.011,"Maatwebsite\\Excel\\Tests\\Mixins\\DownloadCollectionTest::test_can_download_a_collection_with_headers_as_excel":0.011,"Maatwebsite\\Excel\\Tests\\Mixins\\DownloadCollectionTest::test_can_download_collection_with_headers_with_hidden_eloquent_attributes":0.011,"Maatwebsite\\Excel\\Tests\\Mixins\\DownloadCollectionTest::test_can_download_collection_with_headers_when_making_attributes_visible":0.011,"Maatwebsite\\Excel\\Tests\\Mixins\\DownloadCollectionTest::test_can_set_custom_response_headers":0.008,"Maatwebsite\\Excel\\Tests\\Mixins\\DownloadQueryMacroTest::test_can_download_a_query_as_excel":0.058,"Maatwebsite\\Excel\\Tests\\Mixins\\DownloadQueryMacroTest::test_can_download_a_collection_with_headers_as_excel":0.062,"Maatwebsite\\Excel\\Tests\\Mixins\\ImportAsMacroTest::test_can_import_directly_into_a_model_with_mapping":0.032,"Maatwebsite\\Excel\\Tests\\Mixins\\ImportMacroTest::test_can_import_directly_into_a_model":0.035,"Maatwebsite\\Excel\\Tests\\Mixins\\StoreCollectionTest::test_can_store_a_collection_as_excel":0.012,"Maatwebsite\\Excel\\Tests\\Mixins\\StoreCollectionTest::test_can_store_a_collection_as_excel_on_non_default_disk":0.01,"Maatwebsite\\Excel\\Tests\\Mixins\\StoreCollectionTest::test_can_store_a_collection_with_headings_as_excel":0.01,"Maatwebsite\\Excel\\Tests\\Mixins\\StoreCollectionTest::test_can_store_a_model_collection_with_headings_as_excel":0,"Maatwebsite\\Excel\\Tests\\Mixins\\StoreQueryMacroTest::test_can_download_a_query_as_excel":0.061,"Maatwebsite\\Excel\\Tests\\Mixins\\StoreQueryMacroTest::test_can_download_a_query_as_excel_on_different_disk":0.059,"Maatwebsite\\Excel\\Tests\\Mixins\\StoreQueryMacroTest::test_can_store_a_query_with_headers_as_excel":0.065,"Maatwebsite\\Excel\\Tests\\QueuedExportTest::test_can_queue_an_export":0.365,"Maatwebsite\\Excel\\Tests\\QueuedExportTest::test_can_queue_an_export_and_store_on_different_disk":0.349,"Maatwebsite\\Excel\\Tests\\QueuedExportTest::test_can_queue_export_with_remote_temp_disk":0.444,"Maatwebsite\\Excel\\Tests\\QueuedExportTest::test_can_queue_export_with_remote_temp_disk_and_prefix":0.335,"Maatwebsite\\Excel\\Tests\\QueuedExportTest::test_can_implicitly_queue_an_export":0.332,"Maatwebsite\\Excel\\Tests\\QueuedExportTest::test_can_queue_export_with_mapping_on_eloquent_models":0.029,"Maatwebsite\\Excel\\Tests\\QueuedExportTest::test_can_catch_failures":0.01,"Maatwebsite\\Excel\\Tests\\QueuedExportTest::test_can_catch_failures_on_queue_export_job":0.003,"Maatwebsite\\Excel\\Tests\\QueuedExportTest::test_can_set_locale_on_queue_export_job":0.03,"Maatwebsite\\Excel\\Tests\\QueuedExportTest::test_can_queue_export_not_flushing_the_cache":0.385,"Maatwebsite\\Excel\\Tests\\QueuedImportTest::test_cannot_queue_import_that_does_not_implement_should_queue":0.046,"Maatwebsite\\Excel\\Tests\\QueuedImportTest::test_can_queue_an_import":2.07,"Maatwebsite\\Excel\\Tests\\QueuedImportTest::test_can_queue_an_import_with_batch_cache_and_file_store":3.877,"Maatwebsite\\Excel\\Tests\\QueuedImportTest::test_can_queue_import_with_remote_temp_disk":2.219,"Maatwebsite\\Excel\\Tests\\QueuedImportTest::test_can_keep_extension_for_temp_file_on_remote_disk":2.262,"Maatwebsite\\Excel\\Tests\\QueuedImportTest::test_can_queue_import_with_remote_temp_disk_and_prefix":2.062,"Maatwebsite\\Excel\\Tests\\QueuedImportTest::test_can_automatically_delete_temp_file_on_failure_when_using_remote_disk":0.108,"Maatwebsite\\Excel\\Tests\\QueuedImportTest::test_cannot_automatically_delete_temp_file_on_failure_when_using_local_disk":0.106,"Maatwebsite\\Excel\\Tests\\QueuedImportTest::test_can_force_remote_download_and_deletion_for_each_chunk_on_queue":2.176,"Maatwebsite\\Excel\\Tests\\QueuedImportTest::test_can_define_middleware_method_on_queued_import":0.067,"Maatwebsite\\Excel\\Tests\\QueuedImportTest::test_can_define_retry_until_method_on_queued_import":0.064,"Maatwebsite\\Excel\\Tests\\QueuedImportTest::test_can_define_max_exceptions_property_on_queued_import":0.106,"Maatwebsite\\Excel\\Tests\\QueuedQueryExportTest::test_can_queue_an_export":0.205,"Maatwebsite\\Excel\\Tests\\QueuedQueryExportTest::test_can_queue_an_export_with_batch_cache_and_file_store":3.691,"Maatwebsite\\Excel\\Tests\\QueuedQueryExportTest::test_can_queue_an_export_with_mapping":0.051,"Maatwebsite\\Excel\\Tests\\QueuedQueryExportTest::test_can_queue_scout_export":0.372,"Maatwebsite\\Excel\\Tests\\QueuedViewExportTest::test_can_queue_an_export":0.138,"Maatwebsite\\Excel\\Tests\\QueuedViewExportTest::test_can_export_multiple_sheets_from_view":0.266,"Maatwebsite\\Excel\\Tests\\TemporaryFileTest::test_can_use_default_rights":0.002,"Maatwebsite\\Excel\\Tests\\TemporaryFileTest::test_can_use_dir_rights":0.001,"Maatwebsite\\Excel\\Tests\\TemporaryFileTest::test_can_use_file_rights":0.001,"Maatwebsite\\Excel\\Tests\\Concerns\\ImportableTest::test_cannot_import_a_non_existing_xlsx_file":0.001,"Maatwebsite\\Excel\\Tests\\Validators\\RowValidatorTest::test_format_rule_with_array_input":0.025,"Maatwebsite\\Excel\\Tests\\Validators\\RowValidatorTest::test_format_rule_with_object_input":0,"Maatwebsite\\Excel\\Tests\\Validators\\RowValidatorTest::test_format_rule_with_callable_input":0,"Maatwebsite\\Excel\\Tests\\Validators\\RowValidatorTest::test_format_rule_with_string_input_matching_pattern":0.006,"Maatwebsite\\Excel\\Tests\\Validators\\RowValidatorTest::test_format_rule_with_string_input_not_matching_pattern":0,"Maatwebsite\\Excel\\Tests\\Validators\\RowValidatorTest::test_format_rule_with_required_without_all":0.006,"Maatwebsite\\Excel\\Tests\\Validators\\RowValidatorTest::test_format_rule_with_required_without":0}} ================================================ FILE: .styleci.yml ================================================ preset: laravel risky: false enabled: - align_double_arrow - align_equals - concat_with_spaces - ordered_class_elements disabled: - concat_without_spaces - not_operator_with_successor_space - unalign_equals finder: not-name: - "*.md" not-path: - ".github" ================================================ FILE: CHANGELOG.md ================================================ # Changelog All notable changes to this project will be documented in this file. Please view https://github.com/SpartnerNL/Laravel-Excel/releases for the most recent changelog ## [3.1.47] - 2023-02-16 - Support Laravel 10 ### Fixed - Fix Bug Multiple drawings change the behavior of the startCell (#3865). - Allow configuring read data only in chunks as well. ## [3.1.46] - 2023-01-27 - Support Laravel 10 ## [3.1.45] - 2023-01-02 ### Added - Add support for ignoring PHP auto_detect_line_endings INI directive ### Fixed - Fix the PSR simple cache dependency mess to maintain backwards compatibility and support 3.0 of the interface. ## [3.1.44] - 2022-10-14 ### Fixed - Fix output of `WithFormatData` in combination with `SkipsEmptyRows` (#3760) ### Changed - Cast empty headings to indexed integer (#3646) - Adds `isEmptyWhen` to customize is row empty logic. (#3645) ### Fixed - Fix temporary local files not being cleaned up when setting force_resync_remote config to true (#3623) - Fix testing for multiple stored files by regex matching (#3631). - Allow `required_unless` rule (#3660) ## [3.1.40] - 2022-05-02 - Fix testing for multiple stored files by regex matching (#3631). ### Changed - Adds `WithDefaultStyles` concern to allow configuring the workbook default styles. - Adds `WithBackgroundColor` concern to allow configuring the workbook default background color. - Expose the ability to set custom response headers when exporting collections via Exportable ## [3.1.39] - 2022-04-23 ### Fixed - Fix PHP8.1 return type for Failure class (#3588) ## [3.1.38] - 2022-03-24 ### Changed - Adds concern `WithGroupedHeadingRow` to allow imported sheets to group the values of columns with the same header in an array ### Fixed - Fix for `prepareForValidation` callback not being called when using `SkipsEmptyRows` ## [3.1.37] - 2022-02-28 ### Fixed - Add `@mixin` docblock to all macroable classes to allow for IDE autocompletion of delegate classes - Fix issue with `Excel::toArray` not allowing nullable reader types for uploaded files ### Changed - Change default Csv Import to auto-detect the delimiter when not explicitly defined ## [3.1.36] - 2022-02-03 ### Fixed - Fix return type of `FromQuery::query()` ### Changed - Support Laravel 9 - Added a config setting to specify DB connection - Added a config setting to specify CSV output encoding - Added an ability to specify CSV output encoding through csvSettings ## [3.1.35] - 2022-01-04 ### Fixed - Removed cache flush for cell caching as this is already handled by PhpSpreadsheet - Fix `SkipsEmptyRows` support with the `WithColumnLimit` concern - Added range support to FormatColumn ## [3.1.34] - 2021-12-2 ### Changed - Change default local_path configuration - Fix queueImport function to be able to assert chained jobs - Skipped failure no longer persists in `ToCollection` and `ToArray`. - Fix missing InteractsWithQueue trait in AppendToSheet jobs - Add return types to `Row`'s `ArrayAccess` implementation ## [3.1.33] - 2021-08-12 ### Fixed - Make TransactionManager a singleton (#3270) - Fix Exportable disk options (#3296) - Delete temporary file after exception in import class (#3312) ## [3.1.32] - 2021-07-08 ### Added - Add assertExportedInRaw assertion (#3255) ### Fixed - Make commands detect model directory now (#3213) ## [3.1.31] - 2021-06-02 ### Added - Custom heading row formatter can use column index (#3166) - Added WithFormatData concern (#3154) ### Fixed - Create failures of rows than didn't exist but where requested in row validation - Fix Bug Formulas are not calculated when import implements WithCalculatedFormulas with SkipsEmptyRows #3127 - PhpSpreadsheet 1.18 support ## [3.1.30] - 2021-04-06 ### Added - Octane compatibility ## [3.1.29] - 2021-03-16 ### Fixed - Fix AfterImport Event not being called (#3085) ## [3.1.28] - 2021-03-10 ### Added - Added WithUpsertColumns concern (#3046) - Added ShouldQueueWithoutChain concern (#3072) ### Fixed - Limit Phpspreadsheet version to 1.16 until bugfix release - Fixed issue with not autosizing columns (#3055) - Fix selecting worksheets by name with chunk reading (#3052) ## [3.1.27] - 2021-02-22 ### Added - Added SkipsEmptyRows concern - Added HasReferencesToOtherSheets concern to allow import of calculated ### Changed - Bump minimum PhpSpreadsheet version - Progressbar NullOutput as fallback ## [3.1.26] - 2020-11-13 ### Added - PHP 8 support ## [3.1.25] - 2020-11-13 ### Added - Added an ability to prepare rows before appending rows to sheet. Just add `prepareRows` method for your export class if needed. - Added an ability to catch exceptions from `QueueExport` job. Just add `failed` method for your export class if needed. - Added an ability to set locale for queued export. Just implement `Illuminate\Contracts\Translation\HasLocalePreference` for your export. - Added `JsonSerializable` support in `Maatwebsite\Excel\Validators\Failure`. - Added `$maxExceptions` support in `Maatwebsite\Excel\Jobs\ReadChunk.php`. - Added support to upsert models by implementing the `WithUpserts` concern. ## [3.1.24] - 2020-10-28 ### Added - Added support for `prepareForValidation` on `WithValidation` concern - Added support for `withValidator` on `WithValidation` concern - Added `ArrayAccess` to `Row` ### Fixed - Corrected SkipsErrors doc block ## [3.1.23] - 2020-09-29 ### Added - Added `ignore_empty` setting to `config/excel.php` - Added `strict_null_comparison` setting to `config/excel.php` ## [3.1.22] - 2020-09-08 - Laravel 8 support - Lumen improvements ## [3.1.21] - 2020-08-06 ### Added - Added WithProperties concern - Added default spreadsheet properties config - Added WithColumnWidths concern - Added WithStyles concern. - Config setting to configure cell caching ### Changed - Sheet titles longer than 31 chars get trimmed. - Sheet titles with unsupported chars get cleaned. ### Fixed - Fixed issue with using ShouldAutosize in combination with FromView column widths. ## [3.1.20] - 2020-07-22 ### Added - Re-sycing remote temporary file - Remember row number - Remember chunk offset - WithColumnLimit concern - WithReadFilter concern - Publishing the stubs ### Changed - Interacting with queued jobs - Retry until and middleware on queued imports - Using WithValidation with FromCollection & FromArray - Read filters for WithLimit and HeadingRowImport - Bump of minimum version PhpSpreadsheet ### Fixed - Fixed test helper docblocks on the Excel facade. - Fix for importing with a start row beyond the highest row. - Fixed `BeforeSheet` and `AfterSheet` events receiving exportable instance instead of importable when calling on an Import. - Fix for value binders not working in queued exports. - Fix when using WithLimit concern when having less rows than the limit. - Fix AfterImport job being fired twice if not using queueing. - Raw() method now also available on Exportable. - Fix for breaking changes in PhpSpreadsheet with empty enclosures. [Unreleased]: https://github.com/Maatwebsite/Laravel-Excel/compare/3.1.47...HEAD [3.1.47]: https://github.com/Maatwebsite/Laravel-Excel/compare/3.1.46...3.1.47 [3.1.46]: https://github.com/Maatwebsite/Laravel-Excel/compare/3.1.45...3.1.46 [3.1.45]: https://github.com/Maatwebsite/Laravel-Excel/compare/3.1.44...3.1.45 [3.1.44]: https://github.com/Maatwebsite/Laravel-Excel/compare/3.1.43...3.1.44 [3.1.43]: https://github.com/Maatwebsite/Laravel-Excel/compare/3.1.42...3.1.43 [3.1.42]: https://github.com/Maatwebsite/Laravel-Excel/compare/3.1.41...3.1.42 [3.1.41]: https://github.com/Maatwebsite/Laravel-Excel/compare/3.1.40...3.1.41 [3.1.40]: https://github.com/Maatwebsite/Laravel-Excel/compare/3.1.39...3.1.40 [3.1.39]: https://github.com/Maatwebsite/Laravel-Excel/compare/3.1.38...3.1.39 [3.1.38]: https://github.com/Maatwebsite/Laravel-Excel/compare/3.1.37...3.1.38 [3.1.37]: https://github.com/Maatwebsite/Laravel-Excel/compare/3.1.36...3.1.37 [3.1.36]: https://github.com/Maatwebsite/Laravel-Excel/compare/3.1.35...3.1.36 [3.1.35]: https://github.com/Maatwebsite/Laravel-Excel/compare/3.1.34...3.1.35 [3.1.34]: https://github.com/Maatwebsite/Laravel-Excel/compare/3.1.33...3.1.34 [3.1.33]: https://github.com/Maatwebsite/Laravel-Excel/compare/3.1.32...3.1.33 [3.1.32]: https://github.com/Maatwebsite/Laravel-Excel/compare/3.1.31...3.1.32 [3.1.31]: https://github.com/Maatwebsite/Laravel-Excel/compare/3.1.30...3.1.31 [3.1.30]: https://github.com/Maatwebsite/Laravel-Excel/compare/3.1.29...3.1.30 [3.1.29]: https://github.com/Maatwebsite/Laravel-Excel/compare/3.1.28...3.1.29 [3.1.28]: https://github.com/Maatwebsite/Laravel-Excel/compare/3.1.27...3.1.28 [3.1.27]: https://github.com/Maatwebsite/Laravel-Excel/compare/3.1.26...3.1.27 [3.1.26]: https://github.com/Maatwebsite/Laravel-Excel/compare/3.1.25...3.1.26 [3.1.25]: https://github.com/Maatwebsite/Laravel-Excel/compare/3.1.24...3.1.25 [3.1.24]: https://github.com/Maatwebsite/Laravel-Excel/compare/3.1.23...3.1.24 [3.1.23]: https://github.com/Maatwebsite/Laravel-Excel/compare/3.1.22...3.1.23 [3.1.22]: https://github.com/Maatwebsite/Laravel-Excel/compare/3.1.21...3.1.22 [3.1.21]: https://github.com/Maatwebsite/Laravel-Excel/compare/3.1.20...3.1.21 [3.1.20]: https://github.com/Maatwebsite/Laravel-Excel/compare/3.1.19...3.1.20 _Older release notes can be found in Github releases._ ================================================ FILE: CODE_OF_CONDUCT.md ================================================ # Contributor Covenant Code of Conduct ## Our Pledge 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. ## Our Standards Examples of behavior that contributes to creating a positive environment include: * Using welcoming and inclusive language * Being respectful of differing viewpoints and experiences * Gracefully accepting constructive criticism * Focusing on what is best for the community * Showing empathy towards other community members Examples of unacceptable behavior by participants include: * The use of sexualized language or imagery and unwelcome sexual attention or advances * Trolling, insulting/derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or electronic address, without explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Our Responsibilities 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. 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. ## Scope 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. ## Enforcement 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. 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. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] [homepage]: http://contributor-covenant.org [version]: http://contributor-covenant.org/version/1/4/ ================================================ FILE: CONTRIBUTING.md ================================================ # Contributing Find the contributing guide at: https://docs.laravel-excel.com/3.1/getting-started/contributing.html ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) Spartner Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================

Laravel Excel logo


Supercharged Excel exports and imports

A simple, but elegant Laravel wrapper around PhpSpreadsheet exports and imports.

Quickstart · Documentation · Video Course · Nova · Blog · Contributing · Support

Github Actions StyleCI Latest Stable Version Total Downloads License

## ✨ Features - **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. - **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. - **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. - **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. ![banner](https://user-images.githubusercontent.com/7728097/57463977-2263fc80-727c-11e9-833d-669d816fb7fb.jpg)
## 🎓 Learning Laravel Excel You can find the full documentation of Laravel Excel [on the website](https://docs.laravel-excel.com). 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). Some articles and tutorials can be found on our blog: https://medium.com/maatwebsite/laravel-excel/home ## :mailbox_with_mail: License & Postcardware ![1_5nblgs68uarg0wxxejozdq](https://user-images.githubusercontent.com/7728097/53638144-9e5f1a00-3c25-11e9-9f4a-fc71c9d94562.jpg) 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. **Spartner** Markt 2 6231 LS Meerssen The Netherlands. 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) ## Created by Spartner (formerly Maatwebsite) 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. https://spartner.software info@spartner.nl +31 (0) 10 - 7449312 ## :wrench: Supported Versions Versions will be supported for a limited amount of time. | Version | Laravel Version | Php Version | Support | |---- |----|----|----| | 2.1 | <=5.6 | <=7.0 | Unsupported since 15-5-2018 | | 3.0 | ^5.5 | ^7.0 | Unsupported since 31-12-2018 | | 3.1 | >=5.8 \| <=12.x | ^7.2 \| ^8.0 | New features | ================================================ FILE: SECURITY.md ================================================ # Security Policy **PLEASE DON'T DISCLOSE SECURITY-RELATED ISSUES PUBLICLY, [SEE BELOW](#reporting-a-vulnerability).** ## Supported Versions Version | Security Fixes Until --- | --- 3.1 | - 3.0 | 31-12-2018 2.1 | 15-5-2018 ## Reporting a Vulnerability 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. ================================================ FILE: composer.json ================================================ { "name": "maatwebsite/excel", "description": "Supercharged Excel exports and imports in Laravel", "license": "MIT", "keywords": [ "laravel", "php", "phpspreadsheet", "phpexcel", "excel", "csv", "export", "import", "batch" ], "authors": [ { "name": "Patrick Brouwers", "email": "patrick@spartner.nl" } ], "require": { "ext-json": "*", "php": "^7.0||^8.0", "phpoffice/phpspreadsheet": "^1.30.0", "illuminate/support": "5.8.*||^6.0||^7.0||^8.0||^9.0||^10.0||^11.0||^12.0||^13.0", "psr/simple-cache": "^1.0||^2.0||^3.0", "composer/semver": "^3.3" }, "require-dev": { "orchestra/testbench": "^6.0||^7.0||^8.0||^9.0||^10.0||^11.0", "predis/predis": "^1.1", "laravel/scout": "^7.0||^8.0||^9.0||^10.0||^11.0" }, "autoload": { "psr-4": { "Maatwebsite\\Excel\\": "src/" } }, "autoload-dev": { "psr-4": { "Maatwebsite\\Excel\\Tests\\": "tests/" } }, "extra": { "laravel": { "providers": [ "Maatwebsite\\Excel\\ExcelServiceProvider" ], "aliases": { "Excel": "Maatwebsite\\Excel\\Facades\\Excel" } } }, "minimum-stability": "dev", "prefer-stable": true } ================================================ FILE: config/excel.php ================================================ [ /* |-------------------------------------------------------------------------- | Chunk size |-------------------------------------------------------------------------- | | When using FromQuery, the query is automatically chunked. | Here you can specify how big the chunk should be. | */ 'chunk_size' => 1000, /* |-------------------------------------------------------------------------- | Pre-calculate formulas during export |-------------------------------------------------------------------------- */ 'pre_calculate_formulas' => false, /* |-------------------------------------------------------------------------- | Enable strict null comparison |-------------------------------------------------------------------------- | | When enabling strict null comparison empty cells ('') will | be added to the sheet. */ 'strict_null_comparison' => false, /* |-------------------------------------------------------------------------- | CSV Settings |-------------------------------------------------------------------------- | | Configure e.g. delimiter, enclosure and line ending for CSV exports. | */ 'csv' => [ 'delimiter' => ',', 'enclosure' => '"', 'line_ending' => PHP_EOL, 'use_bom' => false, 'include_separator_line' => false, 'excel_compatibility' => false, 'output_encoding' => '', 'test_auto_detect' => true, ], /* |-------------------------------------------------------------------------- | Worksheet properties |-------------------------------------------------------------------------- | | Configure e.g. default title, creator, subject,... | */ 'properties' => [ 'creator' => '', 'lastModifiedBy' => '', 'title' => '', 'description' => '', 'subject' => '', 'keywords' => '', 'category' => '', 'manager' => '', 'company' => '', ], ], 'imports' => [ /* |-------------------------------------------------------------------------- | Read Only |-------------------------------------------------------------------------- | | When dealing with imports, you might only be interested in the | data that the sheet exists. By default we ignore all styles, | however if you want to do some logic based on style data | you can enable it by setting read_only to false. | */ 'read_only' => true, /* |-------------------------------------------------------------------------- | Ignore Empty |-------------------------------------------------------------------------- | | When dealing with imports, you might be interested in ignoring | rows that have null values or empty strings. By default rows | containing empty strings or empty values are not ignored but can be | ignored by enabling the setting ignore_empty to true. | */ 'ignore_empty' => false, /* |-------------------------------------------------------------------------- | Heading Row Formatter |-------------------------------------------------------------------------- | | Configure the heading row formatter. | Available options: none|slug|custom | */ 'heading_row' => [ 'formatter' => 'slug', ], /* |-------------------------------------------------------------------------- | CSV Settings |-------------------------------------------------------------------------- | | Configure e.g. delimiter, enclosure and line ending for CSV imports. | */ 'csv' => [ 'delimiter' => null, 'enclosure' => '"', 'escape_character' => '\\', 'contiguous' => false, 'input_encoding' => Csv::GUESS_ENCODING, ], /* |-------------------------------------------------------------------------- | Worksheet properties |-------------------------------------------------------------------------- | | Configure e.g. default title, creator, subject,... | */ 'properties' => [ 'creator' => '', 'lastModifiedBy' => '', 'title' => '', 'description' => '', 'subject' => '', 'keywords' => '', 'category' => '', 'manager' => '', 'company' => '', ], /* |-------------------------------------------------------------------------- | Cell Middleware |-------------------------------------------------------------------------- | | Configure middleware that is executed on getting a cell value | */ 'cells' => [ 'middleware' => [ //\Maatwebsite\Excel\Middleware\TrimCellValue::class, //\Maatwebsite\Excel\Middleware\ConvertEmptyCellValuesToNull::class, ], ], ], /* |-------------------------------------------------------------------------- | Extension detector |-------------------------------------------------------------------------- | | Configure here which writer/reader type should be used when the package | needs to guess the correct type based on the extension alone. | */ 'extension_detector' => [ 'xlsx' => Excel::XLSX, 'xlsm' => Excel::XLSX, 'xltx' => Excel::XLSX, 'xltm' => Excel::XLSX, 'xls' => Excel::XLS, 'xlt' => Excel::XLS, 'ods' => Excel::ODS, 'ots' => Excel::ODS, 'slk' => Excel::SLK, 'xml' => Excel::XML, 'gnumeric' => Excel::GNUMERIC, 'htm' => Excel::HTML, 'html' => Excel::HTML, 'csv' => Excel::CSV, 'tsv' => Excel::TSV, /* |-------------------------------------------------------------------------- | PDF Extension |-------------------------------------------------------------------------- | | Configure here which Pdf driver should be used by default. | Available options: Excel::MPDF | Excel::TCPDF | Excel::DOMPDF | */ 'pdf' => Excel::DOMPDF, ], /* |-------------------------------------------------------------------------- | Value Binder |-------------------------------------------------------------------------- | | PhpSpreadsheet offers a way to hook into the process of a value being | written to a cell. In there some assumptions are made on how the | value should be formatted. If you want to change those defaults, | you can implement your own default value binder. | | Possible value binders: | | [x] Maatwebsite\Excel\DefaultValueBinder::class | [x] PhpOffice\PhpSpreadsheet\Cell\StringValueBinder::class | [x] PhpOffice\PhpSpreadsheet\Cell\AdvancedValueBinder::class | */ 'value_binder' => [ 'default' => Maatwebsite\Excel\DefaultValueBinder::class, ], 'cache' => [ /* |-------------------------------------------------------------------------- | Default cell caching driver |-------------------------------------------------------------------------- | | By default PhpSpreadsheet keeps all cell values in memory, however when | dealing with large files, this might result into memory issues. If you | want to mitigate that, you can configure a cell caching driver here. | When using the illuminate driver, it will store each value in the | cache store. This can slow down the process, because it needs to | store each value. You can use the "batch" store if you want to | only persist to the store when the memory limit is reached. | | Drivers: memory|illuminate|batch | */ 'driver' => 'memory', /* |-------------------------------------------------------------------------- | Batch memory caching |-------------------------------------------------------------------------- | | When dealing with the "batch" caching driver, it will only | persist to the store when the memory limit is reached. | Here you can tweak the memory limit to your liking. | */ 'batch' => [ 'memory_limit' => 60000, ], /* |-------------------------------------------------------------------------- | Illuminate cache |-------------------------------------------------------------------------- | | When using the "illuminate" caching driver, it will automatically use | your default cache store. However if you prefer to have the cell | cache on a separate store, you can configure the store name here. | You can use any store defined in your cache config. When leaving | at "null" it will use the default store. | */ 'illuminate' => [ 'store' => null, ], /* |-------------------------------------------------------------------------- | Cache Time-to-live (TTL) |-------------------------------------------------------------------------- | | The TTL of items written to cache. If you want to keep the items cached | indefinitely, set this to null. Otherwise, set a number of seconds, | a \DateInterval, or a callable. | | Allowable types: callable|\DateInterval|int|null | */ 'default_ttl' => 10800, ], /* |-------------------------------------------------------------------------- | Transaction Handler |-------------------------------------------------------------------------- | | By default the import is wrapped in a transaction. This is useful | for when an import may fail and you want to retry it. With the | transactions, the previous import gets rolled-back. | | You can disable the transaction handler by setting this to null. | Or you can choose a custom made transaction handler here. | | Supported handlers: null|db | */ 'transactions' => [ 'handler' => 'db', 'db' => [ 'connection' => null, ], ], 'temporary_files' => [ /* |-------------------------------------------------------------------------- | Local Temporary Path |-------------------------------------------------------------------------- | | When exporting and importing files, we use a temporary file, before | storing reading or downloading. Here you can customize that path. | permissions is an array with the permission flags for the directory (dir) | and the create file (file). | */ 'local_path' => storage_path('framework/cache/laravel-excel'), /* |-------------------------------------------------------------------------- | Local Temporary Path Permissions |-------------------------------------------------------------------------- | | Permissions is an array with the permission flags for the directory (dir) | and the create file (file). | If omitted the default permissions of the filesystem will be used. | */ 'local_permissions' => [ // 'dir' => 0755, // 'file' => 0644, ], /* |-------------------------------------------------------------------------- | Remote Temporary Disk |-------------------------------------------------------------------------- | | When dealing with a multi server setup with queues in which you | cannot rely on having a shared local temporary path, you might | want to store the temporary file on a shared disk. During the | queue executing, we'll retrieve the temporary file from that | location instead. When left to null, it will always use | the local path. This setting only has effect when using | in conjunction with queued imports and exports. | */ 'remote_disk' => null, 'remote_prefix' => null, /* |-------------------------------------------------------------------------- | Force Resync |-------------------------------------------------------------------------- | | When dealing with a multi server setup as above, it's possible | for the clean up that occurs after entire queue has been run to only | cleanup the server that the last AfterImportJob runs on. The rest of the server | would still have the local temporary file stored on it. In this case your | local storage limits can be exceeded and future imports won't be processed. | To mitigate this you can set this config value to be true, so that after every | queued chunk is processed the local temporary file is deleted on the server that | processed it. | */ 'force_resync_remote' => null, ], ]; ================================================ FILE: phpunit.xml.dist ================================================ ./tests/ ./src ================================================ FILE: src/Cache/BatchCache.php ================================================ cache = $cache; $this->memory = $memory; $this->defaultTTL = $defaultTTL; } public function __sleep() { return ['memory']; } public function __wakeup() { $this->cache = Cache::driver( config('excel.cache.illuminate.store') ); } /** * {@inheritdoc} */ public function get(string $key, mixed $default = null): mixed { if ($this->memory->has($key)) { return $this->memory->get($key); } return $this->cache->get($key, $default); } /** * {@inheritdoc} */ public function set(string $key, mixed $value, null|int|\DateInterval $ttl = null): bool { if (func_num_args() === 2) { $ttl = value($this->defaultTTL); } $this->memory->set($key, $value, $ttl); if ($this->memory->reachedMemoryLimit()) { return $this->cache->setMultiple($this->memory->flush(), $ttl); } return true; } /** * {@inheritdoc} */ public function delete(string $key): bool { if ($this->memory->has($key)) { return $this->memory->delete($key); } return $this->cache->delete($key); } /** * {@inheritdoc} */ public function clear(): bool { $this->memory->clear(); return $this->cache->clear(); } /** * {@inheritdoc} */ public function getMultiple(iterable $keys, mixed $default = null): iterable { // Check if all keys are still in memory $memory = $this->memory->getMultiple($keys, $default); $actualItemsInMemory = count(array_filter($memory)); if ($actualItemsInMemory === count($keys)) { return $memory; } // Get all rows from cache if none is hold in memory. if ($actualItemsInMemory === 0) { return $this->cache->getMultiple($keys, $default); } // Add missing values from cache. foreach ($this->cache->getMultiple($keys, $default) as $key => $value) { if (null !== $value) { $memory[$key] = $value; } } return $memory; } /** * {@inheritdoc} */ public function setMultiple(iterable $values, null|int|\DateInterval $ttl = null): bool { if (func_num_args() === 1) { $ttl = value($this->defaultTTL); } $this->memory->setMultiple($values, $ttl); if ($this->memory->reachedMemoryLimit()) { return $this->cache->setMultiple($this->memory->flush(), $ttl); } return true; } /** * {@inheritdoc} */ public function deleteMultiple(iterable $keys): bool { $keys = is_array($keys) ? $keys : iterator_to_array($keys); $this->memory->deleteMultiple($keys); return $this->cache->deleteMultiple($keys); } /** * {@inheritdoc} */ public function has(string $key): bool { if ($this->memory->has($key)) { return true; } return $this->cache->has($key); } } ================================================ FILE: src/Cache/BatchCacheDeprecated.php ================================================ cache = $cache; $this->memory = $memory; $this->defaultTTL = $defaultTTL; } public function __sleep() { return ['memory']; } public function __wakeup() { $this->cache = Cache::driver( config('excel.cache.illuminate.store') ); } /** * {@inheritdoc} */ public function get($key, $default = null) { if ($this->memory->has($key)) { return $this->memory->get($key); } return $this->cache->get($key, $default); } /** * {@inheritdoc} */ public function set($key, $value, $ttl = null) { if (func_num_args() === 2) { $ttl = value($this->defaultTTL); } $this->memory->set($key, $value, $ttl); if ($this->memory->reachedMemoryLimit()) { return $this->cache->setMultiple($this->memory->flush(), $ttl); } return true; } /** * {@inheritdoc} */ public function delete($key) { if ($this->memory->has($key)) { return $this->memory->delete($key); } return $this->cache->delete($key); } /** * {@inheritdoc} */ public function clear() { $this->memory->clear(); return $this->cache->clear(); } /** * {@inheritdoc} */ public function getMultiple($keys, $default = null) { // Check if all keys are still in memory $memory = $this->memory->getMultiple($keys, $default); $actualItemsInMemory = count(array_filter($memory)); if ($actualItemsInMemory === count($keys)) { return $memory; } // Get all rows from cache if none is hold in memory. if ($actualItemsInMemory === 0) { return $this->cache->getMultiple($keys, $default); } // Add missing values from cache. foreach ($this->cache->getMultiple($keys, $default) as $key => $value) { if (null !== $value) { $memory[$key] = $value; } } return $memory; } /** * {@inheritdoc} */ public function setMultiple($values, $ttl = null) { if (func_num_args() === 1) { $ttl = value($this->defaultTTL); } $this->memory->setMultiple($values, $ttl); if ($this->memory->reachedMemoryLimit()) { return $this->cache->setMultiple($this->memory->flush(), $ttl); } return true; } /** * {@inheritdoc} */ public function deleteMultiple($keys) { $keys = is_array($keys) ? $keys : iterator_to_array($keys); $this->memory->deleteMultiple($keys); return $this->cache->deleteMultiple($keys); } /** * {@inheritdoc} */ public function has($key) { if ($this->memory->has($key)) { return true; } return $this->cache->has($key); } } ================================================ FILE: src/Cache/CacheManager.php ================================================ createIlluminateDriver(), $this->createMemoryDriver(), config('excel.cache.default_ttl') ); } return new BatchCache( $this->createIlluminateDriver(), $this->createMemoryDriver(), config('excel.cache.default_ttl') ); } /** * @return CacheInterface */ public function createIlluminateDriver(): CacheInterface { return Cache::driver( config('excel.cache.illuminate.store') ); } public function flush() { $this->driver()->clear(); } public function isInMemory(): bool { return $this->getDefaultDriver() === self::DRIVER_MEMORY; } } ================================================ FILE: src/Cache/MemoryCache.php ================================================ memoryLimit = $memoryLimit; } /** * {@inheritdoc} */ public function clear(): bool { $this->cache = []; return true; } /** * {@inheritdoc} */ public function delete(string $key): bool { unset($this->cache[$key]); return true; } /** * {@inheritdoc} */ public function deleteMultiple($keys): bool { foreach ($keys as $key) { $this->delete($key); } return true; } /** * {@inheritdoc} */ public function get(string $key, mixed $default = null): mixed { if ($this->has($key)) { return $this->cache[$key]; } return $default; } /** * {@inheritdoc} */ public function getMultiple(iterable $keys, mixed $default = null): iterable { $results = []; foreach ($keys as $key) { $results[$key] = $this->get($key, $default); } return $results; } /** * {@inheritdoc} */ public function has($key): bool { return isset($this->cache[$key]); } /** * {@inheritdoc} */ public function set(string $key, mixed $value, null|int|\DateInterval $ttl = null): bool { $this->cache[$key] = $value; return true; } /** * {@inheritdoc} */ public function setMultiple($values, $ttl = null): bool { foreach ($values as $key => $value) { $this->set($key, $value); } return true; } /** * @return bool */ public function reachedMemoryLimit(): bool { // When no limit is given, we'll never reach any limit. if (null === $this->memoryLimit) { return false; } return count($this->cache) >= $this->memoryLimit; } /** * @return array */ public function flush(): array { $memory = $this->cache; foreach ($memory as $cell) { if ($cell instanceof Cell) { $cell->detach(); } } $this->clear(); return $memory; } } ================================================ FILE: src/Cache/MemoryCacheDeprecated.php ================================================ memoryLimit = $memoryLimit; } /** * {@inheritdoc} */ public function clear() { $this->cache = []; return true; } /** * {@inheritdoc} */ public function delete($key) { unset($this->cache[$key]); return true; } /** * {@inheritdoc} */ public function deleteMultiple($keys) { foreach ($keys as $key) { $this->delete($key); } return true; } /** * {@inheritdoc} */ public function get($key, $default = null) { if ($this->has($key)) { return $this->cache[$key]; } return $default; } /** * {@inheritdoc} */ public function getMultiple($keys, $default = null) { $results = []; foreach ($keys as $key) { $results[$key] = $this->get($key, $default); } return $results; } /** * {@inheritdoc} */ public function has($key) { return isset($this->cache[$key]); } /** * {@inheritdoc} */ public function set($key, $value, $ttl = null) { $this->cache[$key] = $value; return true; } /** * {@inheritdoc} */ public function setMultiple($values, $ttl = null) { foreach ($values as $key => $value) { $this->set($key, $value); } return true; } /** * @return bool */ public function reachedMemoryLimit(): bool { // When no limit is given, we'll never reach any limit. if (null === $this->memoryLimit) { return false; } return count($this->cache) >= $this->memoryLimit; } /** * @return array */ public function flush(): array { $memory = $this->cache; foreach ($memory as $cell) { if ($cell instanceof Cell) { $cell->detach(); } } $this->clear(); return $memory; } } ================================================ FILE: src/Cell.php ================================================ cell = $cell; } /** * @param Worksheet $worksheet * @param string $coordinate * @return Cell * * @throws \PhpOffice\PhpSpreadsheet\Exception */ public static function make(Worksheet $worksheet, string $coordinate) { return new static($worksheet->getCell($coordinate)); } /** * @return SpreadsheetCell */ public function getDelegate(): SpreadsheetCell { return $this->cell; } /** * @param null $nullValue * @param bool $calculateFormulas * @param bool $formatData * @return mixed */ public function getValue($nullValue = null, $calculateFormulas = false, $formatData = true) { $value = $nullValue; if ($this->cell->getValue() !== null) { if ($this->cell->getValue() instanceof RichText) { $value = $this->cell->getValue()->getPlainText(); } elseif ($calculateFormulas) { try { $value = $this->cell->getCalculatedValue(); } catch (Exception $e) { $value = $this->cell->getOldCalculatedValue(); } } else { $value = $this->cell->getValue(); } if ($formatData) { $style = $this->cell->getWorksheet()->getParent()->getCellXfByIndex($this->cell->getXfIndex()); $value = NumberFormat::toFormattedString( $value, ($style && $style->getNumberFormat()) ? $style->getNumberFormat()->getFormatCode() : NumberFormat::FORMAT_GENERAL ); } } return app(Pipeline::class)->send($value)->through(config('excel.imports.cells.middleware', []))->thenReturn(); } } ================================================ FILE: src/ChunkReader.php ================================================ container = $container; } /** * @param WithChunkReading $import * @param Reader $reader * @param TemporaryFile $temporaryFile * @return PendingDispatch|Collection|null */ public function read(WithChunkReading $import, Reader $reader, TemporaryFile $temporaryFile) { if ($import instanceof WithEvents) { $reader->beforeImport($import); } $chunkSize = $import->chunkSize(); $totalRows = $reader->getTotalRows(); $worksheets = $reader->getWorksheets($import); $queue = property_exists($import, 'queue') ? $import->queue : null; $delayCleanup = property_exists($import, 'cleanupInterval') ? $import->cleanupInterval : 60; if ($import instanceof WithProgressBar) { $import->getConsoleOutput()->progressStart(array_sum($totalRows)); } $jobs = new Collection(); foreach ($worksheets as $name => $sheetImport) { $startRow = HeadingRowExtractor::determineStartRow($sheetImport); if ($sheetImport instanceof WithLimit) { $limit = $sheetImport->limit(); if ($limit <= $totalRows[$name]) { $totalRows[$name] = $sheetImport->limit(); } } for ($currentRow = $startRow; $currentRow <= $totalRows[$name]; $currentRow += $chunkSize) { $jobs->push(new ReadChunk( $import, $reader->getPhpSpreadsheetReader(), $temporaryFile, $name, $sheetImport, $currentRow, $chunkSize )); } } $afterImportJob = new AfterImportJob($import, $reader); if ($import instanceof ShouldQueueWithoutChain) { $afterImportJob->setInterval($delayCleanup); $afterImportJob->setDependencies($jobs); $jobs->push($afterImportJob->delay($delayCleanup)); return $jobs->each(function ($job) use ($queue) { dispatch($job->onQueue($queue)); }); } $jobs->push($afterImportJob); if ($import instanceof ShouldQueue) { return new PendingDispatch( (new QueueImport($import))->chain($jobs->toArray()) ); } $jobs->each(function ($job) { try { function_exists('dispatch_now') ? dispatch_now($job) : $this->dispatchNow($job); } catch (Throwable $e) { if (method_exists($job, 'failed')) { $job->failed($e); } throw $e; } }); if ($import instanceof WithProgressBar) { $import->getConsoleOutput()->progressFinish(); } unset($jobs); return null; } /** * Dispatch a command to its appropriate handler in the current process without using the synchronous queue. * * @param object $command * @param mixed $handler * @return mixed */ protected function dispatchNow($command, $handler = null) { $uses = class_uses_recursive($command); if (in_array(InteractsWithQueue::class, $uses) && in_array(Queueable::class, $uses) && !$command->job ) { $command->setJob(new SyncJob($this->container, json_encode([]), 'sync', 'sync')); } $method = method_exists($command, 'handle') ? 'handle' : '__invoke'; return $this->container->call([$command, $method]); } } ================================================ FILE: src/Concerns/Exportable.php ================================================ headers ?? []; $fileName = $fileName ?? $this->fileName ?? null; $writerType = $writerType ?? $this->writerType ?? null; if (null === $fileName) { throw new NoFilenameGivenException(); } return $this->getExporter()->download($this, $fileName, $writerType, $headers); } /** * @param string $filePath * @param string|null $disk * @param string|null $writerType * @param mixed $diskOptions * @return bool|PendingDispatch * * @throws NoFilePathGivenException */ public function store(?string $filePath = null, ?string $disk = null, ?string $writerType = null, $diskOptions = []) { $filePath = $filePath ?? $this->filePath ?? null; if (null === $filePath) { throw NoFilePathGivenException::export(); } return $this->getExporter()->store( $this, $filePath, $disk ?? $this->disk ?? null, $writerType ?? $this->writerType ?? null, $diskOptions ?: $this->diskOptions ?? [] ); } /** * @param string|null $filePath * @param string|null $disk * @param string|null $writerType * @param mixed $diskOptions * @return PendingDispatch * * @throws NoFilePathGivenException */ public function queue(?string $filePath = null, ?string $disk = null, ?string $writerType = null, $diskOptions = []) { $filePath = $filePath ?? $this->filePath ?? null; if (null === $filePath) { throw NoFilePathGivenException::export(); } return $this->getExporter()->queue( $this, $filePath, $disk ?? $this->disk ?? null, $writerType ?? $this->writerType ?? null, $diskOptions ?: $this->diskOptions ?? [] ); } /** * @param string|null $writerType * @return string */ public function raw($writerType = null) { $writerType = $writerType ?? $this->writerType ?? null; return $this->getExporter()->raw($this, $writerType); } /** * Create an HTTP response that represents the object. * * @param \Illuminate\Http\Request $request * @return \Illuminate\Http\Response * * @throws NoFilenameGivenException */ public function toResponse($request) { return $this->download(); } /** * @return Exporter */ private function getExporter(): Exporter { return app(Exporter::class); } } ================================================ FILE: src/Concerns/FromArray.php ================================================ getFilePath($filePath); return $this->getImporter()->import( $this, $filePath, $disk ?? $this->disk ?? null, $readerType ?? $this->readerType ?? null ); } /** * @param string|UploadedFile|null $filePath * @param string|null $disk * @param string|null $readerType * @return array * * @throws NoFilePathGivenException */ public function toArray($filePath = null, ?string $disk = null, ?string $readerType = null): array { $filePath = $this->getFilePath($filePath); return $this->getImporter()->toArray( $this, $filePath, $disk ?? $this->disk ?? null, $readerType ?? $this->readerType ?? null ); } /** * @param string|UploadedFile|null $filePath * @param string|null $disk * @param string|null $readerType * @return Collection * * @throws NoFilePathGivenException */ public function toCollection($filePath = null, ?string $disk = null, ?string $readerType = null): Collection { $filePath = $this->getFilePath($filePath); return $this->getImporter()->toCollection( $this, $filePath, $disk ?? $this->disk ?? null, $readerType ?? $this->readerType ?? null ); } /** * @param string|UploadedFile|null $filePath * @param string|null $disk * @param string|null $readerType * @return PendingDispatch * * @throws NoFilePathGivenException * @throws InvalidArgumentException */ public function queue($filePath = null, ?string $disk = null, ?string $readerType = null) { if (!$this instanceof ShouldQueue) { throw new InvalidArgumentException('Importable should implement ShouldQueue to be queued.'); } return $this->import($filePath, $disk, $readerType); } /** * @param OutputStyle $output * @return $this */ public function withOutput(OutputStyle $output) { $this->output = $output; return $this; } /** * @return OutputStyle */ public function getConsoleOutput(): OutputStyle { if (!$this->output instanceof OutputStyle) { $this->output = new OutputStyle(new StringInput(''), new NullOutput()); } return $this->output; } /** * @param UploadedFile|string|null $filePath * @return UploadedFile|string * * @throws NoFilePathGivenException */ private function getFilePath($filePath = null) { $filePath = $filePath ?? $this->filePath ?? null; if (null === $filePath) { throw NoFilePathGivenException::import(); } return $filePath; } /** * @return Importer */ private function getImporter(): Importer { return app(Importer::class); } } ================================================ FILE: src/Concerns/MapsCsvSettings.php ================================================ 'beforeExport', BeforeWriting::class => 'beforeWriting', BeforeImport::class => 'beforeImport', AfterImport::class => 'afterImport', AfterBatch::class => 'afterBatch', AfterChunk::class => 'afterChunk', ImportFailed::class => 'importFailed', BeforeSheet::class => 'beforeSheet', AfterSheet::class => 'afterSheet', ]; $listeners = []; foreach ($listenersClasses as $class => $name) { // Method names are case insensitive in php if (method_exists($this, $name)) { // Allow methods to not be static $listeners[$class] = [$this, $name]; } } return $listeners; } } ================================================ FILE: src/Concerns/RemembersChunkOffset.php ================================================ chunkOffset = $chunkOffset; } /** * @return int|null */ public function getChunkOffset() { return $this->chunkOffset; } } ================================================ FILE: src/Concerns/RemembersRowNumber.php ================================================ rowNumber = $rowNumber; } /** * @return int|null */ public function getRowNumber() { return $this->rowNumber; } } ================================================ FILE: src/Concerns/ShouldAutoSize.php ================================================ errors[] = $e; } /** * @return Throwable[]|Collection */ public function errors(): Collection { return new Collection($this->errors); } } ================================================ FILE: src/Concerns/SkipsFailures.php ================================================ failures = array_merge($this->failures, $failures); } /** * @return Failure[]|Collection */ public function failures(): Collection { return new Collection($this->failures); } } ================================================ FILE: src/Concerns/SkipsOnError.php ================================================ conditionallySelectedSheets = is_array($sheets) ? $sheets : func_get_args(); return $this; } /** * @return array */ public function sheets(): array { return \array_filter($this->conditionalSheets(), function ($name) { return \in_array($name, $this->conditionallySelectedSheets, false); }, ARRAY_FILTER_USE_KEY); } /** * @return array */ abstract public function conditionalSheets(): array; } ================================================ FILE: src/Concerns/WithCustomChunkSize.php ================================================ count() by the chunk size. * Depending on the implementation of the query() method (eg. When using a groupBy clause), this calculation might not be correct. * * When this is the case, you should use this method to provide a custom calculation of the query size. * * @return int */ public function querySize(): int; } ================================================ FILE: src/Concerns/WithCustomStartCell.php ================================================ option('model') && $this->option('query')) { return $this->resolveStubPath('/stubs/export.query-model.stub'); } elseif ($this->option('model')) { return $this->resolveStubPath('/stubs/export.model.stub'); } elseif ($this->option('query')) { return $this->resolveStubPath('/stubs/export.query.stub'); } return $this->resolveStubPath('/stubs/export.plain.stub'); } /** * Get the default namespace for the class. * * @param string $rootNamespace * @return string */ protected function getDefaultNamespace($rootNamespace) { return $rootNamespace . '\Exports'; } /** * Build the class with the given name. * Remove the base controller import if we are already in base namespace. * * @param string $name * @return string */ protected function buildClass($name) { $replace = []; if ($this->option('model')) { $replace = $this->buildModelReplacements($replace); } return str_replace( array_keys($replace), array_values($replace), parent::buildClass($name) ); } /** * Get the console command options. * * @return array */ protected function getOptions() { return [ ['model', 'm', InputOption::VALUE_OPTIONAL, 'Generate an export for the given model.'], ['query', '', InputOption::VALUE_NONE, 'Generate an export for a query.'], ]; } } ================================================ FILE: src/Console/ImportMakeCommand.php ================================================ option('model') ? $this->resolveStubPath('/stubs/import.model.stub') : $this->resolveStubPath('/stubs/import.collection.stub'); } /** * Get the default namespace for the class. * * @param string $rootNamespace * @return string */ protected function getDefaultNamespace($rootNamespace) { return $rootNamespace . '\Imports'; } /** * Build the class with the given name. * Remove the base controller import if we are already in base namespace. * * @param string $name * @return string */ protected function buildClass($name) { $replace = []; if ($this->option('model')) { $replace = $this->buildModelReplacements($replace); } return str_replace( array_keys($replace), array_values($replace), parent::buildClass($name) ); } /** * Get the console command options. * * @return array */ protected function getOptions() { return [ ['model', 'm', InputOption::VALUE_OPTIONAL, 'Generate an import for the given model.'], ]; } } ================================================ FILE: src/Console/WithModelStub.php ================================================ parseModel($this->option('model')); return array_merge($replace, [ 'DummyFullModelClass' => $modelClass, 'DummyModelClass' => class_basename($modelClass), ]); } /** * Get the fully-qualified model class name. * * @param string $model * @return string */ protected function parseModel($model): string { if (preg_match('([^A-Za-z0-9_/\\\\])', $model)) { throw new InvalidArgumentException('Model name contains invalid characters.'); } $model = ltrim($model, '\\/'); $model = str_replace('/', '\\', $model); $rootNamespace = $this->rootNamespace(); if (Str::startsWith($model, $rootNamespace)) { return $model; } $model = is_dir(app_path('Models')) ? $rootNamespace . 'Models\\' . $model : $rootNamespace . $model; return $model; } /** * Resolve the fully-qualified path to the stub. * * @param string $stub * @return string */ protected function resolveStubPath($stub) { return file_exists($customPath = $this->laravel->basePath(trim($stub, '/'))) ? $customPath : __DIR__ . $stub; } } ================================================ FILE: src/Console/stubs/export.model.stub ================================================ getDelegate(), $method)) { return call_user_func_array([$this->getDelegate(), $method], $parameters); } array_unshift($parameters, $this); return $this->__callMacro($method, $parameters); } /** * @return object */ abstract public function getDelegate(); } ================================================ FILE: src/Events/AfterBatch.php ================================================ manager = $manager; $this->batchSize = $batchSize; $this->startRow = $startRow; parent::__construct($importable); } public function getManager(): ModelManager { return $this->manager; } /** * @return mixed */ public function getDelegate() { return $this->manager; } public function getBatchSize(): int { return $this->batchSize; } public function getStartRow(): int { return $this->startRow; } } ================================================ FILE: src/Events/AfterChunk.php ================================================ sheet = $sheet; $this->startRow = $startRow; parent::__construct($importable); } public function getSheet(): Sheet { return $this->sheet; } public function getDelegate() { return $this->sheet; } public function getStartRow(): int { return $this->startRow; } } ================================================ FILE: src/Events/AfterImport.php ================================================ reader = $reader; parent::__construct($importable); } /** * @return Reader */ public function getReader(): Reader { return $this->reader; } /** * @return mixed */ public function getDelegate() { return $this->reader; } } ================================================ FILE: src/Events/AfterSheet.php ================================================ sheet = $sheet; parent::__construct($exportable); } /** * @return Sheet */ public function getSheet(): Sheet { return $this->sheet; } /** * @return mixed */ public function getDelegate() { return $this->sheet; } } ================================================ FILE: src/Events/BeforeExport.php ================================================ writer = $writer; parent::__construct($exportable); } /** * @return Writer */ public function getWriter(): Writer { return $this->writer; } /** * @return mixed */ public function getDelegate() { return $this->writer; } } ================================================ FILE: src/Events/BeforeImport.php ================================================ reader = $reader; parent::__construct($importable); } /** * @return Reader */ public function getReader(): Reader { return $this->reader; } /** * @return mixed */ public function getDelegate() { return $this->reader; } } ================================================ FILE: src/Events/BeforeSheet.php ================================================ sheet = $sheet; parent::__construct($exportable); } /** * @return Sheet */ public function getSheet(): Sheet { return $this->sheet; } /** * @return mixed */ public function getDelegate() { return $this->sheet; } } ================================================ FILE: src/Events/BeforeWriting.php ================================================ writer = $writer; parent::__construct($exportable); } /** * @return Writer */ public function getWriter(): Writer { return $this->writer; } /** * @return mixed */ public function getDelegate() { return $this->writer; } } ================================================ FILE: src/Events/Event.php ================================================ concernable = $concernable; } /** * @return object */ public function getConcernable() { return $this->concernable; } /** * @return mixed */ abstract public function getDelegate(); /** * @param string $concern * @return bool */ public function appliesToConcern(string $concern): bool { return $this->getConcernable() instanceof $concern; } } ================================================ FILE: src/Events/ImportFailed.php ================================================ e = $e; } /** * @return Throwable */ public function getException(): Throwable { return $this->e; } } ================================================ FILE: src/Excel.php ================================================ writer = $writer; $this->reader = $reader; $this->filesystem = $filesystem; $this->queuedWriter = $queuedWriter; } /** * {@inheritdoc} */ public function download($export, string $fileName, ?string $writerType = null, array $headers = []) { // Clear output buffer to prevent stuff being prepended to the Excel output. if (ob_get_length() > 0) { ob_end_clean(); ob_start(); } return response()->download( $this->export($export, $fileName, $writerType)->getLocalPath(), $fileName, $headers )->deleteFileAfterSend(true); } /** * {@inheritdoc} * * @param string|null $disk Fallback for usage with named properties */ public function store($export, string $filePath, ?string $diskName = null, ?string $writerType = null, $diskOptions = [], ?string $disk = null) { if ($export instanceof ShouldQueue) { return $this->queue($export, $filePath, $diskName ?: $disk, $writerType, $diskOptions); } $temporaryFile = $this->export($export, $filePath, $writerType); $exported = $this->filesystem->disk($diskName ?: $disk, $diskOptions)->copy( $temporaryFile, $filePath ); $temporaryFile->delete(); return $exported; } /** * {@inheritdoc} */ public function queue($export, string $filePath, ?string $disk = null, ?string $writerType = null, $diskOptions = []) { $writerType = FileTypeDetector::detectStrict($filePath, $writerType); return $this->queuedWriter->store( $export, $filePath, $disk, $writerType, $diskOptions ); } /** * {@inheritdoc} */ public function raw($export, string $writerType) { $temporaryFile = $this->writer->export($export, $writerType); $contents = $temporaryFile->contents(); $temporaryFile->delete(); return $contents; } /** * {@inheritdoc} */ public function import($import, $filePath, ?string $disk = null, ?string $readerType = null) { $readerType = FileTypeDetector::detect($filePath, $readerType); $response = $this->reader->read($import, $filePath, $readerType, $disk); if ($response instanceof PendingDispatch) { return $response; } return $this; } /** * {@inheritdoc} */ public function toArray($import, $filePath, ?string $disk = null, ?string $readerType = null): array { $readerType = FileTypeDetector::detect($filePath, $readerType); return $this->reader->toArray($import, $filePath, $readerType, $disk); } /** * {@inheritdoc} */ public function toCollection($import, $filePath, ?string $disk = null, ?string $readerType = null): Collection { $readerType = FileTypeDetector::detect($filePath, $readerType); return $this->reader->toCollection($import, $filePath, $readerType, $disk); } /** * {@inheritdoc} */ public function queueImport(ShouldQueue $import, $filePath, ?string $disk = null, ?string $readerType = null) { return $this->import($import, $filePath, $disk, $readerType); } /** * @param object $export * @param string|null $fileName * @param string $writerType * @return TemporaryFile * * @throws \PhpOffice\PhpSpreadsheet\Exception */ protected function export($export, string $fileName, ?string $writerType = null): TemporaryFile { $writerType = FileTypeDetector::detectStrict($fileName, $writerType); return $this->writer->export($export, $writerType); } } ================================================ FILE: src/ExcelServiceProvider.php ================================================ app->runningInConsole()) { $this->publishes([ __DIR__ . '/Console/stubs/export.model.stub' => base_path('stubs/export.model.stub'), __DIR__ . '/Console/stubs/export.plain.stub' => base_path('stubs/export.plain.stub'), __DIR__ . '/Console/stubs/export.query.stub' => base_path('stubs/export.query.stub'), __DIR__ . '/Console/stubs/export.query-model.stub' => base_path('stubs/export.query-model.stub'), __DIR__ . '/Console/stubs/import.collection.stub' => base_path('stubs/import.collection.stub'), __DIR__ . '/Console/stubs/import.model.stub' => base_path('stubs/import.model.stub'), ], 'stubs'); if ($this->app instanceof LumenApplication) { $this->app->configure('excel'); } else { $this->publishes([ $this->getConfigFile() => config_path('excel.php'), ], 'config'); } } if ($this->app instanceof \Illuminate\Foundation\Application) { // Laravel $this->app->booted(function ($app) { $app->make(SettingsProvider::class)->provide(); }); } else { // Lumen $this->app->make(SettingsProvider::class)->provide(); } } /** * {@inheritdoc} */ public function register() { $this->mergeConfigFrom( $this->getConfigFile(), 'excel' ); $this->app->bind(CacheManager::class, function ($app) { return new CacheManager($app); }); $this->app->singleton(TransactionManager::class, function ($app) { return new TransactionManager($app); }); $this->app->bind(TransactionHandler::class, function ($app) { return $app->make(TransactionManager::class)->driver(); }); $this->app->bind(TemporaryFileFactory::class, function () { return new TemporaryFileFactory( config('excel.temporary_files.local_path', config('excel.exports.temp_path', storage_path('framework/laravel-excel'))), config('excel.temporary_files.remote_disk') ); }); $this->app->bind(Filesystem::class, function ($app) { return new Filesystem($app->make('filesystem')); }); $this->app->bind('excel', function ($app) { return new Excel( $app->make(Writer::class), $app->make(QueuedWriter::class), $app->make(Reader::class), $app->make(Filesystem::class) ); }); $this->app->alias('excel', Excel::class); $this->app->alias('excel', Exporter::class); $this->app->alias('excel', Importer::class); Collection::mixin(new DownloadCollectionMixin); Collection::mixin(new StoreCollectionMixin); Builder::macro('downloadExcel', (new DownloadQueryMacro)()); Builder::macro('storeExcel', (new StoreQueryMacro())()); Builder::macro('import', (new ImportMacro())()); Builder::macro('importAs', (new ImportAsMacro())()); $this->commands([ ExportMakeCommand::class, ImportMakeCommand::class, ]); } /** * @return string */ protected function getConfigFile(): string { return __DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'config' . DIRECTORY_SEPARATOR . 'excel.php'; } } ================================================ FILE: src/Exceptions/ConcernConflictException.php ================================================ failures = $failures; parent::__construct(); } /** * @return Failure[]|Collection */ public function failures(): Collection { return new Collection($this->failures); } /** * @return int[] */ public function skippedRows(): array { return $this->failures()->map->row()->all(); } } ================================================ FILE: src/Exceptions/SheetNotFoundException.php ================================================ setReadDataOnly(config('excel.imports.read_only', true)); } if (method_exists($reader, 'setReadEmptyCells')) { $reader->setReadEmptyCells(!config('excel.imports.ignore_empty', false)); } if ($reader instanceof Csv) { static::applyCsvSettings(config('excel.imports.csv', [])); if ($import instanceof WithCustomCsvSettings) { static::applyCsvSettings($import->getCsvSettings()); } $reader->setDelimiter(static::$delimiter); $reader->setEnclosure(static::$enclosure); $reader->setEscapeCharacter(static::$escapeCharacter); $reader->setContiguous(static::$contiguous); $reader->setInputEncoding(static::$inputEncoding); if (method_exists($reader, 'setTestAutoDetect')) { $reader->setTestAutoDetect(static::$testAutoDetect); } } if ($import instanceof WithReadFilter) { $reader->setReadFilter($import->readFilter()); } elseif ($import instanceof WithLimit) { $reader->setReadFilter(new LimitFilter( $import instanceof WithStartRow ? $import->startRow() : 1, $import->limit() )); } return $reader; } /** * @param TemporaryFile $temporaryFile * @return string * * @throws NoTypeDetectedException */ private static function identify(TemporaryFile $temporaryFile): string { try { return IOFactory::identify($temporaryFile->getLocalPath()); } catch (Exception $e) { throw new NoTypeDetectedException('', 0, $e); } } } ================================================ FILE: src/Factories/WriterFactory.php ================================================ setUseDiskCaching( config('excel.cache.driver', CacheManager::DRIVER_MEMORY) !== CacheManager::DRIVER_MEMORY ); if (static::includesCharts($export)) { $writer->setIncludeCharts(true); } if ($writer instanceof Html && $export instanceof WithMultipleSheets) { $writer->writeAllSheets(); } if ($writer instanceof Csv) { static::applyCsvSettings(config('excel.exports.csv', [])); // Auto-detect TSV files and apply tab delimiter if ($filePath && static::isTsvFile($filePath) && !($export instanceof WithCustomCsvSettings)) { static::applyCsvSettings(['delimiter' => "\t"]); } if ($export instanceof WithCustomCsvSettings) { static::applyCsvSettings($export->getCsvSettings()); } $writer->setDelimiter(static::$delimiter); $writer->setEnclosure(static::$enclosure); $writer->setEnclosureRequired((bool) static::$enclosure); $writer->setLineEnding(static::$lineEnding); $writer->setUseBOM(static::$useBom); $writer->setIncludeSeparatorLine(static::$includeSeparatorLine); $writer->setExcelCompatibility(static::$excelCompatibility); $writer->setOutputEncoding(static::$outputEncoding); } // Calculation settings $writer->setPreCalculateFormulas( $export instanceof WithPreCalculateFormulas ? true : config('excel.exports.pre_calculate_formulas', false) ); return $writer; } /** * @param $export * @return bool */ private static function includesCharts($export): bool { if ($export instanceof WithCharts) { return true; } if ($export instanceof WithMultipleSheets) { foreach ($export->sheets() as $sheet) { if ($sheet instanceof WithCharts) { return true; } } } return false; } /** * @param string $filePath * @return bool */ private static function isTsvFile(string $filePath): bool { $pathInfo = pathinfo($filePath); $extension = strtolower($pathInfo['extension'] ?? ''); return $extension === 'tsv'; } } ================================================ FILE: src/Fakes/ExcelFake.php ================================================ downloads[$fileName] = $export; return new BinaryFileResponse(__DIR__ . '/fake_file'); } /** * {@inheritdoc} * * @param string|null $diskName Fallback for usage with named properties */ public function store($export, string $filePath, ?string $disk = null, ?string $writerType = null, $diskOptions = [], ?string $diskName = null) { if ($export instanceof ShouldQueue) { return $this->queue($export, $filePath, $disk ?: $diskName, $writerType); } $this->stored[$disk ?: $diskName ?: 'default'][$filePath] = $export; return true; } /** * {@inheritdoc} */ public function queue($export, string $filePath, ?string $disk = null, ?string $writerType = null, $diskOptions = []) { Queue::fake(); $this->stored[$disk ?? 'default'][$filePath] = $export; $this->queued[$disk ?? 'default'][$filePath] = $export; $this->job = new class { use Queueable; public function handle() { // } }; Queue::push($this->job); return new PendingDispatch($this->job); } /** * @param object $export * @param string $writerType * @return string */ public function raw($export, string $writerType) { $this->raws[get_class($export)] = $export; return 'RAW-CONTENTS'; } /** * @param object $import * @param string|UploadedFile $file * @param string|null $disk * @param string|null $readerType * @return Reader|PendingDispatch */ public function import($import, $file, ?string $disk = null, ?string $readerType = null) { if ($import instanceof ShouldQueue) { return $this->queueImport($import, $file, $disk, $readerType); } $filePath = ($file instanceof UploadedFile) ? $file->getClientOriginalName() : $file; $this->imported[$disk ?? 'default'][$filePath] = $import; return $this; } /** * @param object $import * @param string|UploadedFile $file * @param string|null $disk * @param string|null $readerType * @return array */ public function toArray($import, $file, ?string $disk = null, ?string $readerType = null): array { $filePath = ($file instanceof UploadedFile) ? $file->getFilename() : $file; $this->imported[$disk ?? 'default'][$filePath] = $import; return []; } /** * @param object $import * @param string|UploadedFile $file * @param string|null $disk * @param string|null $readerType * @return Collection */ public function toCollection($import, $file, ?string $disk = null, ?string $readerType = null): Collection { $filePath = ($file instanceof UploadedFile) ? $file->getFilename() : $file; $this->imported[$disk ?? 'default'][$filePath] = $import; return new Collection(); } /** * @param ShouldQueue $import * @param string|UploadedFile $file * @param string|null $disk * @param string $readerType * @return PendingDispatch */ public function queueImport(ShouldQueue $import, $file, ?string $disk = null, ?string $readerType = null) { Queue::fake(); $filePath = ($file instanceof UploadedFile) ? $file->getFilename() : $file; $this->queued[$disk ?? 'default'][$filePath] = $import; $this->imported[$disk ?? 'default'][$filePath] = $import; $this->job = new class { use Queueable; public function handle() { // } }; Queue::push($this->job); return new PendingDispatch($this->job); } /** * When asserting downloaded, stored, queued or imported, use regular expression * to look for a matching file path. * * @return void */ public function matchByRegex() { $this->matchByRegex = true; } /** * When asserting downloaded, stored, queued or imported, use regular string * comparison for matching file path. * * @return void */ public function doNotMatchByRegex() { $this->matchByRegex = false; } /** * @param string $fileName * @param callable|null $callback */ public function assertDownloaded(string $fileName, $callback = null) { $fileName = $this->assertArrayHasKey($fileName, $this->downloads, sprintf('%s is not downloaded', $fileName)); $callback = $callback ?: function () { return true; }; Assert::assertTrue( $callback($this->downloads[$fileName]), "The file [{$fileName}] was not downloaded with the expected data." ); } /** * @param string $filePath * @param string|callable|null $disk * @param callable|null $callback */ public function assertStored(string $filePath, $disk = null, $callback = null) { if (is_callable($disk)) { $callback = $disk; $disk = null; } $disk = $disk ?? 'default'; $storedOnDisk = $this->stored[$disk] ?? []; $filePath = $this->assertArrayHasKey( $filePath, $storedOnDisk, sprintf('%s is not stored on disk %s', $filePath, $disk) ); $callback = $callback ?: function () { return true; }; Assert::assertTrue( $callback($storedOnDisk[$filePath]), "The file [{$filePath}] was not stored with the expected data." ); } /** * @param string $filePath * @param string|callable|null $disk * @param callable|null $callback */ public function assertQueued(string $filePath, $disk = null, $callback = null) { if (is_callable($disk)) { $callback = $disk; $disk = null; } $disk = $disk ?? 'default'; $queuedForDisk = $this->queued[$disk] ?? []; $filePath = $this->assertArrayHasKey( $filePath, $queuedForDisk, sprintf('%s is not queued for export on disk %s', $filePath, $disk) ); $callback = $callback ?: function () { return true; }; Assert::assertTrue( $callback($queuedForDisk[$filePath]), "The file [{$filePath}] was not stored with the expected data." ); } public function assertQueuedWithChain($chain): void { Queue::assertPushedWithChain(get_class($this->job), $chain); } /** * @param string $classname * @param callable|null $callback */ public function assertExportedInRaw(string $classname, $callback = null) { Assert::assertArrayHasKey($classname, $this->raws, sprintf('%s is not exported in raw', $classname)); $callback = $callback ?: function () { return true; }; Assert::assertTrue( $callback($this->raws[$classname]), "The [{$classname}] export was not exported in raw with the expected data." ); } /** * @param string $filePath * @param string|callable|null $disk * @param callable|null $callback */ public function assertImported(string $filePath, $disk = null, $callback = null) { if (is_callable($disk)) { $callback = $disk; $disk = null; } $disk = $disk ?? 'default'; $importedOnDisk = $this->imported[$disk] ?? []; $filePath = $this->assertArrayHasKey( $filePath, $importedOnDisk, sprintf('%s is not stored on disk %s', $filePath, $disk) ); $callback = $callback ?: function () { return true; }; Assert::assertTrue( $callback($importedOnDisk[$filePath]), "The file [{$filePath}] was not imported with the expected data." ); } /** * Asserts that an array has a specified key and returns the key if successful. * * @see matchByRegex for more information about file path matching * * @param string $key * @param array $array * @param string $message * @return string * * @throws ExpectationFailedException * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException * @throws Exception */ protected function assertArrayHasKey(string $key, array $disk, string $message = ''): string { if ($this->matchByRegex) { $files = array_keys($disk); $results = preg_grep($key, $files); Assert::assertGreaterThan(0, count($results), $message); Assert::assertEquals(1, count($results), "More than one result matches the file name expression '$key'."); return array_values($results)[0]; } Assert::assertArrayHasKey($key, $disk, $message); return $key; } } ================================================ FILE: src/Fakes/fake_file ================================================ ================================================ FILE: src/Files/Disk.php ================================================ disk = $disk; $this->name = $name; $this->diskOptions = $diskOptions; } /** * @param string $name * @param array $arguments * @return mixed */ public function __call($name, $arguments) { return $this->disk->{$name}(...$arguments); } /** * @param string $destination * @param string|resource $contents * @return bool */ public function put(string $destination, $contents): bool { return $this->disk->put($destination, $contents, $this->diskOptions); } /** * @param TemporaryFile $source * @param string $destination * @return bool */ public function copy(TemporaryFile $source, string $destination): bool { $readStream = $source->readStream(); if (!is_resource($readStream)) { return false; } if (realpath($destination)) { $tempStream = fopen($destination, 'rb+'); $success = stream_copy_to_stream($readStream, $tempStream) !== false; if (is_resource($tempStream)) { fclose($tempStream); } } else { $success = $this->put($destination, $readStream); } if (is_resource($readStream)) { fclose($readStream); } return $success; } /** * @param string $filename */ public function touch(string $filename) { $this->disk->put($filename, '', $this->diskOptions); } } ================================================ FILE: src/Files/Filesystem.php ================================================ filesystem = $filesystem; } /** * @param string|null $disk * @param array $diskOptions * @return Disk */ public function disk(?string $disk = null, array $diskOptions = []): Disk { return new Disk( $this->filesystem->disk($disk), $disk, $diskOptions ); } } ================================================ FILE: src/Files/LocalTemporaryFile.php ================================================ filePath = realpath($filePath); } /** * @return string */ public function getLocalPath(): string { return $this->filePath; } /** * @return bool */ public function exists(): bool { return file_exists($this->filePath); } /** * @return bool */ public function delete(): bool { if (@unlink($this->filePath) || !$this->exists()) { return true; } return unlink($this->filePath); } /** * @return resource */ public function readStream() { return fopen($this->getLocalPath(), 'rb+'); } /** * @return string */ public function contents(): string { return file_get_contents($this->filePath); } /** * @param @param string|resource $contents */ public function put($contents) { file_put_contents($this->filePath, $contents); } } ================================================ FILE: src/Files/RemoteTemporaryFile.php ================================================ disk = $disk; $this->filename = $filename; $this->localTemporaryFile = $localTemporaryFile; $this->disk()->touch($filename); } public function __sleep() { return ['disk', 'filename', 'localTemporaryFile']; } /** * @return string */ public function getLocalPath(): string { return $this->localTemporaryFile->getLocalPath(); } /** * @return bool */ public function existsLocally(): bool { return $this->localTemporaryFile->exists(); } /** * @return bool */ public function exists(): bool { return $this->disk()->exists($this->filename); } /** * @return bool */ public function deleteLocalCopy(): bool { return $this->localTemporaryFile->delete(); } /** * @return bool */ public function delete(): bool { // we don't need to delete local copy as it's deleted at end of each chunk if (!config('excel.temporary_files.force_resync_remote')) { $this->deleteLocalCopy(); } return $this->disk()->delete($this->filename); } /** * @return TemporaryFile */ public function sync(bool $copy = true): TemporaryFile { if (!$this->localTemporaryFile->exists()) { $this->localTemporaryFile = resolve(TemporaryFileFactory::class) ->makeLocal(Arr::last(explode('/', $this->filename))); } $copy && $this->disk()->copy( $this, $this->localTemporaryFile->getLocalPath() ); return $this; } /** * Store on remote disk. */ public function updateRemote() { $this->disk()->copy( $this->localTemporaryFile, $this->filename ); } /** * @return resource */ public function readStream() { return $this->disk()->readStream($this->filename); } /** * @return string */ public function contents(): string { return $this->disk()->get($this->filename); } /** * @param string|resource $contents */ public function put($contents) { $this->disk()->put($this->filename, $contents); } /** * @return Disk */ public function disk(): Disk { return $this->diskInstance ?: $this->diskInstance = app(Filesystem::class)->disk($this->disk); } } ================================================ FILE: src/Files/TemporaryFile.php ================================================ getRealPath(), 'rb'); } elseif ($disk === null && realpath($filePath) !== false) { $readStream = fopen($filePath, 'rb'); } else { $diskInstance = app('filesystem')->disk($disk); if (!$diskInstance->exists($filePath)) { $logPath = '[' . $filePath . ']'; if ($disk) { $logPath .= ' (' . $disk . ')'; } throw new FileNotFoundException('File ' . $logPath . ' does not exist and can therefore not be imported.'); } $readStream = $diskInstance->readStream($filePath); } $this->put($readStream); if (is_resource($readStream)) { fclose($readStream); } return $this->sync(); } } ================================================ FILE: src/Files/TemporaryFileFactory.php ================================================ temporaryPath = $temporaryPath; $this->temporaryDisk = $temporaryDisk; } /** * @param string|null $fileExtension * @return TemporaryFile */ public function make(?string $fileExtension = null): TemporaryFile { if (null !== $this->temporaryDisk) { return $this->makeRemote($fileExtension); } return $this->makeLocal(null, $fileExtension); } /** * @param string|null $fileName * @param string|null $fileExtension * @return LocalTemporaryFile */ public function makeLocal(?string $fileName = null, ?string $fileExtension = null): LocalTemporaryFile { if (!file_exists($this->temporaryPath) && !mkdir($concurrentDirectory = $this->temporaryPath, config('excel.temporary_files.local_permissions.dir', 0777), true) && !is_dir($concurrentDirectory)) { throw new \RuntimeException(sprintf('Directory "%s" was not created', $concurrentDirectory)); } return new LocalTemporaryFile( $this->temporaryPath . DIRECTORY_SEPARATOR . ($fileName ?: $this->generateFilename($fileExtension)) ); } /** * @param string|null $fileExtension * @return RemoteTemporaryFile */ private function makeRemote(?string $fileExtension = null): RemoteTemporaryFile { $filename = $this->generateFilename($fileExtension); return new RemoteTemporaryFile( $this->temporaryDisk, config('excel.temporary_files.remote_prefix') . $filename, $this->makeLocal($filename) ); } /** * @param string|null $fileExtension * @return string */ private function generateFilename(?string $fileExtension = null): string { return 'laravel-excel-' . Str::random(32) . ($fileExtension ? '.' . $fileExtension : ''); } } ================================================ FILE: src/Filters/ChunkReadFilter.php ================================================ headingRow = $headingRow; $this->startRow = $startRow; $this->endRow = $startRow + $chunkSize; $this->worksheetName = $worksheetName; } /** * @param string $column * @param int $row * @param string $worksheetName * @return bool */ public function readCell($column, $row, $worksheetName = '') { // Only read the heading row, and the rows that are configured in $this->_startRow and $this->_endRow return ($worksheetName === $this->worksheetName || $worksheetName === '') && ($row === $this->headingRow || ($row >= $this->startRow && $row < $this->endRow)); } } ================================================ FILE: src/Filters/LimitFilter.php ================================================ startRow = $startRow; $this->endRow = $startRow + $limit; } /** * @param string $column * @param int $row * @param string $worksheetName * @return bool */ public function readCell($column, $row, $worksheetName = '') { return $row >= $this->startRow && $row <= $this->endRow; } } ================================================ FILE: src/HasEventBus.php ================================================ $listener) { $this->events[$event][] = $listener; } } public function clearListeners() { $this->events = []; } /** * Register a global event listener. * * @param string $event * @param callable $listener */ public static function listen(string $event, callable $listener) { static::$globalEvents[$event][] = $listener; } /** * @param object $event */ public function raise($event) { foreach ($this->listeners($event) as $listener) { $listener($event); } } /** * @param object $event * @return callable[] */ public function listeners($event): array { $name = \get_class($event); $localListeners = $this->events[$name] ?? []; $globalListeners = static::$globalEvents[$name] ?? []; return array_merge($globalListeners, $localListeners); } } ================================================ FILE: src/HeadingRowImport.php ================================================ headingRow = $headingRow; } /** * @return int */ public function startRow(): int { return $this->headingRow; } /** * @return int */ public function limit(): int { return 1; } /** * @param mixed $row * @return array */ public function map($row): array { return HeadingRowFormatter::format($row); } } ================================================ FILE: src/Helpers/ArrayHelper.php ================================================ getClientOriginalExtension(); } if (null === $type && trim($extension) === '') { throw new NoTypeDetectedException(); } return config('excel.extension_detector.' . strtolower($extension)); } /** * @param string $filePath * @param string|null $type * @return string * * @throws NoTypeDetectedException */ public static function detectStrict(string $filePath, ?string $type = null): string { $type = static::detect($filePath, $type); if (!$type) { throw new NoTypeDetectedException(); } return $type; } } ================================================ FILE: src/Importer.php ================================================ limit(); if ($limit > $highestRow) { return null; } // When no start row given, // use the first row as start row. $startRow = $startRow ?? 1; // Subtract 1 row from the start row, so a limit // of 1 row, will have the same start and end row. return ($startRow - 1) + $limit; } } ================================================ FILE: src/Imports/HeadingRowExtractor.php ================================================ headingRow() : self::DEFAULT_HEADING_ROW; } /** * @param WithHeadingRow|mixed $importable * @return int */ public static function determineStartRow($importable): int { if ($importable instanceof WithStartRow) { return $importable->startRow(); } // The start row is the row after the heading row if we have one! return $importable instanceof WithHeadingRow ? self::headingRow($importable) + 1 : self::DEFAULT_HEADING_ROW; } /** * @param Worksheet $worksheet * @param WithHeadingRow|mixed $importable * @return array */ public static function extract(Worksheet $worksheet, $importable): array { if (!$importable instanceof WithHeadingRow) { return []; } $headingRowNumber = self::headingRow($importable); $rows = iterator_to_array($worksheet->getRowIterator($headingRowNumber, $headingRowNumber)); $headingRow = head($rows); $endColumn = $importable instanceof WithColumnLimit ? $importable->endColumn() : null; return HeadingRowFormatter::format((new Row($headingRow))->toArray(null, false, false, $endColumn)); } /** * @param array $headingRow * @param WithGroupedHeadingRow|mixed $importable * @return array */ public static function extractGrouping($headingRow, $importable) { $headerIsGrouped = array_fill(0, count($headingRow), false); if (!$importable instanceof WithGroupedHeadingRow) { return $headerIsGrouped; } array_walk($headerIsGrouped, function (&$value, $key) use ($headingRow) { if (array_count_values($headingRow)[$headingRow[$key]] > 1) { $value = true; } }); return $headerIsGrouped; } } ================================================ FILE: src/Imports/HeadingRowFormatter.php ================================================ map(function ($value, $key) { return static::callFormatter($value, $key); })->toArray(); } /** * @param string $name */ public static function default(?string $name = null) { if (null !== $name && !isset(static::$customFormatters[$name]) && !in_array($name, static::$defaultFormatters, true)) { throw new InvalidArgumentException(sprintf('Formatter "%s" does not exist', $name)); } static::$formatter = $name; } /** * @param string $name * @param callable $formatter */ public static function extend(string $name, callable $formatter) { static::$customFormatters[$name] = $formatter; } /** * Reset the formatter. */ public static function reset() { static::default(); } /** * @param mixed $value * @return mixed */ protected static function callFormatter($value, $key=null) { static::$formatter = static::$formatter ?? config('excel.imports.heading_row.formatter', self::FORMATTER_SLUG); // Call custom formatter if (isset(static::$customFormatters[static::$formatter])) { $formatter = static::$customFormatters[static::$formatter]; return $formatter($value, $key); } if (empty($value)) { return $key; } if (static::$formatter === self::FORMATTER_SLUG) { return Str::slug($value, '_'); } // No formatter (FORMATTER_NONE) return $value; } } ================================================ FILE: src/Imports/ModelImporter.php ================================================ manager = $manager; } /** * @param Worksheet $worksheet * @param ToModel $import * @param int|null $startRow * @param string|null $endColumn * * @throws \Maatwebsite\Excel\Validators\ValidationException */ public function import(Worksheet $worksheet, ToModel $import, int $startRow = 1) { if ($startRow > $worksheet->getHighestRow()) { return; } if ($import instanceof WithEvents) { $this->registerListeners($import->registerEvents()); } $headingRow = HeadingRowExtractor::extract($worksheet, $import); $headerIsGrouped = HeadingRowExtractor::extractGrouping($headingRow, $import); $batchSize = $import instanceof WithBatchInserts ? $import->batchSize() : 1; $endRow = EndRowFinder::find($import, $startRow, $worksheet->getHighestRow()); $progessBar = $import instanceof WithProgressBar; $withMapping = $import instanceof WithMapping; $withCalcFormulas = $import instanceof WithCalculatedFormulas; $formatData = $import instanceof WithFormatData; $withValidation = $import instanceof WithValidation && method_exists($import, 'prepareForValidation'); $endColumn = $import instanceof WithColumnLimit ? $import->endColumn() : null; $this->manager->setRemembersRowNumber(method_exists($import, 'rememberRowNumber')); $i = 0; $batchStartRow = $startRow; foreach ($worksheet->getRowIterator($startRow, $endRow) as $spreadSheetRow) { $i++; $row = new Row($spreadSheetRow, $headingRow, $headerIsGrouped); if (!$import instanceof SkipsEmptyRows || !$row->isEmpty($withCalcFormulas)) { $rowArray = $row->toArray(null, $withCalcFormulas, $formatData, $endColumn); if ($import instanceof SkipsEmptyRows && method_exists($import, 'isEmptyWhen') && $import->isEmptyWhen($rowArray)) { continue; } if ($withValidation) { $rowArray = $import->prepareForValidation($rowArray, $row->getIndex()); } if ($withMapping) { $rowArray = $import->map($rowArray); } $this->manager->add( $row->getIndex(), $rowArray ); // Flush each batch. if (($i % $batchSize) === 0) { $this->flush($import, $batchSize, $batchStartRow); $batchStartRow += $i; $i = 0; if ($progessBar) { $import->getConsoleOutput()->progressAdvance($batchSize); } } } } if ($i > 0) { // Flush left-overs. $this->flush($import, $batchSize, $batchStartRow); } } private function flush(ToModel $import, int $batchSize, int $startRow) { $this->manager->flush($import, $batchSize > 1); $this->raise(new AfterBatch($this->manager, $import, $batchSize, $startRow)); } } ================================================ FILE: src/Imports/ModelManager.php ================================================ validator = $validator; $this->cascade = $cascade; } /** * @param int $row * @param array $attributes */ public function add(int $row, array $attributes) { $this->rows[$row] = $attributes; } /** * @param bool $remembersRowNumber */ public function setRemembersRowNumber(bool $remembersRowNumber) { $this->remembersRowNumber = $remembersRowNumber; } /** * @param ToModel $import * @param bool $massInsert * * @throws ValidationException */ public function flush(ToModel $import, bool $massInsert = false) { if ($import instanceof WithValidation) { $this->validateRows($import); } if ($massInsert) { $this->massFlush($import); } else { $this->singleFlush($import); } $this->rows = []; } /** * @param ToModel $import * @param array $attributes * @param int|null $rowNumber * @return Model[]|Collection */ public function toModels(ToModel $import, array $attributes, $rowNumber = null): Collection { if ($this->remembersRowNumber) { $import->rememberRowNumber($rowNumber); } return Collection::wrap($import->model($attributes)); } /** * @param ToModel $import */ private function massFlush(ToModel $import) { $this->rows() ->flatMap(function (array $attributes, $index) use ($import) { return $this->toModels($import, $attributes, $index); }) ->mapToGroups(function ($model) { return [\get_class($model) => $this->prepare($model)->getAttributes()]; }) ->each(function (Collection $models, string $model) use ($import) { try { /* @var Model $model */ if ($import instanceof WithUpserts) { $model::query()->upsert( $models->toArray(), $import->uniqueBy(), $import instanceof WithUpsertColumns ? $import->upsertColumns() : null ); return; } elseif ($import instanceof WithSkipDuplicates) { $model::query()->insertOrIgnore($models->toArray()); return; } $model::query()->insert($models->toArray()); } catch (Throwable $e) { $this->handleException($import, $e); } }); } /** * @param ToModel $import */ private function singleFlush(ToModel $import) { $this ->rows() ->each(function (array $attributes, $index) use ($import) { $this->toModels($import, $attributes, $index)->each(function (Model $model) use ($import) { try { if ($import instanceof WithUpserts) { $model->upsert( $model->getAttributes(), $import->uniqueBy(), $import instanceof WithUpsertColumns ? $import->upsertColumns() : null ); return; } elseif ($import instanceof WithSkipDuplicates) { $model::query()->insertOrIgnore([$model->getAttributes()]); return; } if ($import instanceof PersistRelations) { $this->cascade->persist($model); } else { $model->saveOrFail(); } } catch (Throwable $e) { $this->handleException($import, $e); } }); }); } /** * @param Model $model * @return Model */ private function prepare(Model $model): Model { if ($model->usesTimestamps()) { $time = $model->freshTimestamp(); $updatedAtColumn = $model->getUpdatedAtColumn(); // If model has updated at column and not manually provided. if ($updatedAtColumn && null === $model->{$updatedAtColumn}) { $model->setUpdatedAt($time); } $createdAtColumn = $model->getCreatedAtColumn(); // If model has created at column and not manually provided. if ($createdAtColumn && null === $model->{$createdAtColumn}) { $model->setCreatedAt($time); } } return $model; } /** * @param WithValidation $import * * @throws ValidationException */ private function validateRows(WithValidation $import) { try { $this->validator->validate($this->rows, $import); } catch (RowSkippedException $e) { foreach ($e->skippedRows() as $row) { unset($this->rows[$row]); } } } /** * @return Collection */ private function rows(): Collection { return new Collection($this->rows); } private function handleException(ToModel $import, Throwable $e): void { if (!$import instanceof SkipsOnError) { throw $e; } $import->onError($e); } } ================================================ FILE: src/Imports/Persistence/CascadePersistManager.php ================================================ transaction = $transaction; } /** * @param Model $model * @return bool */ public function persist(Model $model): bool { return ($this->transaction)(function () use ($model) { return $this->save($model); }); } /** * @param Model $model * @return bool */ private function save(Model $model): bool { if (!$model->save()) { return false; } foreach ($model->getRelations() as $relationName => $models) { $models = array_filter( $models instanceof Collection ? $models->all() : [$models] ); $relation = $model->{$relationName}(); if ($relation instanceof BelongsTo) { if (!$this->persistBelongsTo($relation, $models)) { return false; } } if ($relation instanceof BelongsToMany) { if (!$this->persistBelongsToMany($relation, $models)) { return false; } } } // We need to save the model again to // make sure all updates are performed. $model->save(); return true; } /** * @param BelongsTo $relation * @param array $models * @return bool */ private function persistBelongsTo(BelongsTo $relation, array $models): bool { // With belongs to, we first need to save all relations, // so we can use their foreign key to attach to the relation. foreach ($models as $model) { // Cascade any relations that this child model may have. if (!$this->save($model)) { return false; } $relation->associate($model); } return true; } /** * @param BelongsToMany $relation * @param array $models * @return bool */ private function persistBelongsToMany(BelongsToMany $relation, array $models): bool { foreach ($models as $model) { $relation->save($model); // Cascade any relations that this child model may have. if (!$this->save($model)) { return false; } } return true; } } ================================================ FILE: src/Jobs/AfterImportJob.php ================================================ import = $import; $this->reader = $reader; } public function setInterval(int $interval) { $this->interval = $interval; } public function setDependencies(Collection $jobs) { $this->dependencyIds = $jobs->map(function (ReadChunk $job) { return $job->getUniqueId(); })->all(); } public function handle() { foreach ($this->dependencyIds as $id) { if (!ReadChunk::isComplete($id)) { // Until there is no jobs left to run we put this job back into the queue every minute // Note: this will do nothing in a SyncQueue but that's desired, because in a SyncQueue jobs run in order $this->release($this->interval); return; } } if ($this->import instanceof ShouldQueue && $this->import instanceof WithEvents) { $this->reader->clearListeners(); $this->reader->registerListeners($this->import->registerEvents()); } $this->reader->afterImport($this->import); } /** * @param Throwable $e */ public function failed(Throwable $e) { if ($this->import instanceof WithEvents) { $this->registerListeners($this->import->registerEvents()); $this->raise(new ImportFailed($e)); if (method_exists($this->import, 'failed')) { $this->import->failed($e); } } } } ================================================ FILE: src/Jobs/AppendDataToSheet.php ================================================ sheetExport = $sheetExport; $this->data = $data; $this->temporaryFile = $temporaryFile; $this->writerType = $writerType; $this->sheetIndex = $sheetIndex; } /** * Get the middleware the job should be dispatched through. * * @return array */ public function middleware() { return (method_exists($this->sheetExport, 'middleware')) ? $this->sheetExport->middleware() : []; } /** * @param Writer $writer * * @throws \PhpOffice\PhpSpreadsheet\Exception * @throws \PhpOffice\PhpSpreadsheet\Reader\Exception */ public function handle(Writer $writer) { (new LocalizeJob($this->sheetExport))->handle($this, function () use ($writer) { $writer = $writer->reopen($this->temporaryFile, $this->writerType); $sheet = $writer->getSheetByIndex($this->sheetIndex); $sheet->appendRows($this->data, $this->sheetExport); $writer->write($this->sheetExport, $this->temporaryFile, $this->writerType); }); } } ================================================ FILE: src/Jobs/AppendPaginatedToSheet.php ================================================ sheetExport = $sheetExport; $this->temporaryFile = $temporaryFile; $this->writerType = $writerType; $this->sheetIndex = $sheetIndex; $this->page = $page; $this->perPage = $perPage; } /** * Get the middleware the job should be dispatched through. * * @return array */ public function middleware() { return (method_exists($this->sheetExport, 'middleware')) ? $this->sheetExport->middleware() : []; } /** * @param Writer $writer * * @throws \PhpOffice\PhpSpreadsheet\Exception * @throws \PhpOffice\PhpSpreadsheet\Reader\Exception */ public function handle(Writer $writer) { (new LocalizeJob($this->sheetExport))->handle($this, function () use ($writer) { $writer = $writer->reopen($this->temporaryFile, $this->writerType); $sheet = $writer->getSheetByIndex($this->sheetIndex); $sheet->appendRows($this->chunk($this->sheetExport->query()), $this->sheetExport); $writer->write($this->sheetExport, $this->temporaryFile, $this->writerType); }); } /** * @param Builder|Relation|EloquentBuilder|ScoutBuilder $query */ protected function chunk($query) { if ($query instanceof \Laravel\Scout\Builder) { return $query->paginate($this->perPage, 'page', $this->page)->items(); } // Fallback return $query->forPage($this->page, $this->perPage)->get(); } } ================================================ FILE: src/Jobs/AppendQueryToSheet.php ================================================ sheetExport = $sheetExport; $this->temporaryFile = $temporaryFile; $this->writerType = $writerType; $this->sheetIndex = $sheetIndex; $this->page = $page; $this->chunkSize = $chunkSize; } /** * Get the middleware the job should be dispatched through. * * @return array */ public function middleware() { return (method_exists($this->sheetExport, 'middleware')) ? $this->sheetExport->middleware() : []; } /** * @param Writer $writer * * @throws \PhpOffice\PhpSpreadsheet\Exception * @throws \PhpOffice\PhpSpreadsheet\Reader\Exception */ public function handle(Writer $writer) { (new LocalizeJob($this->sheetExport))->handle($this, function () use ($writer) { if ($this->sheetExport instanceof WithEvents) { $this->registerListeners($this->sheetExport->registerEvents()); } $writer = $writer->reopen($this->temporaryFile, $this->writerType); $sheet = $writer->getSheetByIndex($this->sheetIndex); $query = $this->sheetExport->query()->forPage($this->page, $this->chunkSize); $sheet->appendRows($query->get(), $this->sheetExport); $writer->write($this->sheetExport, $this->temporaryFile, $this->writerType); $this->raise(new AfterChunk($sheet, $this->sheetExport, ($this->page - 1) * $this->chunkSize)); $this->clearListeners(); }); } } ================================================ FILE: src/Jobs/AppendViewToSheet.php ================================================ sheetExport = $sheetExport; $this->temporaryFile = $temporaryFile; $this->writerType = $writerType; $this->sheetIndex = $sheetIndex; } /** * Get the middleware the job should be dispatched through. * * @return array */ public function middleware() { return (method_exists($this->sheetExport, 'middleware')) ? $this->sheetExport->middleware() : []; } /** * @param Writer $writer * * @throws \PhpOffice\PhpSpreadsheet\Exception * @throws \PhpOffice\PhpSpreadsheet\Reader\Exception */ public function handle(Writer $writer) { (new LocalizeJob($this->sheetExport))->handle($this, function () use ($writer) { $writer = $writer->reopen($this->temporaryFile, $this->writerType); $sheet = $writer->getSheetByIndex($this->sheetIndex); $sheet->fromView($this->sheetExport, $this->sheetIndex); $writer->write($this->sheetExport, $this->temporaryFile, $this->writerType); }); } } ================================================ FILE: src/Jobs/CloseSheet.php ================================================ sheetExport = $sheetExport; $this->temporaryFile = $temporaryFile; $this->writerType = $writerType; $this->sheetIndex = $sheetIndex; } /** * @param Writer $writer * * @throws \PhpOffice\PhpSpreadsheet\Exception * @throws \PhpOffice\PhpSpreadsheet\Reader\Exception */ public function handle(Writer $writer) { $writer = $writer->reopen( $this->temporaryFile, $this->writerType ); $sheet = $writer->getSheetByIndex($this->sheetIndex); if ($this->sheetExport instanceof WithEvents) { $sheet->registerListeners($this->sheetExport->registerEvents()); } $sheet->close($this->sheetExport); $writer->write( $this->sheetExport, $this->temporaryFile, $this->writerType ); } } ================================================ FILE: src/Jobs/ExtendedQueueable.php ================================================ each(function ($job) { $serialized = method_exists($this, 'serializeJob') ? $this->serializeJob($job) : serialize($job); $this->chained[] = $serialized; }); return $this; } } ================================================ FILE: src/Jobs/Middleware/LocalizeJob.php ================================================ localizable = $localizable; } /** * Handles the job. * * @param mixed $job * @param Closure $next * @return mixed */ public function handle($job, Closure $next) { $locale = value(function () { if ($this->localizable instanceof HasLocalePreference) { return $this->localizable->preferredLocale(); } return null; }); return $this->withLocale($locale, function () use ($next, $job) { return $next($job); }); } } ================================================ FILE: src/Jobs/ProxyFailures.php ================================================ sheetExport, 'failed')) { $this->sheetExport->failed($e); } } } ================================================ FILE: src/Jobs/QueueExport.php ================================================ export = $export; $this->writerType = $writerType; $this->temporaryFile = $temporaryFile; } /** * Get the middleware the job should be dispatched through. * * @return array */ public function middleware() { return (method_exists($this->export, 'middleware')) ? $this->export->middleware() : []; } /** * @param Writer $writer * * @throws \PhpOffice\PhpSpreadsheet\Exception */ public function handle(Writer $writer) { (new LocalizeJob($this->export))->handle($this, function () use ($writer) { $writer->open($this->export); $sheetExports = [$this->export]; if ($this->export instanceof WithMultipleSheets) { $sheetExports = $this->export->sheets(); } if (count($sheetExports) === 0) { throw new NoSheetsFoundException('Your export did not return any sheet export instances, please make sure your sheets() method always at least returns one instance.'); } // Pre-create the worksheets foreach ($sheetExports as $sheetIndex => $sheetExport) { $sheet = $writer->addNewSheet($sheetIndex); $sheet->open($sheetExport); } // Write to temp file with empty sheets. $writer->write($this->export, $this->temporaryFile, $this->writerType); }); } /** * @param Throwable $e */ public function failed(Throwable $e) { if (method_exists($this->export, 'failed')) { $this->export->failed($e); } } } ================================================ FILE: src/Jobs/QueueImport.php ================================================ timeout = $import->timeout ?? null; $this->tries = $import->tries ?? null; } } public function handle() { // } } ================================================ FILE: src/Jobs/ReadChunk.php ================================================ import = $import; $this->reader = $reader; $this->temporaryFile = $temporaryFile; $this->sheetName = $sheetName; $this->sheetImport = $sheetImport; $this->startRow = $startRow; $this->chunkSize = $chunkSize; $this->timeout = $import->timeout ?? null; $this->tries = $import->tries ?? null; $this->maxExceptions = $import->maxExceptions ?? null; $this->backoff = method_exists($import, 'backoff') ? $import->backoff() : ($import->backoff ?? null); $this->connection = property_exists($import, 'connection') ? $import->connection : null; $this->queue = property_exists($import, 'queue') ? $import->queue : null; } public function getUniqueId(): string { if (!isset($this->uniqueId)) { $this->uniqueId = uniqid(); Cache::set('laravel-excel/read-chunk/' . $this->uniqueId, true); } return $this->uniqueId; } public static function isComplete(string $id): bool { return !Cache::has('laravel-excel/read-chunk/' . $id); } /** * Get the middleware the job should be dispatched through. * * @return array */ public function middleware() { return (method_exists($this->import, 'middleware')) ? $this->import->middleware() : []; } /** * Determine the time at which the job should timeout. * * @return \DateTime */ public function retryUntil() { return (method_exists($this->import, 'retryUntil')) ? $this->import->retryUntil() : null; } /** * @param TransactionHandler $transaction * * @throws \Maatwebsite\Excel\Exceptions\SheetNotFoundException * @throws \PhpOffice\PhpSpreadsheet\Reader\Exception */ public function handle(TransactionHandler $transaction) { if (method_exists($this->import, 'setChunkOffset')) { $this->import->setChunkOffset($this->startRow); } if (method_exists($this->sheetImport, 'setChunkOffset')) { $this->sheetImport->setChunkOffset($this->startRow); } if ($this->sheetImport instanceof WithCustomValueBinder) { Cell::setValueBinder($this->sheetImport); } $headingRow = HeadingRowExtractor::headingRow($this->sheetImport); $filter = new ChunkReadFilter( $headingRow, $this->startRow, $this->chunkSize, $this->sheetName ); $this->reader->setReadFilter($filter); $this->reader->setReadDataOnly(config('excel.imports.read_only', true)); $this->reader->setReadEmptyCells(!config('excel.imports.ignore_empty', false)); $spreadsheet = $this->reader->load( $this->temporaryFile->sync()->getLocalPath() ); $sheet = Sheet::byName( $spreadsheet, $this->sheetName ); if ($sheet->getHighestRow() < $this->startRow) { $sheet->disconnect(); $this->cleanUpTempFile(); return; } $transaction(function () use ($sheet) { $sheet->import( $this->sheetImport, $this->startRow ); $sheet->disconnect(); $this->cleanUpTempFile(); $sheet->raise(new AfterChunk($sheet, $this->import, $this->startRow)); }); } /** * @param Throwable $e */ public function failed(Throwable $e) { $this->cleanUpTempFile(true); if ($this->import instanceof WithEvents) { $this->registerListeners($this->import->registerEvents()); $this->raise(new ImportFailed($e)); if (method_exists($this->import, 'failed')) { $this->import->failed($e); } } } private function cleanUpTempFile(bool $force = false): bool { if (!empty($this->uniqueId)) { Cache::delete('laravel-excel/read-chunk/' . $this->uniqueId); } if (!$force && !config('excel.temporary_files.force_resync_remote')) { return true; } if (!$this->temporaryFile instanceof RemoteTemporaryFile) { return true; } return $this->temporaryFile->deleteLocalCopy(); } } ================================================ FILE: src/Jobs/StoreQueuedExport.php ================================================ disk = $disk; $this->filePath = $filePath; $this->temporaryFile = $temporaryFile; $this->diskOptions = $diskOptions; } /** * @param Filesystem $filesystem */ public function handle(Filesystem $filesystem) { $filesystem->disk($this->disk, $this->diskOptions)->copy( $this->temporaryFile, $this->filePath ); $this->temporaryFile->delete(); } } ================================================ FILE: src/MappedReader.php ================================================ mapping(); array_walk_recursive($mapped, function (&$coordinate) use ($import, $worksheet) { $cell = Cell::make($worksheet, $coordinate); $coordinate = $cell->getValue( null, $import instanceof WithCalculatedFormulas, $import instanceof WithFormatData ); }); if ($import instanceof ToModel) { $model = $import->model($mapped); if ($model) { $model->saveOrFail(); } } if ($import instanceof ToCollection) { $import->collection(new Collection($mapped)); } if ($import instanceof ToArray) { $import->array($mapped); } } } ================================================ FILE: src/Middleware/CellMiddleware.php ================================================ collection = $collection->toBase(); $this->withHeadings = $withHeading; } /** * @return Collection */ public function collection() { return $this->collection; } /** * @return array */ public function headings(): array { if (!$this->withHeadings) { return []; } $firstRow = $this->collection->first(); if ($firstRow instanceof Arrayable || \is_object($firstRow)) { return array_keys(Sheet::mapArraybleRow($firstRow)); } return $this->collection->collapse()->keys()->all(); } }; return $export->download($fileName, $writerType, $responseHeaders); }; } } ================================================ FILE: src/Mixins/DownloadQueryMacro.php ================================================ query = $query; $this->withHeadings = $withHeadings; } /** * @return Builder */ public function query() { return $this->query; } /** * @return array */ public function headings(): array { if (!$this->withHeadings) { return []; } $firstRow = (clone $this->query)->first(); if ($firstRow) { return array_keys(Sheet::mapArraybleRow($firstRow)); } return []; } }; return $export->download($fileName, $writerType); }; } } ================================================ FILE: src/Mixins/ImportAsMacro.php ================================================ getModel()), $mapping) implements ToModel { use Importable; /** * @var string */ private $model; /** * @var callable */ private $mapping; /** * @param string $model * @param callable $mapping */ public function __construct(string $model, callable $mapping) { $this->model = $model; $this->mapping = $mapping; } /** * @param array $row * @return Model|Model[]|null */ public function model(array $row) { return (new $this->model)->fill( ($this->mapping)($row) ); } }; return $import->import($filename, $disk, $readerType); }; } } ================================================ FILE: src/Mixins/ImportMacro.php ================================================ getModel())) implements ToModel, WithHeadingRow { use Importable; /** * @var string */ private $model; /** * @param string $model */ public function __construct(string $model) { $this->model = $model; } /** * @param array $row * @return Model|Model[]|null */ public function model(array $row) { return (new $this->model)->fill($row); } }; return $import->import($filename, $disk, $readerType); }; } } ================================================ FILE: src/Mixins/StoreCollectionMixin.php ================================================ collection = $collection->toBase(); $this->withHeadings = $withHeadings; } /** * @return Collection */ public function collection() { return $this->collection; } /** * @return array */ public function headings(): array { if (!$this->withHeadings) { return []; } return is_array($first = $this->collection->first()) ? $this->collection->collapse()->keys()->all() : array_keys($first->toArray()); } }; return $export->store($filePath, $disk, $writerType); }; } } ================================================ FILE: src/Mixins/StoreQueryMacro.php ================================================ query = $query; $this->withHeadings = $withHeadings; } /** * @return Builder */ public function query() { return $this->query; } /** * @return array */ public function headings(): array { if (!$this->withHeadings) { return []; } $firstRow = (clone $this->query)->first(); if ($firstRow) { return array_keys(Sheet::mapArraybleRow($firstRow)); } return []; } }; return $export->store($filePath, $disk, $writerType); }; } } ================================================ FILE: src/QueuedWriter.php ================================================ writer = $writer; $this->chunkSize = config('excel.exports.chunk_size', 1000); $this->temporaryFileFactory = $temporaryFileFactory; } /** * @param object $export * @param string $filePath * @param string $disk * @param string|null $writerType * @param array|string $diskOptions * @return \Illuminate\Foundation\Bus\PendingDispatch */ public function store($export, string $filePath, ?string $disk = null, ?string $writerType = null, $diskOptions = []) { $extension = pathinfo($filePath, PATHINFO_EXTENSION); $temporaryFile = $this->temporaryFileFactory->make($extension); $jobs = $this->buildExportJobs($export, $temporaryFile, $writerType); $jobs->push(new StoreQueuedExport( $temporaryFile, $filePath, $disk, $diskOptions )); return new PendingDispatch( (new QueueExport($export, $temporaryFile, $writerType))->chain($jobs->toArray()) ); } /** * @param object $export * @param TemporaryFile $temporaryFile * @param string $writerType * @return Collection */ private function buildExportJobs($export, TemporaryFile $temporaryFile, string $writerType): Collection { $sheetExports = [$export]; if ($export instanceof WithMultipleSheets) { $sheetExports = $export->sheets(); } $jobs = new Collection; foreach ($sheetExports as $sheetIndex => $sheetExport) { if ($sheetExport instanceof FromCollection) { $jobs = $jobs->merge($this->exportCollection($sheetExport, $temporaryFile, $writerType, $sheetIndex)); } elseif ($sheetExport instanceof FromQuery) { $jobs = $jobs->merge($this->exportQuery($sheetExport, $temporaryFile, $writerType, $sheetIndex)); } elseif ($sheetExport instanceof FromView) { $jobs = $jobs->merge($this->exportView($sheetExport, $temporaryFile, $writerType, $sheetIndex)); } $jobs->push(new CloseSheet($sheetExport, $temporaryFile, $writerType, $sheetIndex)); } return $jobs; } /** * @param FromCollection $export * @param TemporaryFile $temporaryFile * @param string $writerType * @param int $sheetIndex * @return Collection|LazyCollection */ private function exportCollection( FromCollection $export, TemporaryFile $temporaryFile, string $writerType, int $sheetIndex ) { return $export ->collection() ->chunk($this->getChunkSize($export)) ->map(function ($rows) use ($writerType, $temporaryFile, $sheetIndex, $export) { if ($rows instanceof Traversable) { $rows = iterator_to_array($rows); } return new AppendDataToSheet( $export, $temporaryFile, $writerType, $sheetIndex, $rows ); }); } /** * @param FromQuery $export * @param TemporaryFile $temporaryFile * @param string $writerType * @param int $sheetIndex * @return Collection */ private function exportQuery( FromQuery $export, TemporaryFile $temporaryFile, string $writerType, int $sheetIndex ): Collection { $query = $export->query(); if ($query instanceof \Laravel\Scout\Builder) { return $this->exportScout($export, $temporaryFile, $writerType, $sheetIndex); } $count = $export instanceof WithCustomQuerySize ? $export->querySize() : $query->count(); $spins = ceil($count / $this->getChunkSize($export)); $jobs = new Collection(); for ($page = 1; $page <= $spins; $page++) { $jobs->push(new AppendQueryToSheet( $export, $temporaryFile, $writerType, $sheetIndex, $page, $this->getChunkSize($export) )); } return $jobs; } /** * @param FromQuery $export * @param TemporaryFile $temporaryFile * @param string $writerType * @param int $sheetIndex * @return Collection */ private function exportScout( FromQuery $export, TemporaryFile $temporaryFile, string $writerType, int $sheetIndex ): Collection { $jobs = new Collection(); $chunk = $export->query()->paginate($this->getChunkSize($export)); // Append first page $jobs->push(new AppendDataToSheet( $export, $temporaryFile, $writerType, $sheetIndex, $chunk->items() )); // Append rest of pages for ($page = 2; $page <= $chunk->lastPage(); $page++) { $jobs->push(new AppendPaginatedToSheet( $export, $temporaryFile, $writerType, $sheetIndex, $page, $this->getChunkSize($export) )); } return $jobs; } /** * @param FromView $export * @param TemporaryFile $temporaryFile * @param string $writerType * @param int $sheetIndex * @return Collection */ private function exportView( FromView $export, TemporaryFile $temporaryFile, string $writerType, int $sheetIndex ): Collection { $jobs = new Collection(); $jobs->push(new AppendViewToSheet( $export, $temporaryFile, $writerType, $sheetIndex )); return $jobs; } /** * @param object|WithCustomChunkSize $export * @return int */ private function getChunkSize($export): int { if ($export instanceof WithCustomChunkSize) { return $export->chunkSize(); } return $this->chunkSize; } } ================================================ FILE: src/Reader.php ================================================ setDefaultValueBinder(); $this->transaction = $transaction; $this->temporaryFileFactory = $temporaryFileFactory; } public function __sleep() { return ['spreadsheet', 'sheetImports', 'currentFile', 'temporaryFileFactory', 'reader']; } public function __wakeup() { $this->transaction = app(TransactionHandler::class); } /** * @param object $import * @param string|UploadedFile $filePath * @param string|null $readerType * @param string|null $disk * @return \Illuminate\Foundation\Bus\PendingDispatch|$this * * @throws NoTypeDetectedException * @throws \Illuminate\Contracts\Filesystem\FileNotFoundException * @throws Exception */ public function read($import, $filePath, ?string $readerType = null, ?string $disk = null) { $this->reader = $this->getReader($import, $filePath, $readerType, $disk); if ($import instanceof WithChunkReading) { return app(ChunkReader::class)->read($import, $this, $this->currentFile); } try { $this->loadSpreadsheet($import); ($this->transaction)(function () use ($import) { $sheetsToDisconnect = []; foreach ($this->sheetImports as $index => $sheetImport) { if ($sheet = $this->getSheet($import, $sheetImport, $index)) { $sheet->import($sheetImport, $sheet->getStartRow($sheetImport)); // when using WithCalculatedFormulas we need to keep the sheet until all sheets are imported if (!($sheetImport instanceof HasReferencesToOtherSheets)) { $sheet->disconnect(); } else { $sheetsToDisconnect[] = $sheet; } } } foreach ($sheetsToDisconnect as $sheet) { $sheet->disconnect(); } }); $this->afterImport($import); } catch (Throwable $e) { $this->raise(new ImportFailed($e)); $this->garbageCollect(); throw $e; } return $this; } /** * @param object $import * @param string|UploadedFile $filePath * @param string $readerType * @param string|null $disk * @return array * * @throws \Illuminate\Contracts\Filesystem\FileNotFoundException * @throws \PhpOffice\PhpSpreadsheet\Exception * @throws NoTypeDetectedException * @throws Exceptions\SheetNotFoundException */ public function toArray($import, $filePath, ?string $readerType = null, ?string $disk = null): array { $this->reader = $this->getReader($import, $filePath, $readerType, $disk); $this->loadSpreadsheet($import); $sheets = []; $sheetsToDisconnect = []; foreach ($this->sheetImports as $index => $sheetImport) { $calculatesFormulas = $sheetImport instanceof WithCalculatedFormulas; $formatData = $sheetImport instanceof WithFormatData; if ($sheet = $this->getSheet($import, $sheetImport, $index)) { $sheets[$index] = $sheet->toArray($sheetImport, $sheet->getStartRow($sheetImport), null, $calculatesFormulas, $formatData); // when using WithCalculatedFormulas we need to keep the sheet until all sheets are imported if (!($sheetImport instanceof HasReferencesToOtherSheets)) { $sheet->disconnect(); } else { $sheetsToDisconnect[] = $sheet; } } } foreach ($sheetsToDisconnect as $sheet) { $sheet->disconnect(); } $this->afterImport($import); return $sheets; } /** * @param object $import * @param string|UploadedFile $filePath * @param string $readerType * @param string|null $disk * @return Collection * * @throws \Illuminate\Contracts\Filesystem\FileNotFoundException * @throws \PhpOffice\PhpSpreadsheet\Exception * @throws NoTypeDetectedException * @throws Exceptions\SheetNotFoundException */ public function toCollection($import, $filePath, ?string $readerType = null, ?string $disk = null): Collection { $this->reader = $this->getReader($import, $filePath, $readerType, $disk); $this->loadSpreadsheet($import); $sheets = new Collection(); $sheetsToDisconnect = []; foreach ($this->sheetImports as $index => $sheetImport) { $calculatesFormulas = $sheetImport instanceof WithCalculatedFormulas; $formatData = $sheetImport instanceof WithFormatData; if ($sheet = $this->getSheet($import, $sheetImport, $index)) { $sheets->put($index, $sheet->toCollection($sheetImport, $sheet->getStartRow($sheetImport), null, $calculatesFormulas, $formatData)); // when using WithCalculatedFormulas we need to keep the sheet until all sheets are imported if (!($sheetImport instanceof HasReferencesToOtherSheets)) { $sheet->disconnect(); } else { $sheetsToDisconnect[] = $sheet; } } } foreach ($sheetsToDisconnect as $sheet) { $sheet->disconnect(); } $this->afterImport($import); return $sheets; } /** * @return Spreadsheet */ public function getDelegate() { return $this->spreadsheet; } /** * @return $this */ public function setDefaultValueBinder(): self { Cell::setValueBinder( app(config('excel.value_binder.default', DefaultValueBinder::class)) ); return $this; } /** * @param object $import */ public function loadSpreadsheet($import) { $this->sheetImports = $this->buildSheetImports($import); $this->readSpreadsheet(); // When no multiple sheets, use the main import object // for each loaded sheet in the spreadsheet if (!$import instanceof WithMultipleSheets) { $this->sheetImports = array_fill(0, $this->spreadsheet->getSheetCount(), $import); } $this->beforeImport($import); } public function readSpreadsheet() { $this->spreadsheet = $this->reader->load( $this->currentFile->getLocalPath() ); } /** * @param object $import */ public function beforeImport($import) { $this->raise(new BeforeImport($this, $import)); } /** * @param object $import */ public function afterImport($import) { $this->raise(new AfterImport($this, $import)); $this->garbageCollect(); } /** * @return IReader */ public function getPhpSpreadsheetReader(): IReader { return $this->reader; } /** * @param object $import * @return array */ public function getWorksheets($import): array { // Csv doesn't have worksheets. if (!method_exists($this->reader, 'listWorksheetNames')) { return ['Worksheet' => $import]; } $worksheets = []; $worksheetNames = $this->reader->listWorksheetNames($this->currentFile->getLocalPath()); if ($import instanceof WithMultipleSheets) { $sheetImports = $import->sheets(); foreach ($sheetImports as $index => $sheetImport) { // Translate index to name. if (is_numeric($index)) { $index = $worksheetNames[$index] ?? $index; } // Specify with worksheet name should have which import. $worksheets[$index] = $sheetImport; } // Load specific sheets. if (method_exists($this->reader, 'setLoadSheetsOnly')) { $this->reader->setLoadSheetsOnly( collect($worksheetNames)->intersect(array_keys($worksheets))->values()->all() ); } } else { // Each worksheet the same import class. foreach ($worksheetNames as $name) { $worksheets[$name] = $import; } } return $worksheets; } /** * @return array */ public function getTotalRows(): array { $info = $this->reader->listWorksheetInfo($this->currentFile->getLocalPath()); $totalRows = []; foreach ($info as $sheet) { $totalRows[$sheet['worksheetName']] = $sheet['totalRows']; } return $totalRows; } /** * @param $import * @param $sheetImport * @param $index * @return Sheet|null * * @throws \PhpOffice\PhpSpreadsheet\Exception * @throws SheetNotFoundException */ protected function getSheet($import, $sheetImport, $index) { try { return Sheet::make($this->spreadsheet, $index); } catch (SheetNotFoundException $e) { if ($import instanceof SkipsUnknownSheets) { $import->onUnknownSheet($index); return null; } if ($sheetImport instanceof SkipsUnknownSheets) { $sheetImport->onUnknownSheet($index); return null; } throw $e; } } /** * @param object $import * @return array */ private function buildSheetImports($import): array { $sheetImports = []; if ($import instanceof WithMultipleSheets) { $sheetImports = $import->sheets(); // When only sheet names are given and the reader has // an option to load only the selected sheets. if ( method_exists($this->reader, 'setLoadSheetsOnly') && count(array_filter(array_keys($sheetImports), 'is_numeric')) === 0 ) { $this->reader->setLoadSheetsOnly(array_keys($sheetImports)); } } return $sheetImports; } /** * @param object $import * @param string|UploadedFile $filePath * @param string|null $readerType * @param string $disk * @return IReader * * @throws \Illuminate\Contracts\Filesystem\FileNotFoundException * @throws NoTypeDetectedException * @throws \PhpOffice\PhpSpreadsheet\Reader\Exception * @throws InvalidArgumentException */ private function getReader($import, $filePath, ?string $readerType = null, ?string $disk = null): IReader { $shouldQueue = $import instanceof ShouldQueue; if ($shouldQueue && !$import instanceof WithChunkReading) { throw new InvalidArgumentException('ShouldQueue is only supported in combination with WithChunkReading.'); } if ($import instanceof WithEvents) { $this->registerListeners($import->registerEvents()); } if ($import instanceof WithCustomValueBinder) { Cell::setValueBinder($import); } $fileExtension = pathinfo($filePath, PATHINFO_EXTENSION); $temporaryFile = $shouldQueue ? $this->temporaryFileFactory->make($fileExtension) : $this->temporaryFileFactory->makeLocal(null, $fileExtension); $this->currentFile = $temporaryFile->copyFrom( $filePath, $disk ); return ReaderFactory::make( $import, $this->currentFile, $readerType ); } /** * Garbage collect. */ private function garbageCollect() { $this->clearListeners(); $this->setDefaultValueBinder(); // Force garbage collecting unset($this->sheetImports, $this->spreadsheet); $this->currentFile->delete(); } } ================================================ FILE: src/RegistersCustomConcerns.php ================================================ Writer::class, BeforeExport::class => Writer::class, BeforeSheet::class => Sheet::class, AfterSheet::class => Sheet::class, ]; /** * @param string $concern * @param callable $handler * @param string $event */ public static function extend(string $concern, callable $handler, string $event = BeforeWriting::class) { /** @var HasEventBus $delegate */ $delegate = static::$eventMap[$event] ?? BeforeWriting::class; $delegate::listen($event, function (Event $event) use ($concern, $handler) { if ($event->appliesToConcern($concern)) { $handler($event->getConcernable(), $event->getDelegate()); } }); } } ================================================ FILE: src/Row.php ================================================ row = $row; $this->headingRow = $headingRow; $this->headerIsGrouped = $headerIsGrouped; } /** * @return SpreadsheetRow */ public function getDelegate(): SpreadsheetRow { return $this->row; } /** * @param null $nullValue * @param bool $calculateFormulas * @param bool $formatData * @param string|null $endColumn * @return Collection */ public function toCollection($nullValue = null, $calculateFormulas = false, $formatData = true, ?string $endColumn = null): Collection { return new Collection($this->toArray($nullValue, $calculateFormulas, $formatData, $endColumn)); } /** * @param null $nullValue * @param bool $calculateFormulas * @param bool $formatData * @param string|null $endColumn * @return array */ public function toArray($nullValue = null, $calculateFormulas = false, $formatData = true, ?string $endColumn = null) { if (is_array($this->rowCache) && ($this->rowCacheFormatData === $formatData) && ($this->rowCacheEndColumn === $endColumn)) { return $this->rowCache; } $cells = []; $i = 0; foreach ($this->row->getCellIterator('A', $endColumn) as $cell) { $value = (new Cell($cell))->getValue($nullValue, $calculateFormulas, $formatData); if (isset($this->headingRow[$i])) { if (!$this->headerIsGrouped[$i]) { $cells[$this->headingRow[$i]] = $value; } else { $cells[$this->headingRow[$i]][] = $value; } } else { $cells[] = $value; } $i++; } if (isset($this->preparationCallback)) { $cells = ($this->preparationCallback)($cells, $this->row->getRowIndex()); } $this->rowCache = $cells; $this->rowCacheFormatData = $formatData; $this->rowCacheEndColumn = $endColumn; return $cells; } /** * @param bool $calculateFormulas * @param string|null $endColumn * @return bool */ public function isEmpty($calculateFormulas = false, ?string $endColumn = null): bool { return count(array_filter($this->toArray(null, $calculateFormulas, false, $endColumn))) === 0; } /** * @return int */ public function getIndex(): int { return $this->row->getRowIndex(); } #[\ReturnTypeWillChange] public function offsetExists($offset) { return isset($this->toArray()[$offset]); } #[\ReturnTypeWillChange] public function offsetGet($offset) { return $this->toArray()[$offset]; } #[\ReturnTypeWillChange] public function offsetSet($offset, $value) { // } #[\ReturnTypeWillChange] public function offsetUnset($offset) { // } /** * @param \Closure $preparationCallback * * @internal */ public function setPreparationCallback(?Closure $preparationCallback = null) { $this->preparationCallback = $preparationCallback; } } ================================================ FILE: src/SettingsProvider.php ================================================ cache = $cache; } /** * Provide PhpSpreadsheet settings. */ public function provide() { $this->configureCellCaching(); } protected function configureCellCaching() { Settings::setCache( $this->cache->driver() ); } } ================================================ FILE: src/Sheet.php ================================================ worksheet = $worksheet; $this->chunkSize = config('excel.exports.chunk_size', 100); $this->temporaryFileFactory = app(TemporaryFileFactory::class); } /** * @param Spreadsheet $spreadsheet * @param string|int $index * @return Sheet * * @throws \PhpOffice\PhpSpreadsheet\Exception * @throws SheetNotFoundException */ public static function make(Spreadsheet $spreadsheet, $index) { if (is_numeric($index)) { return self::byIndex($spreadsheet, $index); } return self::byName($spreadsheet, $index); } /** * @param Spreadsheet $spreadsheet * @param int $index * @return Sheet * * @throws \PhpOffice\PhpSpreadsheet\Exception * @throws SheetNotFoundException */ public static function byIndex(Spreadsheet $spreadsheet, int $index): Sheet { if (!isset($spreadsheet->getAllSheets()[$index])) { throw SheetNotFoundException::byIndex($index, $spreadsheet->getSheetCount()); } return new static($spreadsheet->getSheet($index)); } /** * @param Spreadsheet $spreadsheet * @param string $name * @return Sheet * * @throws SheetNotFoundException */ public static function byName(Spreadsheet $spreadsheet, string $name): Sheet { if (!$spreadsheet->sheetNameExists($name)) { throw SheetNotFoundException::byName($name); } return new static($spreadsheet->getSheetByName($name)); } /** * @param object $sheetExport * * @throws \PhpOffice\PhpSpreadsheet\Exception */ public function open($sheetExport) { $this->exportable = $sheetExport; if ($sheetExport instanceof WithCustomValueBinder) { SpreadsheetCell::setValueBinder($sheetExport); } if ($sheetExport instanceof WithEvents) { $this->registerListeners($sheetExport->registerEvents()); } $this->raise(new BeforeSheet($this, $this->exportable)); if ($sheetExport instanceof WithTitle) { $title = $sheetExport->title(); $title = str_replace(['*', ':', '/', '\\', '?', '[', ']'], '', $title); if (StringHelper::countCharacters($title) > Worksheet::SHEET_TITLE_MAXIMUM_LENGTH) { $title = StringHelper::substring($title, 0, Worksheet::SHEET_TITLE_MAXIMUM_LENGTH); } $this->worksheet->setTitle($title); } if (($sheetExport instanceof FromQuery || $sheetExport instanceof FromCollection || $sheetExport instanceof FromArray) && $sheetExport instanceof FromView) { throw ConcernConflictException::queryOrCollectionAndView(); } if (!$sheetExport instanceof FromView && $sheetExport instanceof WithHeadings) { if ($sheetExport instanceof WithCustomStartCell) { $startCell = $sheetExport->startCell(); } $this->append( ArrayHelper::ensureMultipleRows($sheetExport->headings()), $startCell ?? null, $this->hasStrictNullComparison($sheetExport) ); } } /** * @param object $sheetExport * * @throws \PhpOffice\PhpSpreadsheet\Exception * @throws \PhpOffice\PhpSpreadsheet\Reader\Exception */ public function export($sheetExport) { $this->open($sheetExport); if ($sheetExport instanceof FromView) { $this->fromView($sheetExport); } else { if ($sheetExport instanceof FromQuery) { $this->fromQuery($sheetExport, $this->worksheet); } if ($sheetExport instanceof FromCollection) { $this->fromCollection($sheetExport); } if ($sheetExport instanceof FromArray) { $this->fromArray($sheetExport); } if ($sheetExport instanceof FromIterator) { $this->fromIterator($sheetExport); } if ($sheetExport instanceof FromGenerator) { $this->fromGenerator($sheetExport); } } $this->close($sheetExport); } /** * @param object $import * @param int $startRow */ public function import($import, int $startRow = 1) { if ($import instanceof WithEvents) { $this->registerListeners($import->registerEvents()); } $this->raise(new BeforeSheet($this, $import)); if ($import instanceof WithProgressBar && !$import instanceof WithChunkReading) { $import->getConsoleOutput()->progressStart($this->worksheet->getHighestRow()); } $calculatesFormulas = $import instanceof WithCalculatedFormulas; $formatData = $import instanceof WithFormatData; if ($import instanceof WithMappedCells) { app(MappedReader::class)->map($import, $this->worksheet); } else { if ($import instanceof ToModel) { app(ModelImporter::class)->import($this->worksheet, $import, $startRow); } if ($import instanceof ToCollection) { $rows = $this->toCollection($import, $startRow, null, $calculatesFormulas, $formatData); if ($import instanceof WithValidation) { $rows = $this->validated($import, $startRow, $rows); } $import->collection($rows); } if ($import instanceof ToArray) { $rows = $this->toArray($import, $startRow, null, $calculatesFormulas, $formatData); if ($import instanceof WithValidation) { $rows = $this->validated($import, $startRow, $rows); } $import->array($rows); } } if ($import instanceof OnEachRow) { $headingRow = HeadingRowExtractor::extract($this->worksheet, $import); $headerIsGrouped = HeadingRowExtractor::extractGrouping($headingRow, $import); $endColumn = $import instanceof WithColumnLimit ? $import->endColumn() : null; $preparationCallback = $this->getPreparationCallback($import); foreach ($this->worksheet->getRowIterator()->resetStart($startRow ?? 1) as $row) { $sheetRow = new Row($row, $headingRow, $headerIsGrouped); if ($import instanceof WithValidation) { $sheetRow->setPreparationCallback($preparationCallback); } $rowArray = $sheetRow->toArray(null, $import instanceof WithCalculatedFormulas, $import instanceof WithFormatData, $endColumn); $rowIsEmptyAccordingToImport = $import instanceof SkipsEmptyRows && method_exists($import, 'isEmptyWhen') && $import->isEmptyWhen($rowArray); if (!$import instanceof SkipsEmptyRows || ($import instanceof SkipsEmptyRows && (!$rowIsEmptyAccordingToImport && !$sheetRow->isEmpty($calculatesFormulas)))) { if ($import instanceof WithValidation) { $toValidate = [$sheetRow->getIndex() => $rowArray]; try { app(RowValidator::class)->validate($toValidate, $import); $import->onRow($sheetRow); } catch (RowSkippedException $e) { } catch (Throwable $e) { if ($import instanceof SkipsOnError) { $import->onError($e); } else { throw $e; } } } else { try { $import->onRow($sheetRow); } catch (Throwable $e) { if ($import instanceof SkipsOnError) { $import->onError($e); } else { throw $e; } } } } if ($import instanceof WithProgressBar) { $import->getConsoleOutput()->progressAdvance(); } } } $this->raise(new AfterSheet($this, $import)); if ($import instanceof WithProgressBar && !$import instanceof WithChunkReading) { $import->getConsoleOutput()->progressFinish(); } } /** * @param object $import * @param int|null $startRow * @param null $nullValue * @param bool $calculateFormulas * @param bool $formatData * @return array */ public function toArray($import, ?int $startRow = null, $nullValue = null, $calculateFormulas = false, $formatData = false) { if ($startRow > $this->worksheet->getHighestRow()) { return []; } $endRow = EndRowFinder::find($import, $startRow, $this->worksheet->getHighestRow()); $headingRow = HeadingRowExtractor::extract($this->worksheet, $import); $headerIsGrouped = HeadingRowExtractor::extractGrouping($headingRow, $import); $endColumn = $import instanceof WithColumnLimit ? $import->endColumn() : null; $rows = []; foreach ($this->worksheet->getRowIterator($startRow, $endRow) as $index => $row) { $row = new Row($row, $headingRow, $headerIsGrouped); if ($import instanceof SkipsEmptyRows && $row->isEmpty($calculateFormulas, $endColumn)) { continue; } $row = $row->toArray($nullValue, $calculateFormulas, $formatData, $endColumn); if ($import && method_exists($import, 'isEmptyWhen') && $import->isEmptyWhen($row)) { continue; } if ($import instanceof WithMapping) { $row = $import->map($row); } if ($import instanceof WithValidation && method_exists($import, 'prepareForValidation')) { $row = $import->prepareForValidation($row, $index); } $rows[] = $row; if ($import instanceof WithProgressBar) { $import->getConsoleOutput()->progressAdvance(); } } return $rows; } /** * @param object $import * @param int|null $startRow * @param null $nullValue * @param bool $calculateFormulas * @param bool $formatData * @return Collection */ public function toCollection($import, ?int $startRow = null, $nullValue = null, $calculateFormulas = false, $formatData = false): Collection { $rows = $this->toArray($import, $startRow, $nullValue, $calculateFormulas, $formatData); return new Collection(array_map(function (array $row) { return new Collection($row); }, $rows)); } /** * @param object $sheetExport * * @throws \PhpOffice\PhpSpreadsheet\Exception */ public function close($sheetExport) { if ($sheetExport instanceof WithCharts) { $this->addCharts($sheetExport->charts()); } if ($sheetExport instanceof WithDrawings) { $this->addDrawings($sheetExport->drawings()); } $this->exportable = $sheetExport; if ($sheetExport instanceof WithColumnFormatting) { foreach ($sheetExport->columnFormats() as $column => $format) { $this->formatColumn($column, $format); } } if ($sheetExport instanceof ShouldAutoSize) { $this->autoSize(); } if ($sheetExport instanceof WithColumnWidths) { foreach ($sheetExport->columnWidths() as $column => $width) { $this->worksheet->getColumnDimension($column)->setAutoSize(false)->setWidth($width); } } if ($sheetExport instanceof WithStyles) { $styles = $sheetExport->styles($this->worksheet); if (is_array($styles)) { foreach ($styles as $coordinate => $coordinateStyles) { if (is_numeric($coordinate)) { $coordinate = 'A' . $coordinate . ':' . $this->worksheet->getHighestColumn($coordinate) . $coordinate; } $this->worksheet->getStyle($coordinate)->applyFromArray($coordinateStyles); } } } $this->raise(new AfterSheet($this, $this->exportable)); $this->clearListeners(); } /** * @param FromView $sheetExport * @param int|null $sheetIndex * * @throws \PhpOffice\PhpSpreadsheet\Reader\Exception */ public function fromView(FromView $sheetExport, $sheetIndex = null) { $temporaryFile = $this->temporaryFileFactory->makeLocal(null, 'html'); $temporaryFile->put($sheetExport->view()->render()); $spreadsheet = $this->worksheet->getParent(); /** @var Html $reader */ $reader = IOFactory::createReader('Html'); // If no sheetIndex given, insert content into the last sheet $reader->setSheetIndex($sheetIndex ?? $spreadsheet->getSheetCount() - 1); $reader->loadIntoExisting($temporaryFile->getLocalPath(), $spreadsheet); $temporaryFile->delete(); } /** * @param FromQuery $sheetExport * @param Worksheet $worksheet */ public function fromQuery(FromQuery $sheetExport, Worksheet $worksheet) { $query = $sheetExport->query(); if ($query instanceof \Laravel\Scout\Builder) { $this->fromScout($sheetExport, $worksheet); return; } //Operate on a clone to avoid altering the original //and use the clone operator directly to support old versions of Laravel //that don't have a clone method in eloquent $clonedQuery = clone $query; $clonedQuery->chunk($this->getChunkSize($sheetExport), function ($chunk) use ($sheetExport) { $this->appendRows($chunk, $sheetExport); }); } /** * @param FromQuery $sheetExport * @param Worksheet $worksheet */ public function fromScout(FromQuery $sheetExport, Worksheet $worksheet) { $scout = $sheetExport->query(); $chunkSize = $this->getChunkSize($sheetExport); $chunk = $scout->paginate($chunkSize); // Append first page $this->appendRows($chunk->items(), $sheetExport); // Append rest of pages for ($page = 2; $page <= $chunk->lastPage(); $page++) { $this->appendRows($scout->paginate($chunkSize, 'page', $page)->items(), $sheetExport); } } /** * @param FromCollection $sheetExport */ public function fromCollection(FromCollection $sheetExport) { $this->appendRows($sheetExport->collection()->all(), $sheetExport); } /** * @param FromArray $sheetExport */ public function fromArray(FromArray $sheetExport) { $this->appendRows($sheetExport->array(), $sheetExport); } /** * @param FromIterator $sheetExport */ public function fromIterator(FromIterator $sheetExport) { $iterator = class_exists(LazyCollection::class) ? new LazyCollection(function () use ($sheetExport) { foreach ($sheetExport->iterator() as $row) { yield $row; } }) : $sheetExport->iterator(); $this->appendRows($iterator, $sheetExport); } /** * @param FromGenerator $sheetExport */ public function fromGenerator(FromGenerator $sheetExport) { $generator = class_exists(LazyCollection::class) ? new LazyCollection(function () use ($sheetExport) { foreach ($sheetExport->generator() as $row) { yield $row; } }) : $sheetExport->generator(); $this->appendRows($generator, $sheetExport); } /** * @param array $rows * @param string|null $startCell * @param bool $strictNullComparison */ public function append(array $rows, ?string $startCell = null, bool $strictNullComparison = false) { if (!$startCell) { $startCell = 'A1'; } if ($this->hasRows()) { $startCell = CellHelper::getColumnFromCoordinate($startCell) . ($this->worksheet->getHighestRow() + 1); } $this->worksheet->fromArray($rows, null, $startCell, $strictNullComparison); } public function autoSize() { foreach ($this->buildColumnRange('A', $this->worksheet->getHighestDataColumn()) as $col) { $dimension = $this->worksheet->getColumnDimension($col); // Only auto-size columns that have not have an explicit width. if ($dimension->getWidth() == -1) { $dimension->setAutoSize(true); } } } /** * @param string $column * @param string $format * * @throws \PhpOffice\PhpSpreadsheet\Exception */ public function formatColumn(string $column, string $format) { // If the column is a range, we wouldn't need to calculate the range. if (stripos($column, ':') !== false) { $this->worksheet ->getStyle($column) ->getNumberFormat() ->setFormatCode($format); } else { $this->worksheet ->getStyle($column . '1:' . $column . $this->worksheet->getHighestRow()) ->getNumberFormat() ->setFormatCode($format); } } /** * @param int $chunkSize * @return Sheet */ public function chunkSize(int $chunkSize) { $this->chunkSize = $chunkSize; return $this; } /** * @return Worksheet */ public function getDelegate() { return $this->worksheet; } /** * @param Chart|Chart[] $charts */ public function addCharts($charts) { $charts = \is_array($charts) ? $charts : [$charts]; foreach ($charts as $chart) { $this->worksheet->addChart($chart); } } /** * @param BaseDrawing|BaseDrawing[] $drawings */ public function addDrawings($drawings) { $drawings = \is_array($drawings) ? $drawings : [$drawings]; foreach ($drawings as $drawing) { $drawing->setWorksheet($this->worksheet); } } /** * @param string $concern * @return string */ public function hasConcern(string $concern): string { return $this->exportable instanceof $concern; } /** * @param iterable $rows * @param object $sheetExport */ public function appendRows($rows, $sheetExport) { if (method_exists($sheetExport, 'prepareRows')) { $rows = $sheetExport->prepareRows($rows); } $rows = $rows instanceof LazyCollection ? $rows : new Collection($rows); $rows->flatMap(function ($row) use ($sheetExport) { if ($sheetExport instanceof WithMapping) { $row = $sheetExport->map($row); } if ($sheetExport instanceof WithCustomValueBinder) { SpreadsheetCell::setValueBinder($sheetExport); } return ArrayHelper::ensureMultipleRows( static::mapArraybleRow($row) ); })->chunk(1000)->each(function ($rows) use ($sheetExport) { $this->append( $rows->toArray(), $sheetExport instanceof WithCustomStartCell ? $sheetExport->startCell() : null, $this->hasStrictNullComparison($sheetExport) ); }); } /** * @param mixed $row * @return array */ public static function mapArraybleRow($row): array { // When dealing with eloquent models, we'll skip the relations // as we won't be able to display them anyway. if (is_object($row) && method_exists($row, 'attributesToArray')) { return $row->attributesToArray(); } if ($row instanceof Arrayable) { return $row->toArray(); } // Convert StdObjects to arrays if (is_object($row)) { return json_decode(json_encode($row), true); } return $row; } /** * @param $sheetImport * @return int */ public function getStartRow($sheetImport): int { return HeadingRowExtractor::determineStartRow($sheetImport); } /** * Disconnect the sheet. */ public function disconnect() { $this->worksheet->disconnectCells(); unset($this->worksheet); } /** * @return Collection|array */ protected function validated(WithValidation $import, int $startRow, $rows) { $toValidate = (new Collection($rows))->mapWithKeys(function ($row, $index) use ($startRow) { return [($startRow + $index) => $row]; }); try { app(RowValidator::class)->validate($toValidate->toArray(), $import); } catch (RowSkippedException $e) { foreach ($e->skippedRows() as $row) { unset($rows[$row - $startRow]); } } return $rows; } /** * @param string $lower * @param string $upper * @return \Generator */ protected function buildColumnRange(string $lower, string $upper) { $upper++; for ($i = $lower; $i !== $upper; $i++) { yield $i; } } /** * @return bool */ private function hasRows(): bool { $startCell = 'A1'; if ($this->exportable instanceof WithCustomStartCell) { $startCell = $this->exportable->startCell(); } return $this->worksheet->cellExists($startCell); } /** * @param object $sheetExport * @return bool */ private function hasStrictNullComparison($sheetExport): bool { if ($sheetExport instanceof WithStrictNullComparison) { return true; } return config('excel.exports.strict_null_comparison', false); } /** * @param object|WithCustomChunkSize $export * @return int */ private function getChunkSize($export): int { if ($export instanceof WithCustomChunkSize) { return $export->chunkSize(); } return $this->chunkSize; } /** * @param object|WithValidation $import * @return Closure|null */ private function getPreparationCallback($import) { if (!$import instanceof WithValidation || !method_exists($import, 'prepareForValidation')) { return null; } return function (array $data, int $index) use ($import) { return $import->prepareForValidation($data, $index); }; } } ================================================ FILE: src/Transactions/DbTransactionHandler.php ================================================ connection = $connection; } /** * @param callable $callback * @return mixed * * @throws \Throwable */ public function __invoke(callable $callback) { return $this->connection->transaction($callback); } } ================================================ FILE: src/Transactions/NullTransactionHandler.php ================================================ row = $row; $this->attribute = $attribute; $this->errors = $errors; $this->values = $values; } /** * @return int */ public function row(): int { return $this->row; } /** * @return string */ public function attribute(): string { return $this->attribute; } /** * @return array */ public function errors(): array { return $this->errors; } /** * @return array */ public function values(): array { return $this->values; } /** * @return array */ public function toArray() { return collect($this->errors)->map(function ($message) { return __('There was an error on row :row. :message', ['row' => $this->row, 'message' => $message]); })->all(); } /** * @return array */ #[\ReturnTypeWillChange] public function jsonSerialize() { return [ 'row' => $this->row(), 'attribute' => $this->attribute(), 'errors' => $this->errors(), 'values' => $this->values(), ]; } } ================================================ FILE: src/Validators/RowValidator.php ================================================ validator = $validator; } /** * @param array $rows * @param WithValidation $import * * @throws ValidationException * @throws RowSkippedException */ public function validate(array $rows, WithValidation $import) { $rules = $this->rules($import); $messages = $this->messages($import); $attributes = $this->attributes($import); try { $validator = $this->validator->make($rows, $rules, $messages, $attributes); if (method_exists($import, 'withValidator')) { $import->withValidator($validator); } $validator->validate(); } catch (IlluminateValidationException $e) { $failures = []; foreach ($e->errors() as $attribute => $messages) { $row = strtok($attribute, '.'); $attributeName = strtok(''); $attributeName = $attributes['*.' . $attributeName] ?? $attributeName; $failures[] = new Failure( $row, $attributeName, str_replace($attribute, $attributeName, $messages), $rows[$row] ?? [] ); } if ($import instanceof SkipsOnFailure) { $import->onFailure(...$failures); throw new RowSkippedException(...$failures); } throw new ValidationException( $e, $failures ); } } /** * @param WithValidation $import * @return array */ private function messages(WithValidation $import): array { return method_exists($import, 'customValidationMessages') ? $this->formatKey($import->customValidationMessages()) : []; } /** * @param WithValidation $import * @return array */ private function attributes(WithValidation $import): array { return method_exists($import, 'customValidationAttributes') ? $this->formatKey($import->customValidationAttributes()) : []; } /** * @param WithValidation $import * @return array */ private function rules(WithValidation $import): array { return $this->formatKey($import->rules()); } /** * @param array $elements * @return array */ private function formatKey(array $elements): array { return collect($elements)->mapWithKeys(function ($rule, $attribute) { $attribute = Str::startsWith($attribute, '*.') ? $attribute : '*.' . $attribute; return [$attribute => $this->formatRule($rule)]; })->all(); } /** * @param string|object|callable|array $rules * @return string|array */ private function formatRule($rules) { if (is_array($rules)) { foreach ($rules as $rule) { $formatted[] = $this->formatRule($rule); } return $formatted ?? []; } if (is_object($rules) || is_callable($rules)) { return $rules; } if (Str::contains($rules, 'required_without') && preg_match('/(.*?):(.*)/', $rules, $matches)) { $column = array_map(function ($match) { return Str::startsWith($match, '*.') ? $match : '*.' . $match; }, explode(',', $matches[2])); return $matches[1] . ':' . implode(',', $column); } if (Str::contains($rules, 'required_') && preg_match('/(.*?):(.*),(.*)/', $rules, $matches)) { $column = Str::startsWith($matches[2], '*.') ? $matches[2] : '*.' . $matches[2]; return $matches[1] . ':' . $column . ',' . $matches[3]; } return $rules; } } ================================================ FILE: src/Validators/ValidationException.php ================================================ validator, $previous->response, $previous->errorBag); $this->failures = $failures; } /** * @return string[] */ public function errors(): array { return collect($this->failures)->map->toArray()->all(); } /** * @return array */ public function failures(): array { return $this->failures; } } ================================================ FILE: src/Writer.php ================================================ temporaryFileFactory = $temporaryFileFactory; $this->setDefaultValueBinder(); } /** * @param object $export * @param string $writerType * @return TemporaryFile * * @throws \PhpOffice\PhpSpreadsheet\Exception */ public function export($export, string $writerType): TemporaryFile { $this->open($export); $sheetExports = [$export]; if ($export instanceof WithMultipleSheets) { $sheetExports = $export->sheets(); } foreach ($sheetExports as $sheetExport) { $this->addNewSheet()->export($sheetExport); } return $this->write($export, $this->temporaryFileFactory->makeLocal(null, strtolower($writerType)), $writerType); } /** * @param object $export * @return $this */ public function open($export) { $this->exportable = $export; if ($export instanceof WithEvents) { $this->registerListeners($export->registerEvents()); } $this->exportable = $export; $this->spreadsheet = new Spreadsheet; $this->spreadsheet->disconnectWorksheets(); if ($export instanceof WithCustomValueBinder) { Cell::setValueBinder($export); } $this->handleDocumentProperties($export); if ($export instanceof WithBackgroundColor) { $defaultStyle = $this->spreadsheet->getDefaultStyle(); $backgroundColor = $export->backgroundColor(); if (is_string($backgroundColor)) { $defaultStyle->getFill()->setFillType(Fill::FILL_SOLID)->getStartColor()->setRGB($backgroundColor); } if (is_array($backgroundColor)) { $defaultStyle->applyFromArray(['fill' => $backgroundColor]); } if ($backgroundColor instanceof Color) { $defaultStyle->getFill()->setFillType(Fill::FILL_SOLID)->setStartColor($backgroundColor); } } if ($export instanceof WithDefaultStyles) { $defaultStyle = $this->spreadsheet->getDefaultStyle(); $styles = $export->defaultStyles($defaultStyle); if (is_array($styles)) { $defaultStyle->applyFromArray($styles); } } $this->raise(new BeforeExport($this, $this->exportable)); return $this; } /** * @param TemporaryFile $tempFile * @param string $writerType * @return Writer * * @throws \PhpOffice\PhpSpreadsheet\Reader\Exception */ public function reopen(TemporaryFile $tempFile, string $writerType) { $reader = IOFactory::createReader($writerType); $this->spreadsheet = $reader->load($tempFile->sync()->getLocalPath()); return $this; } /** * Determine if the application is running in a serverless environment. * * @return bool */ public function isRunningServerless(): bool { return isset($_ENV['AWS_LAMBDA_RUNTIME_API']); } /** * @param object $export * @param TemporaryFile $temporaryFile * @param string $writerType * @return TemporaryFile * * @throws \PhpOffice\PhpSpreadsheet\Writer\Exception * @throws \PhpOffice\PhpSpreadsheet\Exception */ public function write($export, TemporaryFile $temporaryFile, string $writerType): TemporaryFile { $this->exportable = $export; $this->spreadsheet->setActiveSheetIndex(0); $this->raise(new BeforeWriting($this, $this->exportable)); $writer = WriterFactory::make( $writerType, $this->spreadsheet, $export, $temporaryFile->getLocalPath() ); if ($temporaryFile instanceof RemoteTemporaryFile && !$temporaryFile->existsLocally() && !$this->isRunningServerless()) { // just ensure that local copy exists (it creates the directory structure), // no need to copy remote content since it will be overwritten below $temporaryFile->sync(false); } $writer->save( $temporaryFile->getLocalPath() ); if ($temporaryFile instanceof RemoteTemporaryFile) { $temporaryFile->updateRemote(); $temporaryFile->deleteLocalCopy(); } $this->clearListeners(); $this->spreadsheet->disconnectWorksheets(); unset($this->spreadsheet); return $temporaryFile; } /** * @param int|null $sheetIndex * @return Sheet * * @throws \PhpOffice\PhpSpreadsheet\Exception */ public function addNewSheet(?int $sheetIndex = null) { return new Sheet($this->spreadsheet->createSheet($sheetIndex)); } /** * @return Spreadsheet */ public function getDelegate() { return $this->spreadsheet; } /** * @return $this */ public function setDefaultValueBinder() { Cell::setValueBinder( app(config('excel.value_binder.default', DefaultValueBinder::class)) ); return $this; } /** * @param int $sheetIndex * @return Sheet * * @throws \PhpOffice\PhpSpreadsheet\Exception */ public function getSheetByIndex(int $sheetIndex) { return new Sheet($this->getDelegate()->getSheet($sheetIndex)); } /** * @param string $concern * @return bool */ public function hasConcern($concern): bool { return $this->exportable instanceof $concern; } /** * @param object $export */ protected function handleDocumentProperties($export) { $properties = config('excel.exports.properties', []); if ($export instanceof WithProperties) { $properties = array_merge($properties, $export->properties()); } if ($export instanceof WithTitle) { $properties = array_merge($properties, ['title' => $export->title()]); } $props = $this->spreadsheet->getProperties(); foreach (array_filter($properties) as $property => $value) { switch ($property) { case 'title': $props->setTitle($value); break; case 'description': $props->setDescription($value); break; case 'creator': $props->setCreator($value); break; case 'lastModifiedBy': $props->setLastModifiedBy($value); break; case 'subject': $props->setSubject($value); break; case 'keywords': $props->setKeywords($value); break; case 'category': $props->setCategory($value); break; case 'manager': $props->setManager($value); break; case 'company': $props->setCompany($value); break; } } } } ================================================ FILE: tests/Cache/BatchCacheTest.php ================================================ 'A1-value', 'A2' => 'A2-value', 'A3' => 'A3-value', ]; $cache = $this->givenCache($inMemory); $this->assertEquals( $inMemory, $cache->getMultiple(['A1', 'A2', 'A3']) ); $this->assertEquals('A3-value', $cache->get('A3')); } public function test_will_get_multiple_from_cache_if_cells_are_persisted() { $inMemory = []; $persisted = [ 'A1' => 'A1-value', 'A2' => 'A2-value', 'A3' => 'A3-value', ]; $cache = $this->givenCache($inMemory, $persisted); $this->assertEquals( $persisted, $cache->getMultiple(['A1', 'A2', 'A3']) ); $this->assertEquals('A3-value', $cache->get('A3')); } public function test_will_get_multiple_from_cache_and_persisted() { $inMemory = [ 'A1' => 'A1-value', 'A2' => 'A2-value', 'A3' => 'A3-value', ]; $persisted = [ 'A4' => 'A4-value', 'A5' => 'A5-value', 'A6' => 'A6-value', ]; $cache = $this->givenCache($inMemory, $persisted); $this->assertEquals( array_merge($inMemory, $persisted), $cache->getMultiple(['A1', 'A2', 'A3', 'A4', 'A5', 'A6']) ); $this->assertEquals('A3-value', $cache->get('A3')); $this->assertEquals('A6-value', $cache->get('A6')); } public function test_it_persists_to_cache_when_memory_limit_reached_on_setting_a_value() { $memoryLimit = 3; $persisted = []; $inMemory = [ 'A1' => 'A1-value', 'A2' => 'A2-value', 'A3' => 'A3-value', ]; $cache = $this->givenCache($inMemory, $persisted, $memoryLimit); // Setting a 4th value will reach the memory limit $cache->set('A4', 'A4-value', 10000); // Nothing in memory anymore $this->assertEquals([], array_filter($this->memory->getMultiple(['A1', 'A2', 'A3', 'A4']))); // All 4 cells show be persisted $this->assertEquals([ 'A1' => 'A1-value', 'A2' => 'A2-value', 'A3' => 'A3-value', 'A4' => 'A4-value', ], $this->cache->getMultiple(['A1', 'A2', 'A3', 'A4'])); // Batch cache should return all 4 cells $this->assertEquals([ 'A1' => 'A1-value', 'A2' => 'A2-value', 'A3' => 'A3-value', 'A4' => 'A4-value', ], $cache->getMultiple(['A1', 'A2', 'A3', 'A4'])); } public function test_it_persists_to_cache_when_memory_limit_reached_on_setting_multiple_values() { $memoryLimit = 3; $persisted = []; $inMemory = [ 'A1' => 'A1-value', 'A2' => 'A2-value', 'A3' => 'A3-value', ]; $cache = $this->givenCache($inMemory, $persisted, $memoryLimit); // Setting a 4th value will reach the memory limit $cache->setMultiple([ 'A4' => 'A4-value', 'A5' => 'A5-value', ], 10000); // Nothing in memory anymore $this->assertEquals([], array_filter($this->memory->getMultiple(['A1', 'A2', 'A3', 'A4', 'A5']))); // All 4 cells show be persisted $this->assertEquals([ 'A1' => 'A1-value', 'A2' => 'A2-value', 'A3' => 'A3-value', 'A4' => 'A4-value', 'A5' => 'A5-value', ], $this->cache->getMultiple(['A1', 'A2', 'A3', 'A4', 'A5'])); // Batch cache should return all 4 cells $this->assertEquals([ 'A1' => 'A1-value', 'A2' => 'A2-value', 'A3' => 'A3-value', 'A4' => 'A4-value', 'A5' => 'A5-value', ], $cache->getMultiple(['A1', 'A2', 'A3', 'A4', 'A5'])); } /** * @dataProvider defaultTTLDataProvider */ #[DataProvider('defaultTTLDataProvider')] public function test_it_writes_to_cache_with_default_ttl($defaultTTL, $receivedAs) { config()->set('excel.cache.default_ttl', $defaultTTL); $cache = $this->givenCache(['A1' => 'A1-value'], [], 1); $this->cache->setEventDispatcher(Event::fake()); $cache->set('A2', 'A2-value'); $expectedTTL = value($receivedAs); $dispatchedCollection = Event::dispatched( KeyWritten::class, function (KeyWritten $event) use ($expectedTTL) { return $event->seconds === $expectedTTL; }); $this->assertCount(2, $dispatchedCollection); } public function test_it_writes_to_cache_with_a_dateinterval_ttl() { // DateInterval is 1 minute config()->set('excel.cache.default_ttl', new DateInterval('PT1M')); $cache = $this->givenCache(['A1' => 'A1-value'], [], 1); $this->cache->setEventDispatcher(Event::fake()); $cache->set('A2', 'A2-value'); $dispatchedCollection = Event::dispatched( KeyWritten::class, function (KeyWritten $event) { return $event->seconds >= 59 && $event->seconds <= 60; }); $this->assertCount(2, $dispatchedCollection); } public function test_it_can_override_default_ttl() { config()->set('excel.cache.default_ttl', 1); $cache = $this->givenCache(['A1' => 'A1-value'], [], 1); $this->cache->setEventDispatcher(Event::fake()); $cache->set('A2', 'A2-value', null); $dispatchedCollection = Event::dispatched( KeyWritten::class, function (KeyWritten $event) { return $event->seconds === null; }); $this->assertCount(2, $dispatchedCollection); } public static function defaultTTLDataProvider(): array { return [ 'null (forever)' => [null, null], 'int value' => [$value = rand(1, 100), $value], 'callable' => [$closure = function () { return 199; }, $closure], ]; } /** * Construct a BatchCache with a in memory store * and an array cache, pretending to be a persistence store. * * @param array $memory * @param array $persisted * @param int|null $memoryLimit * @return CacheInterface */ private function givenCache(array $memory = [], array $persisted = [], $memoryLimit = null): CacheInterface { config()->set('excel.cache.batch.memory_limit', $memoryLimit ?: 60000); $this->memory = $this->app->make(CacheManager::class)->createMemoryDriver(); $this->memory->setMultiple($memory); $store = new ArrayStore(); $store->putMany($persisted, 10000); $this->cache = new Repository($store); if (!InstalledVersions::satisfies(new VersionParser, 'psr/simple-cache', '^3.0')) { return new BatchCacheDeprecated( $this->cache, $this->memory, config('excel.cache.default_ttl') ); } return new BatchCache( $this->cache, $this->memory, config('excel.cache.default_ttl') ); } } ================================================ FILE: tests/CellTest.php ================================================ set('excel.imports.cells.middleware', []); $worksheet = $this->read(__DIR__ . '/Data/Disks/Local/import-middleware.xlsx', 'Xlsx'); $this->assertEquals('test', Cell::make($worksheet->getActiveSheet(), 'A1')->getValue()); // By default spaces are not removed $this->assertEquals(' ', Cell::make($worksheet->getActiveSheet(), 'A2')->getValue()); } public function test_can_trim_empty_cells() { config()->set('excel.imports.cells.middleware', [ TrimCellValue::class, ]); $worksheet = $this->read(__DIR__ . '/Data/Disks/Local/import-middleware.xlsx', 'Xlsx'); $this->assertEquals('', Cell::make($worksheet->getActiveSheet(), 'A2')->getValue()); config()->set('excel.imports.cells.middleware', []); } public function test_convert_empty_cells_to_null() { config()->set('excel.imports.cells.middleware', [ TrimCellValue::class, ConvertEmptyCellValuesToNull::class, ]); $worksheet = $this->read(__DIR__ . '/Data/Disks/Local/import-middleware.xlsx', 'Xlsx'); $this->assertEquals(null, Cell::make($worksheet->getActiveSheet(), 'A2')->getValue()); config()->set('excel.imports.cells.middleware', []); } } ================================================ FILE: tests/Concerns/ExportableTest.php ================================================ expectException(\Maatwebsite\Excel\Exceptions\NoFilenameGivenException::class); $this->expectExceptionMessage('A filename needs to be passed in order to download the export'); $export = new class { use Exportable; }; $export->download(); } public function test_needs_to_have_a_file_name_when_storing() { $this->expectException(\Maatwebsite\Excel\Exceptions\NoFilePathGivenException::class); $this->expectExceptionMessage('A filepath needs to be passed in order to store the export'); $export = new class { use Exportable; }; $export->store(); } public function test_needs_to_have_a_file_name_when_queuing() { $this->expectException(\Maatwebsite\Excel\Exceptions\NoFilePathGivenException::class); $this->expectExceptionMessage('A filepath needs to be passed in order to store the export'); $export = new class { use Exportable; }; $export->queue(); } public function test_responsable_needs_to_have_file_name_configured_inside_the_export() { $this->expectException(\Maatwebsite\Excel\Exceptions\NoFilenameGivenException::class); $this->expectExceptionMessage('A filename needs to be passed in order to download the export'); $export = new class implements Responsable { use Exportable; }; $export->toResponse(new Request()); } public function test_is_responsable() { $export = new class implements Responsable { use Exportable; protected $fileName = 'export.xlsx'; }; $this->assertInstanceOf(Responsable::class, $export); $response = $export->toResponse(new Request()); $this->assertInstanceOf(BinaryFileResponse::class, $response); } public function test_can_have_customized_header() { $export = new class { use Exportable; }; $response = $export->download( 'name.csv', Excel::CSV, [ 'Content-Type' => 'text/csv', ] ); $this->assertEquals('text/csv', $response->headers->get('Content-Type')); } public function test_can_set_custom_headers_in_export_class() { $export = new class { use Exportable; protected $fileName = 'name.csv'; protected $writerType = Excel::CSV; protected $headers = [ 'Content-Type' => 'text/csv', ]; }; $response = $export->toResponse(request()); $this->assertEquals('text/csv', $response->headers->get('Content-Type')); } public function test_can_get_raw_export_contents() { $export = new EmptyExport; $response = $export->raw(Excel::XLSX); $this->assertNotEmpty($response); } public function test_can_have_customized_disk_options_when_storing() { $export = new EmptyExport; $this->mock(Exporter::class) ->shouldReceive('store')->once() ->with($export, 'name.csv', 's3', Excel::CSV, ['visibility' => 'private']); $export->store('name.csv', 's3', Excel::CSV, ['visibility' => 'private']); } public function test_can_have_customized_disk_options_when_queueing() { $export = new EmptyExport; $this->mock(Exporter::class) ->shouldReceive('queue')->once() ->with($export, 'name.csv', 's3', Excel::CSV, ['visibility' => 'private']); $export->queue('name.csv', 's3', Excel::CSV, ['visibility' => 'private']); } public function test_can_set_disk_options_in_export_class_when_storing() { $export = new class { use Exportable; public $disk = 's3'; public $writerType = Excel::CSV; public $diskOptions = ['visibility' => 'private']; }; $this->mock(Exporter::class) ->shouldReceive('store')->once() ->with($export, 'name.csv', 's3', Excel::CSV, ['visibility' => 'private']); $export->store('name.csv'); } public function test_can_set_disk_options_in_export_class_when_queuing() { $export = new class { use Exportable; public $disk = 's3'; public $writerType = Excel::CSV; public $diskOptions = ['visibility' => 'private']; }; $this->mock(Exporter::class) ->shouldReceive('queue')->once() ->with($export, 'name.csv', 's3', Excel::CSV, ['visibility' => 'private']); $export->queue('name.csv'); } public function test_can_override_export_class_disk_options_when_calling_store() { $export = new class { use Exportable; public $diskOptions = ['visibility' => 'public']; }; $this->mock(Exporter::class) ->shouldReceive('store')->once() ->with($export, 'name.csv', 's3', Excel::CSV, ['visibility' => 'private']); $export->store('name.csv', 's3', Excel::CSV, ['visibility' => 'private']); } public function test_can_override_export_class_disk_options_when_calling_queue() { $export = new class { use Exportable; public $diskOptions = ['visibility' => 'public']; }; $this->mock(Exporter::class) ->shouldReceive('queue')->once() ->with($export, 'name.csv', 's3', Excel::CSV, ['visibility' => 'private']); $export->queue('name.csv', 's3', Excel::CSV, ['visibility' => 'private']); } public function test_can_have_empty_disk_options_when_storing() { $export = new EmptyExport; $this->mock(Exporter::class) ->shouldReceive('store')->once() ->with($export, 'name.csv', null, null, []); $export->store('name.csv'); } public function test_can_have_empty_disk_options_when_queueing() { $export = new EmptyExport; $this->mock(Exporter::class) ->shouldReceive('queue')->once() ->with($export, 'name.csv', null, null, []); $export->queue('name.csv'); } } ================================================ FILE: tests/Concerns/FromArrayTest.php ================================================ store('from-array-store.xlsx'); $this->assertTrue($response); $contents = $this->readAsArray(__DIR__ . '/../Data/Disks/Local/from-array-store.xlsx', 'Xlsx'); $this->assertEquals($export->array(), $contents); } } ================================================ FILE: tests/Concerns/FromCollectionTest.php ================================================ store('from-collection-store.xlsx'); $this->assertTrue($response); $contents = $this->readAsArray(__DIR__ . '/../Data/Disks/Local/from-collection-store.xlsx', 'Xlsx'); $this->assertEquals($export->collection()->toArray(), $contents); } public function test_can_export_with_multiple_sheets_from_collection() { $export = new QueuedExport(); $response = $export->store('multiple-sheets-collection-store.xlsx'); $this->assertTrue($response); foreach ($export->sheets() as $sheetIndex => $sheet) { $spreadsheet = $this->read( __DIR__ . '/../Data/Disks/Local/multiple-sheets-collection-store.xlsx', 'Xlsx' ); $worksheet = $spreadsheet->getSheet($sheetIndex); $this->assertEquals($sheet->collection()->toArray(), $worksheet->toArray()); $this->assertEquals($sheet->title(), $worksheet->getTitle()); } } public function test_can_export_from_lazy_collection() { if (!class_exists('\Illuminate\Support\LazyCollection')) { $this->markTestSkipped('Skipping test because LazyCollection is not supported'); return; } $export = new EloquentLazyCollectionExport(); $export->store('from-lazy-collection-store.xlsx'); $contents = $this->readAsArray(__DIR__ . '/../Data/Disks/Local/from-lazy-collection-store.xlsx', 'Xlsx'); $this->assertEquals( $export->collection()->map( function (array $item) { return array_values($item); } )->toArray(), $contents ); } public function test_can_export_from_lazy_collection_with_queue() { if (!class_exists('\Illuminate\Support\LazyCollection')) { $this->markTestSkipped('Skipping test because LazyCollection is not supported'); return; } $export = new EloquentLazyCollectionQueuedExport(); $response = $export->queue('from-lazy-collection-store.xlsx'); $this->assertTrue($response instanceof PendingDispatch); // Force dispatching via __destruct. unset($response); $contents = $this->readAsArray(__DIR__ . '/../Data/Disks/Local/from-lazy-collection-store.xlsx', 'Xlsx'); $this->assertEquals( $export->collection()->map( function (array $item) { return array_values($item); } )->toArray(), $contents ); } } ================================================ FILE: tests/Concerns/FromGeneratorTest.php ================================================ store('from-generator-store.xlsx'); $this->assertTrue($response); $contents = $this->readAsArray(__DIR__ . '/../Data/Disks/Local/from-generator-store.xlsx', 'Xlsx'); $this->assertEquals(iterator_to_array($export->generator()), $contents); } } ================================================ FILE: tests/Concerns/FromIteratorTest.php ================================================ array()); } }; $response = $export->store('from-iterator-store.xlsx'); $this->assertTrue($response); $contents = $this->readAsArray(__DIR__ . '/../Data/Disks/Local/from-iterator-store.xlsx', 'Xlsx'); $this->assertEquals($export->array(), $contents); } } ================================================ FILE: tests/Concerns/FromQueryTest.php ================================================ loadLaravelMigrations(['--database' => 'testing']); $this->withFactories(__DIR__ . '/../Data/Stubs/Database/Factories'); $this->loadMigrationsFrom(dirname(__DIR__) . '/Data/Stubs/Database/Migrations'); $group = factory(Group::class)->create([ 'name' => 'Group 1', ]); factory(User::class)->times(100)->create()->each(function (User $user) use ($group) { $user->groups()->save($group); }); $group_two = factory(Group::class)->create([ 'name' => 'Group 2', ]); factory(User::class)->times(5)->create()->each(function (User $user) use ($group_two) { $user->groups()->save($group_two); }); } public function test_can_export_from_query() { $export = new FromUsersQueryExport; $response = $export->store('from-query-store.xlsx'); $this->assertTrue($response); $contents = $this->readAsArray(__DIR__ . '/../Data/Disks/Local/from-query-store.xlsx', 'Xlsx'); $allUsers = $export->query()->get()->map(function (User $user) { return array_values($user->toArray()); })->toArray(); $this->assertEquals($allUsers, $contents); } public function test_can_export_from_query_with_join() { $export = new FromUsersQueryWithJoinExport(); $response = $export->store('from-query-store.xlsx'); $this->assertTrue($response); $contents = $this->readAsArray(__DIR__ . '/../Data/Disks/Local/from-query-store.xlsx', 'Xlsx'); $allUsers = $export->query->get()->map(function (User $user) { return array_values($user->toArray()); })->toArray(); $this->assertEquals($allUsers, $contents); } public function test_can_export_from_relation_query_queued() { $export = new FromGroupUsersQueuedQueryExport(); $export->queue('from-query-store.xlsx'); $contents = $this->readAsArray(__DIR__ . '/../Data/Disks/Local/from-query-store.xlsx', 'Xlsx'); $allUsers = $export->query()->get()->map(function ($row) use ($export) { return $export->map($row); })->toArray(); $this->assertEquals($allUsers, $contents); } public function test_can_export_from_query_with_eager_loads() { DB::connection()->enableQueryLog(); $export = new FromUsersQueryExportWithEagerLoad(); $response = $export->store('from-query-with-eager-loads.xlsx'); $this->assertTrue($response); // Should be 2 queries: // 1) select all users // 2) eager load query for groups $this->assertCount(2, DB::getQueryLog()); DB::connection()->disableQueryLog(); $contents = $this->readAsArray(__DIR__ . '/../Data/Disks/Local/from-query-with-eager-loads.xlsx', 'Xlsx'); $allUsers = $export->query()->get()->map(function (User $user) use ($export) { return $export->map($user); })->toArray(); $this->assertEquals($allUsers, $contents); } public function test_can_export_from_query_with_eager_loads_and_queued() { DB::connection()->enableQueryLog(); $export = new FromUsersQueryExportWithEagerLoad(); $export->queue('from-query-with-eager-loads.xlsx'); // Should be 3 queries: // 1) Count users to create chunked queues // 2) select all users // 3) eager load query for groups $this->assertCount(3, DB::getQueryLog()); DB::connection()->disableQueryLog(); $contents = $this->readAsArray(__DIR__ . '/../Data/Disks/Local/from-query-with-eager-loads.xlsx', 'Xlsx'); $allUsers = $export->query()->get()->map(function (User $user) use ($export) { return $export->map($user); })->toArray(); $this->assertEquals($allUsers, $contents); } public function test_can_export_from_query_builder_without_using_eloquent() { $export = new FromNonEloquentQueryExport(); $response = $export->store('from-query-without-eloquent.xlsx'); $this->assertTrue($response); $contents = $this->readAsArray(__DIR__ . '/../Data/Disks/Local/from-query-without-eloquent.xlsx', 'Xlsx'); $allUsers = $export->query()->get()->map(function ($row) { return array_values((array) $row); })->all(); $this->assertEquals($allUsers, $contents); } public function test_can_export_from_query_builder_without_using_eloquent_and_queued() { $export = new FromNonEloquentQueryExport(); $export->queue('from-query-without-eloquent.xlsx'); $contents = $this->readAsArray(__DIR__ . '/../Data/Disks/Local/from-query-without-eloquent.xlsx', 'Xlsx'); $allUsers = $export->query()->get()->map(function ($row) { return array_values((array) $row); })->all(); $this->assertEquals($allUsers, $contents); } public function test_can_export_from_query_builder_with_nested_arrays() { $export = new FromNestedArraysQueryExport(); $response = $export->store('from-query-with-nested-arrays.xlsx'); $this->assertTrue($response); $contents = $this->readAsArray(__DIR__ . '/../Data/Disks/Local/from-query-with-nested-arrays.xlsx', 'Xlsx'); $this->assertEquals($this->format_nested_arrays_expected_data($export->query()->get()), $contents); } public function test_can_export_from_query_builder_with_nested_arrays_queued() { $export = new FromNestedArraysQueryExport(); $export->queue('from-query-with-nested-arrays.xlsx'); $contents = $this->readAsArray(__DIR__ . '/../Data/Disks/Local/from-query-with-nested-arrays.xlsx', 'Xlsx'); $this->assertEquals($this->format_nested_arrays_expected_data($export->query()->get()), $contents); } public function test_can_export_from_query_with_batch_caching() { config()->set('excel.cache.driver', 'batch'); $export = new FromUsersQueryExport; $response = $export->store('from-query-store.xlsx'); $this->assertTrue($response); $contents = $this->readAsArray(__DIR__ . '/../Data/Disks/Local/from-query-store.xlsx', 'Xlsx'); $allUsers = $export->query()->get()->map(function (User $user) { return array_values($user->toArray()); })->toArray(); $this->assertEquals($allUsers, $contents); } public function test_can_export_from_query_with_prepare_rows() { $export = new FromUsersQueryExportWithPrepareRows; $this->assertTrue(method_exists($export, 'prepareRows')); $response = $export->store('from-query-store.xlsx'); $this->assertTrue($response); $contents = $this->readAsArray(__DIR__ . '/../Data/Disks/Local/from-query-store.xlsx', 'Xlsx'); $allUsers = $export->query()->get()->map(function (User $user) { $user->name .= '_prepared_name'; return array_values($user->toArray()); })->toArray(); $this->assertEquals($allUsers, $contents); } public function test_can_export_from_scout() { if (!class_exists('\Laravel\Scout\Engines\DatabaseEngine')) { $this->markTestSkipped('Laravel Scout is too old'); return; } $export = new FromUsersScoutExport; $response = $export->store('from-scout-store.xlsx'); $this->assertTrue($response); $contents = $this->readAsArray(__DIR__ . '/../Data/Disks/Local/from-scout-store.xlsx', 'Xlsx'); $allUsers = $export->query()->get()->map(function (User $user) { return array_values($user->toArray()); })->toArray(); $this->assertEquals($allUsers, $contents); } protected function format_nested_arrays_expected_data($groups) { $expected = []; foreach ($groups as $group) { $group_row = [$group->name, '']; foreach ($group->users as $key => $user) { if ($key === 0) { $group_row[1] = $user->email; $expected[] = $group_row; continue; } $expected[] = ['', $user->email]; } } return $expected; } } ================================================ FILE: tests/Concerns/FromViewTest.php ================================================ withFactories(__DIR__ . '/../Data/Stubs/Database/Factories'); } public function test_can_export_from_view() { /** @var Collection|User[] $users */ $users = factory(User::class)->times(100)->make(); $export = new class($users) implements FromView { use Exportable; /** * @var Collection */ protected $users; /** * @param Collection $users */ public function __construct(Collection $users) { $this->users = $users; } /** * @return View */ public function view(): View { return view('users', [ 'users' => $this->users, ]); } }; $response = $export->store('from-view.xlsx'); $this->assertTrue($response); $contents = $this->readAsArray(__DIR__ . '/../Data/Disks/Local/from-view.xlsx', 'Xlsx'); $expected = $users->map(function (User $user) { return [ $user->name, $user->email, ]; })->prepend(['Name', 'Email'])->toArray(); $this->assertEquals($expected, $contents); } public function test_can_export_multiple_sheets_from_view() { /** @var Collection|User[] $users */ $users = factory(User::class)->times(300)->make(); $export = new class($users) implements WithMultipleSheets { use Exportable; /** * @var Collection */ protected $users; /** * @param Collection $users */ public function __construct(Collection $users) { $this->users = $users; } /** * @return SheetForUsersFromView[] */ public function sheets(): array { return [ new SheetForUsersFromView($this->users->forPage(1, 100)), new SheetForUsersFromView($this->users->forPage(2, 100)), new SheetForUsersFromView($this->users->forPage(3, 100)), ]; } }; $response = $export->store('from-multiple-view.xlsx'); $this->assertTrue($response); $contents = $this->readAsArray(__DIR__ . '/../Data/Disks/Local/from-multiple-view.xlsx', 'Xlsx', 0); $expected = $users->forPage(1, 100)->map(function (User $user) { return [ $user->name, $user->email, ]; })->prepend(['Name', 'Email'])->toArray(); $this->assertEquals(101, sizeof($contents)); $this->assertEquals($expected, $contents); $contents = $this->readAsArray(__DIR__ . '/../Data/Disks/Local/from-multiple-view.xlsx', 'Xlsx', 2); $expected = $users->forPage(3, 100)->map(function (User $user) { return [ $user->name, $user->email, ]; })->prepend(['Name', 'Email'])->toArray(); $this->assertEquals(101, sizeof($contents)); $this->assertEquals($expected, $contents); } } ================================================ FILE: tests/Concerns/ImportableTest.php ================================================ import('import.xlsx'); $this->assertInstanceOf(Importer::class, $imported); } public function test_can_import_a_simple_xlsx_file_from_uploaded_file() { $import = new class implements ToArray { use Importable; /** * @param array $array */ public function array(array $array) { Assert::assertEquals([ ['test', 'test'], ['test', 'test'], ], $array); } }; $import->import($this->givenUploadedFile(__DIR__ . '/../Data/Disks/Local/import.xlsx')); } public function test_can_import_a_simple_csv_file_with_html_tags_inside() { $import = new class implements ToArray { use Importable; /** * @param array $array */ public function array(array $array) { Assert::assertEquals([ ['key1', 'A', 'row1'], ['key2', 'B', '

row2

'], ['key3', 'C', 'row3'], ['key4', 'D', 'row4'], ['key5', 'E', 'row5'], ['key6', 'F', 'link"'], ], $array); } }; $import->import('csv-with-html-tags.csv', 'local', Excel::CSV); } public function test_can_import_a_simple_xlsx_file_with_ignore_empty_set_to_true() { config()->set('excel.imports.ignore_empty', true); $import = new class implements ToArray { use Importable; /** * @param array $array */ public function array(array $array) { Assert::assertEquals([ ['test', 'test'], ['test', 'test'], ], $array); } }; $imported = $import->import('import-with-some-empty-rows.xlsx'); $this->assertInstanceOf(Importer::class, $imported); } public function test_can_import_a_simple_xlsx_file_with_ignore_empty_set_to_false() { config()->set('excel.imports.ignore_empty', false); $import = new class implements ToArray { use Importable; /** * @param array $array */ public function array(array $array) { Assert::assertEquals([ ['test', 'test'], ['test', 'test'], ['', ''], ['', ''], ], $array); } }; $imported = $import->import('import-with-some-empty-rows.xlsx'); $this->assertInstanceOf(Importer::class, $imported); } public function test_cannot_import_a_non_existing_xlsx_file() { $this->expectException(FileNotFoundException::class); $import = new class { use Importable; }; $import->import('doesnotexistanywhere.xlsx'); } } ================================================ FILE: tests/Concerns/OnEachRowTest.php ================================================ getCellIterator() as $cell) { Assert::assertEquals('test', $cell->getValue()); } Assert::assertEquals([ 'test', 'test', ], $row->toArray()); Assert::assertEquals('test', $row[0]); $this->called++; } }; $import->import('import.xlsx'); $this->assertEquals(2, $import->called); } public function test_it_respects_the_end_column() { $import = new class implements OnEachRow { use Importable; /** * @param Row $row */ public function onRow(Row $row) { // Accessing a row as an array calls toArray() without an end // column. This saves the row in the cache, so we have to // invalidate the cache once the end column changes $row[0]; Assert::assertEquals([ 'test', ], $row->toArray(null, false, true, 'A')); } }; $import->import('import.xlsx'); } } ================================================ FILE: tests/Concerns/RegistersEventListenersTest.php ================================================ assertInstanceOf(BeforeExport::class, $event); $this->assertInstanceOf(Writer::class, $event->writer); $eventsTriggered++; }; $event::$beforeWriting = function ($event) use (&$eventsTriggered) { $this->assertInstanceOf(BeforeWriting::class, $event); $this->assertInstanceOf(Writer::class, $event->writer); $eventsTriggered++; }; $event::$beforeSheet = function ($event) use (&$eventsTriggered) { $this->assertInstanceOf(BeforeSheet::class, $event); $this->assertInstanceOf(Sheet::class, $event->sheet); $eventsTriggered++; }; $event::$afterSheet = function ($event) use (&$eventsTriggered) { $this->assertInstanceOf(AfterSheet::class, $event); $this->assertInstanceOf(Sheet::class, $event->sheet); $eventsTriggered++; }; $this->assertInstanceOf(BinaryFileResponse::class, $event->download('filename.xlsx')); $this->assertEquals(4, $eventsTriggered); } public function test_events_get_called_when_importing() { $event = new ImportWithRegistersEventListeners(); $eventsTriggered = 0; $event::$beforeImport = function ($event) use (&$eventsTriggered) { $this->assertInstanceOf(BeforeImport::class, $event); $this->assertInstanceOf(Reader::class, $event->reader); $eventsTriggered++; }; $event::$beforeSheet = function ($event) use (&$eventsTriggered) { $this->assertInstanceOf(BeforeSheet::class, $event); $this->assertInstanceOf(Sheet::class, $event->sheet); $eventsTriggered++; }; $event::$afterSheet = function ($event) use (&$eventsTriggered) { $this->assertInstanceOf(AfterSheet::class, $event); $this->assertInstanceOf(Sheet::class, $event->sheet); $eventsTriggered++; }; $event->import('import.xlsx'); $this->assertEquals(3, $eventsTriggered); } public function test_can_have_invokable_class_as_listener() { $event = new ExportWithEvents(); $event->beforeExport = new BeforeExportListener(function ($event) { $this->assertInstanceOf(BeforeExport::class, $event); $this->assertInstanceOf(Writer::class, $event->writer); }); $this->assertInstanceOf(BinaryFileResponse::class, $event->download('filename.xlsx')); } } ================================================ FILE: tests/Concerns/RemembersChunkOffsetTest.php ================================================ setChunkOffset(50); $this->assertEquals(50, $import->getChunkOffset()); } public function test_can_access_chunk_offset_on_import_to_array_in_chunks() { $import = new class implements ToArray, WithChunkReading { use Importable; use RemembersChunkOffset; public $offsets = []; public function array(array $array) { $this->offsets[] = $this->getChunkOffset(); } public function chunkSize(): int { return 2000; } }; $import->import('import-batches.xlsx'); $this->assertEquals([1, 2001, 4001], $import->offsets); } } ================================================ FILE: tests/Concerns/RemembersRowNumberTest.php ================================================ rememberRowNumber(50); $this->assertEquals(50, $import->getRowNumber()); } public function test_can_access_row_number_on_import_to_model() { $import = new class implements ToModel { use Importable; use RemembersRowNumber; public $rowNumbers = []; public function model(array $row) { $this->rowNumbers[] = $this->getRowNumber(); } }; $import->import('import-batches.xlsx'); $this->assertEquals([46, 47, 48, 49, 50, 51, 52, 53, 54, 55], array_slice($import->rowNumbers, 45, 10)); } public function test_can_access_row_number_on_import_to_array_in_chunks() { $import = new class implements ToModel, WithChunkReading { use Importable; use RemembersRowNumber; public $rowNumbers = []; public function chunkSize(): int { return 50; } public function model(array $row) { $this->rowNumbers[] = $this->getRowNumber(); } }; $import->import('import-batches.xlsx'); $this->assertEquals([46, 47, 48, 49, 50, 51, 52, 53, 54, 55], array_slice($import->rowNumbers, 45, 10)); } public function test_can_access_row_number_on_import_to_array_in_chunks_with_batch_inserts() { $import = new class implements ToModel, WithChunkReading, WithBatchInserts { use Importable; use RemembersRowNumber; public $rowNumbers = []; public function chunkSize(): int { return 50; } public function model(array $row) { $this->rowNumbers[] = $this->rowNumber; } public function batchSize(): int { return 50; } }; $import->import('import-batches.xlsx'); $this->assertEquals([46, 47, 48, 49, 50, 51, 52, 53, 54, 55], array_slice($import->rowNumbers, 45, 10)); } } ================================================ FILE: tests/Concerns/ShouldQueueWithoutChainTest.php ================================================ loadLaravelMigrations(['--database' => 'testing']); $this->loadMigrationsFrom(dirname(__DIR__) . '/Data/Stubs/Database/Migrations'); } public function test_can_import_to_model_in_chunks() { DB::connection()->enableQueryLog(); $import = new QueueImportWithoutJobChaining(); $import->import('import-users.xlsx'); $this->assertCount(2, DB::getQueryLog()); DB::connection()->disableQueryLog(); } public function test_can_import_to_model_without_job_chaining() { Queue::fake(); $import = new QueueImportWithoutJobChaining(); $import->import('import-users.xlsx'); Queue::assertPushed(ReadChunk::class, 2); Queue::assertPushed(AfterImportJob::class, 1); Queue::assertPushed(AfterImportJob::class, function ($import) { return !is_null($import->delay); }); Queue::assertNotPushed(QueueImport::class); } public function test_a_queue_name_can_be_specified_when_importing() { Queue::fake(); $import = new QueueImportWithoutJobChaining(); $import->queue = 'queue-name'; $import->import('import-users.xlsx'); Queue::assertPushedOn('queue-name', ReadChunk::class); Queue::assertPushedOn('queue-name', AfterImportJob::class); } public function test_the_cleanup_only_runs_when_all_jobs_are_done() { $fake = Queue::fake(); if (method_exists($fake, 'serializeAndRestore')) { $fake->serializeAndRestore(); // More realism } $import = new QueueImportWithoutJobChaining(); $import->import('import-users.xlsx'); $jobs = Queue::pushedJobs(); $chunks = collect($jobs[ReadChunk::class])->pluck('job'); $chunks->each(function (ReadChunk $chunk) { self::assertFalse(ReadChunk::isComplete($chunk->getUniqueId())); }); self::assertCount(2, $chunks); $afterImport = $jobs[AfterImportJob::class][0]['job']; if (!method_exists($fake, 'except')) { /** @var SyncQueue $queue */ $fake = app(SyncQueue::class); $fake->setContainer(app()); } else { $fake->except([AfterImportJob::class, ReadChunk::class]); } $fake->push($chunks->first()); self::assertTrue(ReadChunk::isComplete($chunks->first()->getUniqueId())); self::assertFalse(ReadChunk::isComplete($chunks->last()->getUniqueId())); Event::listen(JobProcessed::class, function (JobProcessed $event) { self::assertTrue($event->job->isReleased()); }); $fake->push($afterImport); Event::forget(JobProcessed::class); $fake->push($chunks->last()); Event::listen(JobProcessed::class, function (JobProcessed $event) { self::assertFalse($event->job->isReleased()); }); $fake->push($afterImport); Event::forget(JobProcessed::class); } } ================================================ FILE: tests/Concerns/SkipsEmptyRowsTest.php ================================================ called = true; Assert::assertEquals([ ['Test1', 'Test2'], ['Test3', 'Test4'], ['Test5', 'Test6'], ], $collection->toArray()); } }; $import->import('import-empty-rows.xlsx'); $this->assertTrue($import->called); } public function test_skips_empty_rows_when_importing_on_each_row() { $import = new class implements OnEachRow, SkipsEmptyRows { use Importable; public $rows = 0; /** * @param Row $row */ public function onRow(Row $row) { Assert::assertFalse($row->isEmpty()); $this->rows++; } }; $import->import('import-empty-rows.xlsx'); $this->assertEquals(3, $import->rows); } public function test_skips_empty_rows_when_importing_to_model() { $import = new class implements ToModel, SkipsEmptyRows { use Importable; public $rows = 0; /** * @param array $row * @return Model|Model[]|null */ public function model(array $row) { $this->rows++; return null; } }; $import->import('import-empty-rows.xlsx'); $this->assertEquals(3, $import->rows); } public function test_custom_skips_rows_when_importing_to_collection() { $import = new class implements SkipsEmptyRows, ToCollection { use Importable; public $called = false; /** * @param Collection $collection */ public function collection(Collection $collection) { $this->called = true; Assert::assertEquals([ ['Test1', 'Test2'], ['Test3', 'Test4'], ], $collection->toArray()); } public function isEmptyWhen(array $row) { return $row[0] == 'Test5' && $row[1] == 'Test6'; } }; $import->import('import-empty-rows.xlsx'); $this->assertTrue($import->called); } public function test_custom_skips_rows_when_importing_to_model() { $import = new class implements SkipsEmptyRows, ToModel { use Importable; public $called = false; /** * @param array $row */ public function model(array $row) { Assert::assertEquals('Not empty', $row[0]); } public function isEmptyWhen(array $row): bool { $this->called = true; return $row[0] === 'Empty'; } }; $import->import('skip-empty-rows-with-is-empty-when.xlsx'); $this->assertTrue($import->called); } public function test_custom_skips_rows_when_using_oneachrow() { $import = new class implements SkipsEmptyRows, OnEachRow { use Importable; public $called = false; /** * @param array $row */ public function onRow(Row $row) { Assert::assertEquals('Not empty', $row[0]); } public function isEmptyWhen(array $row): bool { $this->called = true; return $row[0] === 'Empty'; } }; $import->import('skip-empty-rows-with-is-empty-when.xlsx'); $this->assertTrue($import->called); } } ================================================ FILE: tests/Concerns/SkipsOnErrorTest.php ================================================ loadLaravelMigrations(['--database' => 'testing']); } public function test_can_skip_on_error() { $import = new class implements ToModel, SkipsOnError { use Importable; public $errors = 0; /** * @param array $row * @return Model|null */ public function model(array $row) { return new User([ 'name' => $row[0], 'email' => $row[1], 'password' => 'secret', ]); } /** * @param Throwable $e */ public function onError(Throwable $e) { Assert::assertInstanceOf(QueryException::class, $e); Assert::stringContains($e->getMessage(), 'Duplicate entry \'patrick@maatwebsite.nl\''); $this->errors++; } }; $import->import('import-users-with-duplicates.xlsx'); $this->assertEquals(1, $import->errors); // Shouldn't have rollbacked other imported rows. $this->assertDatabaseHas('users', [ 'email' => 'patrick@maatwebsite.nl', ]); // Should have skipped inserting $this->assertDatabaseMissing('users', [ 'email' => 'taylor@laravel.com', ]); } public function test_can_skip_errors_and_collect_all_errors_at_the_end() { $import = new class implements ToModel, SkipsOnError { use Importable, SkipsErrors; /** * @param array $row * @return Model|null */ public function model(array $row) { return new User([ 'name' => $row[0], 'email' => $row[1], 'password' => 'secret', ]); } }; $import->import('import-users-with-duplicates.xlsx'); $this->assertCount(1, $import->errors()); /** @var Throwable $e */ $e = $import->errors()->first(); $this->assertInstanceOf(QueryException::class, $e); $this->stringContains($e->getMessage(), 'Duplicate entry \'patrick@maatwebsite.nl\''); // Shouldn't have rollbacked other imported rows. $this->assertDatabaseHas('users', [ 'email' => 'patrick@maatwebsite.nl', ]); // Should have skipped inserting $this->assertDatabaseMissing('users', [ 'email' => 'taylor@laravel.com', ]); } public function test_can_skip_on_error_when_using_oneachrow_with_validation() { $import = new class implements OnEachRow, WithValidation, SkipsOnError { use Importable; public $errors = 0; public $processedRows = 0; /** * @param Row $row */ public function onRow(Row $row) { $this->processedRows++; // This will be called for valid rows $rowArray = $row->toArray(); User::create([ 'name' => $rowArray[0], 'email' => $rowArray[1], 'password' => 'secret', ]); } /** * @return array */ public function rules(): array { return [ '1' => Rule::in(['patrick@maatwebsite.nl']), ]; } /** * @param Throwable $e */ public function onError(Throwable $e) { Assert::assertInstanceOf(ValidationException::class, $e); Assert::stringContains($e->getMessage(), 'The selected 1 is invalid'); $this->errors++; } }; $import->import('import-users.xlsx'); $this->assertEquals(1, $import->errors); $this->assertEquals(1, $import->processedRows); // Only the valid row should be processed // Should have inserted the valid row $this->assertDatabaseHas('users', [ 'email' => 'patrick@maatwebsite.nl', ]); // Should have skipped inserting the invalid row $this->assertDatabaseMissing('users', [ 'email' => 'taylor@laravel.com', ]); } public function test_can_skip_errors_and_collect_all_errors_when_using_oneachrow_with_validation() { $import = new class implements OnEachRow, WithValidation, SkipsOnError { use Importable, SkipsErrors; public $processedRows = 0; /** * @param Row $row */ public function onRow(Row $row) { $this->processedRows++; // This will be called for valid rows $rowArray = $row->toArray(); User::create([ 'name' => $rowArray[0], 'email' => $rowArray[1], 'password' => 'secret', ]); } /** * @return array */ public function rules(): array { return [ '1' => Rule::in(['patrick@maatwebsite.nl']), ]; } }; $import->import('import-users.xlsx'); $this->assertCount(1, $import->errors()); $this->assertEquals(1, $import->processedRows); // Only the valid row should be processed /** @var Throwable $e */ $e = $import->errors()->first(); $this->assertInstanceOf(ValidationException::class, $e); $this->stringContains($e->getMessage(), 'The selected 1 is invalid'); // Should have inserted the valid row $this->assertDatabaseHas('users', [ 'email' => 'patrick@maatwebsite.nl', ]); // Should have skipped inserting the invalid row $this->assertDatabaseMissing('users', [ 'email' => 'taylor@laravel.com', ]); } public function test_can_skip_on_error_when_exception_thrown_in_onrow() { $import = new class implements OnEachRow, SkipsOnError { use Importable; public $errors = 0; public $processedRows = 0; /** * @param Row $row */ public function onRow(Row $row) { $this->processedRows++; $rowArray = $row->toArray(); // Throw an exception for the second row (Taylor Otwell) if ($rowArray[1] === 'taylor@laravel.com') { throw new \Exception('Custom error in onRow for Taylor'); } User::create([ 'name' => $rowArray[0], 'email' => $rowArray[1], 'password' => 'secret', ]); } /** * @param Throwable $e */ public function onError(Throwable $e) { Assert::assertInstanceOf(\Exception::class, $e); Assert::assertEquals('Custom error in onRow for Taylor', $e->getMessage()); $this->errors++; } }; $import->import('import-users.xlsx'); $this->assertEquals(1, $import->errors); $this->assertEquals(2, $import->processedRows); // Both rows should be processed, but one throws exception // Should have inserted the valid row $this->assertDatabaseHas('users', [ 'email' => 'patrick@maatwebsite.nl', ]); // Should have skipped inserting the row that threw exception $this->assertDatabaseMissing('users', [ 'email' => 'taylor@laravel.com', ]); } public function test_can_skip_errors_and_collect_all_errors_when_exception_thrown_in_onrow() { $import = new class implements OnEachRow, SkipsOnError { use Importable, SkipsErrors; public $processedRows = 0; /** * @param Row $row */ public function onRow(Row $row) { $this->processedRows++; $rowArray = $row->toArray(); // Throw an exception for the second row (Taylor Otwell) if ($rowArray[1] === 'taylor@laravel.com') { throw new \RuntimeException('Runtime error in onRow for Taylor'); } User::create([ 'name' => $rowArray[0], 'email' => $rowArray[1], 'password' => 'secret', ]); } }; $import->import('import-users.xlsx'); $this->assertCount(1, $import->errors()); $this->assertEquals(2, $import->processedRows); // Both rows should be processed, but one throws exception /** @var Throwable $e */ $e = $import->errors()->first(); $this->assertInstanceOf(\RuntimeException::class, $e); $this->assertEquals('Runtime error in onRow for Taylor', $e->getMessage()); // Should have inserted the valid row $this->assertDatabaseHas('users', [ 'email' => 'patrick@maatwebsite.nl', ]); // Should have skipped inserting the row that threw exception $this->assertDatabaseMissing('users', [ 'email' => 'taylor@laravel.com', ]); } } ================================================ FILE: tests/Concerns/SkipsOnFailureTest.php ================================================ loadLaravelMigrations(['--database' => 'testing']); } public function test_can_skip_on_error() { $import = new class implements ToModel, WithValidation, SkipsOnFailure { use Importable; public $failures = 0; /** * @param array $row * @return Model|null */ public function model(array $row) { return new User([ 'name' => $row[0], 'email' => $row[1], 'password' => 'secret', ]); } /** * @return array */ public function rules(): array { return [ '1' => Rule::in(['patrick@maatwebsite.nl']), ]; } /** * @param Failure[] $failures */ public function onFailure(Failure ...$failures) { $failure = $failures[0]; Assert::assertEquals(2, $failure->row()); Assert::assertEquals('1', $failure->attribute()); Assert::assertEquals(['The selected 1 is invalid.'], $failure->errors()); Assert::assertEquals(['Taylor Otwell', 'taylor@laravel.com'], $failure->values()); Assert::assertEquals(2, $failure->jsonSerialize()['row']); Assert::assertEquals('1', $failure->jsonSerialize()['attribute']); Assert::assertEquals(['The selected 1 is invalid.'], $failure->jsonSerialize()['errors']); Assert::assertEquals(['Taylor Otwell', 'taylor@laravel.com'], $failure->jsonSerialize()['values']); $this->failures += \count($failures); } }; $import->import('import-users.xlsx'); $this->assertEquals(1, $import->failures); // Shouldn't have rollbacked other imported rows. $this->assertDatabaseHas('users', [ 'email' => 'patrick@maatwebsite.nl', ]); // Should have skipped inserting $this->assertDatabaseMissing('users', [ 'email' => 'taylor@laravel.com', ]); } public function test_skips_only_failed_rows_in_batch() { $import = new class implements ToModel, WithValidation, WithBatchInserts, SkipsOnFailure { use Importable; public $failures = 0; /** * @param array $row * @return Model|null */ public function model(array $row) { return new User([ 'name' => $row[0], 'email' => $row[1], 'password' => 'secret', ]); } /** * @return array */ public function rules(): array { return [ '1' => Rule::in(['patrick@maatwebsite.nl']), ]; } /** * @param Failure[] $failures */ public function onFailure(Failure ...$failures) { $failure = $failures[0]; Assert::assertEquals(2, $failure->row()); Assert::assertEquals('1', $failure->attribute()); Assert::assertEquals(['The selected 1 is invalid.'], $failure->errors()); $this->failures += \count($failures); } /** * @return int */ public function batchSize(): int { return 100; } }; $import->import('import-users.xlsx'); $this->assertEquals(1, $import->failures); // Shouldn't have rollbacked/skipped the rest of the batch. $this->assertDatabaseHas('users', [ 'email' => 'patrick@maatwebsite.nl', ]); // Should have skipped inserting $this->assertDatabaseMissing('users', [ 'email' => 'taylor@laravel.com', ]); } public function test_can_skip_failures_and_collect_all_failures_at_the_end() { $import = new class implements ToModel, WithValidation, SkipsOnFailure { use Importable, SkipsFailures; /** * @param array $row * @return Model|null */ public function model(array $row) { return new User([ 'name' => $row[0], 'email' => $row[1], 'password' => 'secret', ]); } /** * @return array */ public function rules(): array { return [ '1' => Rule::in(['patrick@maatwebsite.nl']), ]; } }; $import->import('import-users.xlsx'); $this->assertCount(1, $import->failures()); /** @var Failure $failure */ $failure = $import->failures()->first(); $this->assertEquals(2, $failure->row()); $this->assertEquals('1', $failure->attribute()); $this->assertEquals(['The selected 1 is invalid.'], $failure->errors()); // Shouldn't have rollbacked other imported rows. $this->assertDatabaseHas('users', [ 'email' => 'patrick@maatwebsite.nl', ]); // Should have skipped inserting $this->assertDatabaseMissing('users', [ 'email' => 'taylor@laravel.com', ]); } public function test_can_validate_using_oneachrow_and_skipsonfailure() { $import = new class implements OnEachRow, WithValidation, SkipsOnFailure { use Importable, SkipsFailures; /** * @param Row $row * @return Model|null */ public function onRow(Row $row) { $row = $row->toArray(); return User::create([ 'name' => $row[0], 'email' => $row[1], 'password' => 'secret', ]); } /** * @return array */ public function rules(): array { return [ '1' => Rule::in(['patrick@maatwebsite.nl']), ]; } }; $this->assertEmpty(User::all()); $import->import('import-users.xlsx'); $this->assertCount(1, $import->failures()); // Shouldn't have rollbacked other imported rows. $this->assertDatabaseHas('users', [ 'email' => 'patrick@maatwebsite.nl', ]); // Should have skipped inserting $this->assertDatabaseMissing('users', [ 'email' => 'taylor@laravel.com', ]); } public function test_can_validate_using_tocollection_and_skipsonfailure() { $import = new class implements ToCollection, WithValidation, SkipsOnFailure { use Importable, SkipsFailures; /** * @param Row $row * @return Model|null */ public function collection(Collection $rows) { $rows = $rows->each(function ($row) { return User::create([ 'name' => $row[0], 'email' => $row[1], 'password' => 'secret', ]); }); } /** * @return array */ public function rules(): array { return [ '1' => Rule::in(['patrick@maatwebsite.nl']), ]; } }; $this->assertEmpty(User::all()); $import->import('import-users.xlsx'); $this->assertCount(1, $import->failures()); // Shouldn't have rollbacked other imported rows. $this->assertDatabaseHas('users', [ 'email' => 'patrick@maatwebsite.nl', ]); // Should have skipped inserting $this->assertDatabaseMissing('users', [ 'email' => 'taylor@laravel.com', ]); } } ================================================ FILE: tests/Concerns/ToArrayTest.php ================================================ called = true; Assert::assertEquals([ ['test', 'test'], ['test', 'test'], ], $array); } }; $import->import('import.xlsx'); $this->assertTrue($import->called); } public function test_can_import_multiple_sheets_to_array() { $import = new class implements ToArray { use Importable; public $called = 0; /** * @param array $array */ public function array(array $array) { $this->called++; $sheetNumber = $this->called; Assert::assertEquals([ [$sheetNumber . '.A1', $sheetNumber . '.B1'], [$sheetNumber . '.A2', $sheetNumber . '.B2'], ], $array); } }; $import->import('import-multiple-sheets.xlsx'); $this->assertEquals(2, $import->called); } } ================================================ FILE: tests/Concerns/ToCollectionTest.php ================================================ called = true; Assert::assertEquals([ ['test', 'test'], ['test', 'test'], ], $collection->toArray()); } }; $import->import('import.xlsx'); $this->assertTrue($import->called); } public function test_can_import_multiple_sheets_to_collection() { $import = new class implements ToCollection { use Importable; public $called = 0; /** * @param Collection $collection */ public function collection(Collection $collection) { $this->called++; $sheetNumber = $this->called; Assert::assertEquals([ [$sheetNumber . '.A1', $sheetNumber . '.B1'], [$sheetNumber . '.A2', $sheetNumber . '.B2'], ], $collection->toArray()); } }; $import->import('import-multiple-sheets.xlsx'); $this->assertEquals(2, $import->called); } } ================================================ FILE: tests/Concerns/ToModelTest.php ================================================ loadLaravelMigrations(['--database' => 'testing']); $this->loadMigrationsFrom(dirname(__DIR__) . '/Data/Stubs/Database/Migrations'); } public function test_can_import_each_row_to_model() { DB::connection()->enableQueryLog(); $import = new class implements ToModel { use Importable; /** * @param array $row * @return Model|Model[]|null */ public function model(array $row) { return new User([ 'name' => $row[0], 'email' => $row[1], 'password' => 'secret', ]); } }; $import->import('import-users.xlsx'); $this->assertCount(2, DB::getQueryLog()); DB::connection()->disableQueryLog(); $this->assertDatabaseHas('users', [ 'name' => 'Patrick Brouwers', 'email' => 'patrick@maatwebsite.nl', ]); $this->assertDatabaseHas('users', [ 'name' => 'Taylor Otwell', 'email' => 'taylor@laravel.com', ]); } public function test_has_timestamps_when_imported_single_model() { $import = new class implements ToModel { use Importable; /** * @param array $row * @return Model|Model[]|null */ public function model(array $row) { return new User([ 'name' => $row[0], 'email' => $row[1], 'password' => 'secret', ]); } }; $import->import('import-users.xlsx'); $user = User::first(); $this->assertNotNull($user->created_at); $this->assertNotNull($user->updated_at); } public function test_can_import_multiple_models_in_single_to_model() { DB::connection()->enableQueryLog(); $import = new class implements ToModel { use Importable; /** * @param array $row * @return Model|Model[]|null */ public function model(array $row) { $user1 = new User([ 'name' => $row[0], 'email' => $row[1], 'password' => 'secret', ]); $faker = Factory::create(); $user2 = new User([ 'name' => $faker->name, 'email' => $faker->email, 'password' => 'secret', ]); return [$user1, $user2]; } }; $import->import('import-users.xlsx'); $this->assertCount(4, DB::getQueryLog()); DB::connection()->disableQueryLog(); } public function test_can_import_multiple_different_types_of_models_in_single_to_model() { DB::connection()->enableQueryLog(); $import = new class implements ToModel { use Importable; /** * @param array $row * @return Model|Model[]|null */ public function model(array $row) { $user = new User([ 'name' => $row[0], 'email' => $row[1], 'password' => 'secret', ]); $group = new Group([ 'name' => $row[0], ]); return [$user, $group]; } }; $import->import('import-users.xlsx'); $this->assertCount(4, DB::getQueryLog()); $this->assertEquals(2, User::count()); $this->assertEquals(2, Group::count()); DB::connection()->disableQueryLog(); } public function test_can_import_models_with_belongs_to_relations() { User::query()->truncate(); Group::query()->truncate(); DB::connection()->enableQueryLog(); $import = new class implements ToModel, PersistRelations { use Importable; /** * @param array $row * @return Model|Model[]|null */ public function model(array $row) { $user = new User([ 'name' => $row[0], 'email' => $row[1], 'password' => 'secret', ]); $user->group()->associate( new Group([ 'name' => $row[0], ]) ); return $user; } }; $import->import('import-users.xlsx'); $this->assertCount(6, DB::getQueryLog()); $users = User::all(); $users->each(function (User $user) { $this->assertInstanceOf(Group::class, $user->group); $this->assertIsInt($user->group->id); }); $this->assertCount(2, $users); $this->assertEquals(2, Group::count()); DB::connection()->disableQueryLog(); } public function test_can_import_models_with_belongs_to_many_relations() { User::query()->truncate(); Group::query()->truncate(); DB::connection()->enableQueryLog(); $import = new class implements ToModel, PersistRelations { use Importable; /** * @param array $row * @return Model|Model[]|null */ public function model(array $row) { $user = new User([ 'name' => $row[0], 'email' => $row[1], 'password' => 'secret', ]); $user->setRelation('groups', new Collection([ new Group([ 'name' => $row[0], ]), ])); return $user; } }; $import->import('import-users.xlsx'); $this->assertCount(6, DB::getQueryLog()); $users = User::all(); $users->each(function (User $user) { $this->assertInstanceOf(Group::class, $user->groups->first()); }); $this->assertCount(2, $users); $this->assertEquals(2, Group::count()); DB::connection()->disableQueryLog(); } } ================================================ FILE: tests/Concerns/WithBackgroundColorTest.php ================================================ store('background-styles.xlsx'); $spreadsheet = $this->read(__DIR__ . '/../Data/Disks/Local/background-styles.xlsx', 'Xlsx'); $sheet = $spreadsheet->getDefaultStyle(); $this->assertEquals(Fill::FILL_SOLID, $sheet->getFill()->getFillType()); $this->assertEquals('000000', $sheet->getFill()->getStartColor()->getRGB()); } public function test_can_configure_background_color_as_array() { $export = new class implements WithBackgroundColor { use Exportable; public function backgroundColor() { return [ 'fillType' => Fill::FILL_GRADIENT_LINEAR, 'startColor' => ['argb' => Color::COLOR_RED], ]; } }; $export->store('background-styles.xlsx'); $spreadsheet = $this->read(__DIR__ . '/../Data/Disks/Local/background-styles.xlsx', 'Xlsx'); $sheet = $spreadsheet->getDefaultStyle(); $this->assertEquals(Fill::FILL_GRADIENT_LINEAR, $sheet->getFill()->getFillType()); $this->assertEquals(Color::COLOR_RED, $sheet->getFill()->getStartColor()->getARGB()); } public function test_can_configure_background_color_with_color_instance() { $export = new class implements WithBackgroundColor { use Exportable; public function backgroundColor() { return new Color(Color::COLOR_BLUE); } }; $export->store('background-styles.xlsx'); $spreadsheet = $this->read(__DIR__ . '/../Data/Disks/Local/background-styles.xlsx', 'Xlsx'); $sheet = $spreadsheet->getDefaultStyle(); $this->assertEquals(Fill::FILL_SOLID, $sheet->getFill()->getFillType()); $this->assertEquals(Color::COLOR_BLUE, $sheet->getFill()->getStartColor()->getARGB()); } } ================================================ FILE: tests/Concerns/WithBatchInsertsTest.php ================================================ loadLaravelMigrations(['--database' => 'testing']); $this->loadMigrationsFrom(dirname(__DIR__) . '/Data/Stubs/Database/Migrations'); } public function test_can_import_to_model_in_batches() { DB::connection()->enableQueryLog(); $import = new class implements ToModel, WithBatchInserts { use Importable; /** * @param array $row * @return Model|null */ public function model(array $row) { return new User([ 'name' => $row[0], 'email' => $row[1], 'password' => 'secret', ]); } /** * @return int */ public function batchSize(): int { return 2; } }; $import->import('import-users.xlsx'); $this->assertCount(1, DB::getQueryLog()); DB::connection()->disableQueryLog(); $this->assertDatabaseHas('users', [ 'name' => 'Patrick Brouwers', 'email' => 'patrick@maatwebsite.nl', ]); $this->assertDatabaseHas('users', [ 'name' => 'Taylor Otwell', 'email' => 'taylor@laravel.com', ]); } public function test_can_import_to_model_in_batches_bigger_file() { DB::connection()->enableQueryLog(); $import = new class implements ToModel, WithBatchInserts { use Importable; /** * @param array $row * @return Model|null */ public function model(array $row) { return new Group([ 'name' => $row[0], ]); } /** * @return int */ public function batchSize(): int { return 1000; } }; $import->import('import-batches.xlsx'); $this->assertCount(5000 / $import->batchSize(), DB::getQueryLog()); DB::connection()->disableQueryLog(); } public function test_can_import_multiple_different_types_of_models_in_single_to_model() { DB::connection()->enableQueryLog(); $import = new class implements ToModel, WithBatchInserts { use Importable; /** * @param array $row * @return Model|Model[]|null */ public function model(array $row) { $user = new User([ 'name' => $row[0], 'email' => $row[1], 'password' => 'secret', ]); $group = new Group([ 'name' => $row[0], ]); return [$user, $group]; } /** * @return int */ public function batchSize(): int { return 2; } }; $import->import('import-users.xlsx'); // Expected 2 batch queries, 1 for users, 1 for groups $this->assertCount(2, DB::getQueryLog()); $this->assertEquals(2, User::count()); $this->assertEquals(2, Group::count()); DB::connection()->disableQueryLog(); } public function test_has_timestamps_when_imported_in_batches() { $import = new class implements ToModel, WithBatchInserts { use Importable; /** * @param array $row * @return Model|Model[]|null */ public function model(array $row) { return new User([ 'name' => $row[0], 'email' => $row[1], 'password' => 'secret', ]); } /** * @return int */ public function batchSize(): int { return 2; } }; $import->import('import-users.xlsx'); $user = User::first(); $this->assertNotNull($user->created_at); $this->assertNotNull($user->updated_at); } } ================================================ FILE: tests/Concerns/WithCalculatedFormulasTest.php ================================================ called = true; Assert::assertSame('=1+1', $array[0][0]); } }; $import->import('import-formulas.xlsx'); $this->assertTrue($import->called); } public function test_can_import_to_array_with_calculated_formulas() { $import = new class implements ToArray, WithCalculatedFormulas { use Importable; public $called = false; /** * @param array $array */ public function array(array $array) { $this->called = true; Assert::assertSame(2, $array[0][0]); } }; $import->import('import-formulas.xlsx'); $this->assertTrue($import->called); } public function test_can_import_to_model_with_calculated_formulas() { $import = new class implements ToModel, WithCalculatedFormulas { use Importable; public $called = false; /** * @param array $row * @return Model|null */ public function model(array $row) { $this->called = true; Assert::assertSame(2, $row[0]); return null; } }; $import->import('import-formulas.xlsx'); $this->assertTrue($import->called); } public function can_import_with_formulas_and_reference() { $import = new class implements ToModel, WithCalculatedFormulas, WithStartRow { use Importable; public $called = false; /** * @param array $row * @return Model|null */ public function model(array $row) { $this->called = true; Assert::assertSame('julien', $row[1]); return null; } public function startRow(): int { return 2; } }; $import->import('import-external-reference.xls'); $this->assertTrue($import->called); } public function test_can_import_to_array_with_calculated_formulas_and_multi_sheet_references() { $import = new class implements WithMultipleSheets, HasReferencesToOtherSheets { use Importable; public $test = 'test1'; public function sheets(): array { return [ new class implements ToArray, HasReferencesToOtherSheets { public $test = 'test2'; public function array(array $array) { Assert::assertEquals([ ['1', '1'], ], $array); } }, new class implements ToArray, WithCalculatedFormulas, HasReferencesToOtherSheets { public $test = 'test2'; public function array(array $array) { Assert::assertEquals([ ['2'], ], $array); } }, ]; } }; $import->import('import-formulas-multiple-sheets.xlsx'); } public function test_can_import_to_array_with_calculated_formulas_and_skips_empty() { $import = new class implements ToArray, WithCalculatedFormulas, SkipsEmptyRows { use Importable; public $called = false; /** * @param array $array */ public function array(array $array) { $this->called = true; Assert::assertSame(2, $array[0][0]); } }; $import->import('import-formulas.xlsx'); $this->assertTrue($import->called); } public function test_can_import_to_model_with_calculated_formulas_and_skips_empty() { $import = new class implements ToModel, WithCalculatedFormulas, SkipsEmptyRows { use Importable; public $called = false; /** * @param array $row * @return Model|null */ public function model(array $row) { $this->called = true; Assert::assertSame(2, $row[0]); return null; } }; $import->import('import-formulas.xlsx'); $this->assertTrue($import->called); } } ================================================ FILE: tests/Concerns/WithChunkReadingTest.php ================================================ loadLaravelMigrations(['--database' => 'testing']); $this->loadMigrationsFrom(dirname(__DIR__) . '/Data/Stubs/Database/Migrations'); } public function test_can_import_to_model_in_chunks_un() { DB::connection()->enableQueryLog(); $import = new class implements ToModel, WithChunkReading, WithEvents { use Importable; public $before = 0; public $after = 0; /** * @param array $row * @return Model|null */ public function model(array $row) { return new User([ 'name' => $row[0], 'email' => $row[1], 'password' => 'secret', ]); } /** * @return int */ public function chunkSize(): int { return 1; } /** * @return array */ public function registerEvents(): array { return [ BeforeImport::class => function (BeforeImport $event) { Assert::assertInstanceOf(Reader::class, $event->reader); $this->before++; }, AfterImport::class => function (AfterImport $event) { Assert::assertInstanceOf(Reader::class, $event->reader); $this->after++; }, ]; } }; $import->import('import-users.xlsx'); $this->assertCount(2, DB::getQueryLog()); DB::connection()->disableQueryLog(); $this->assertEquals(1, $import->before, 'BeforeImport was not called or more than once.'); $this->assertEquals(1, $import->after, 'AfterImport was not called or more than once.'); } public function test_can_import_to_model_in_chunks_and_insert_in_batches() { DB::connection()->enableQueryLog(); $import = new class implements ToModel, WithChunkReading, WithBatchInserts { use Importable; /** * @param array $row * @return Model|null */ public function model(array $row) { return new Group([ 'name' => $row[0], ]); } /** * @return int */ public function chunkSize(): int { return 1000; } /** * @return int */ public function batchSize(): int { return 1000; } }; $import->import('import-batches.xlsx'); $this->assertCount(5000 / $import->batchSize(), DB::getQueryLog()); DB::connection()->disableQueryLog(); } public function test_can_import_to_model_in_chunks_and_insert_in_batches_with_heading_row() { DB::connection()->enableQueryLog(); $import = new class implements ToModel, WithChunkReading, WithBatchInserts, WithHeadingRow { use Importable; /** * @param array $row * @return Model|null */ public function model(array $row) { return new Group([ 'name' => $row['name'], ]); } /** * @return int */ public function chunkSize(): int { return 1000; } /** * @return int */ public function batchSize(): int { return 1000; } }; $import->import('import-batches-with-heading-row.xlsx'); $this->assertCount(5000 / $import->batchSize(), DB::getQueryLog()); DB::connection()->disableQueryLog(); } public function test_can_import_csv_in_chunks_and_insert_in_batches() { DB::connection()->enableQueryLog(); $import = new class implements ToModel, WithChunkReading, WithBatchInserts { use Importable; /** * @param array $row * @return Model|null */ public function model(array $row) { return new Group([ 'name' => $row[0], ]); } /** * @return int */ public function chunkSize(): int { return 1000; } /** * @return int */ public function batchSize(): int { return 1000; } }; $import->import('import-batches.csv'); $this->assertCount(10, DB::getQueryLog()); DB::connection()->disableQueryLog(); } public function test_can_import_to_model_in_chunks_and_insert_in_batches_with_multiple_sheets() { DB::connection()->enableQueryLog(); $import = new class implements ToModel, WithChunkReading, WithBatchInserts { use Importable; /** * @param array $row * @return Model|null */ public function model(array $row) { return new Group([ 'name' => $row[0], ]); } /** * @return int */ public function chunkSize(): int { return 1000; } /** * @return int */ public function batchSize(): int { return 1000; } }; $import->import('import-batches-multiple-sheets.xlsx'); $this->assertCount(10, DB::getQueryLog()); DB::connection()->disableQueryLog(); } public function test_can_import_to_array_in_chunks() { $import = new class implements ToArray, WithChunkReading, WithFormatData { use Importable; public $called = 0; /** * @param array $array */ public function array(array $array) { $this->called++; Assert::assertCount(100, $array); } /** * @return int */ public function chunkSize(): int { return 100; } }; $import->import('import-batches.xlsx'); $this->assertEquals(50, $import->called); } public function test_can_import_to_model_in_chunks_and_insert_in_batches_with_multiple_sheets_objects_by_index() { DB::connection()->enableQueryLog(); $import = new class implements WithMultipleSheets, WithChunkReading { use Importable; /** * @return int */ public function chunkSize(): int { return 1000; } /** * @return array */ public function sheets(): array { return [ new class implements ToModel, WithBatchInserts { /** * @param array $row * @return Model|null */ public function model(array $row) { return new Group([ 'name' => $row[0], ]); } /** * @return int */ public function batchSize(): int { return 1000; } }, new class implements ToModel, WithBatchInserts { /** * @param array $row * @return Model|null */ public function model(array $row) { return new Group([ 'name' => $row[0], ]); } /** * @return int */ public function batchSize(): int { return 2000; } }, ]; } }; $import->import('import-batches-multiple-sheets.xlsx'); $this->assertCount(10, DB::getQueryLog()); DB::connection()->disableQueryLog(); } public function test_can_import_to_model_in_chunks_and_insert_in_batches_with_multiple_sheets_objects_by_name() { DB::connection()->enableQueryLog(); $import = new class implements WithMultipleSheets, WithChunkReading { use Importable; /** * @return int */ public function chunkSize(): int { return 1000; } /** * @return array */ public function sheets(): array { return [ 'Worksheet' => new class implements ToModel, WithBatchInserts { /** * @param array $row * @return Model|null */ public function model(array $row) { return new Group([ 'name' => $row[0], ]); } /** * @return int */ public function batchSize(): int { return 1000; } }, 'Worksheet2' => new class implements ToModel, WithBatchInserts { /** * @param array $row * @return Model|null */ public function model(array $row) { return new Group([ 'name' => $row[0], ]); } /** * @return int */ public function batchSize(): int { return 2000; } }, ]; } }; $import->import('import-batches-multiple-sheets.xlsx'); $this->assertCount(10, DB::getQueryLog()); DB::connection()->disableQueryLog(); } public function test_can_catch_job_failed_in_chunks() { $import = new class implements ToModel, WithChunkReading, WithEvents { use Importable; public $failed = false; /** * @param array $row * @return Model|null */ public function model(array $row) { throw new Exception('Something went wrong in the chunk'); } /** * @return int */ public function chunkSize(): int { return 1; } /** * @return array */ public function registerEvents(): array { return [ ImportFailed::class => function (ImportFailed $event) { Assert::assertInstanceOf(Throwable::class, $event->getException()); Assert::assertEquals('Something went wrong in the chunk', $event->e->getMessage()); $this->failed = true; }, ]; } }; try { $import->import('import-users.xlsx'); } catch (Throwable $e) { $this->assertInstanceOf(Exception::class, $e); $this->assertEquals('Something went wrong in the chunk', $e->getMessage()); } $this->assertTrue($import->failed, 'ImportFailed event was not called.'); } public function test_can_import_to_array_and_format_in_chunks() { config()->set('excel.imports.read_only', false); $import = new class implements ToArray, WithChunkReading, WithFormatData { use Importable; /** * @param array $array */ public function array(array $array) { Assert::assertCount(2, $array); Assert::assertCount(1, $array[0]); Assert::assertCount(1, $array[1]); Assert::assertIsString($array[0][0]); Assert::assertIsString($array[1][0]); Assert::assertEquals('01/12/22', $array[0][0]); Assert::assertEquals('2023-02-20', $array[1][0]); } /** * @return int */ public function chunkSize(): int { return 2; } }; $import->import('import-batches-with-date.xlsx'); } public function test_can_import_to_array_in_chunks_without_formatting() { config()->set('excel.imports.read_only', true); $import = new class implements ToArray, WithChunkReading { use Importable; /** * @param array $array */ public function array(array $array) { Assert::assertCount(2, $array); Assert::assertCount(1, $array[0]); Assert::assertCount(1, $array[1]); Assert::assertIsInt($array[0][0]); Assert::assertIsInt($array[1][0]); Assert::assertEquals((int) Date::dateTimeToExcel(DateTime::createFromFormat('Y-m-d', '2022-12-01')->setTime(0, 0, 0, 0)), $array[0][0]); Assert::assertEquals((int) Date::dateTimeToExcel(DateTime::createFromFormat('Y-m-d', '2023-02-20')->setTime(0, 0, 0, 0)), $array[1][0]); } /** * @return int */ public function chunkSize(): int { return 2; } }; $import->import('import-batches-with-date.xlsx'); } } ================================================ FILE: tests/Concerns/WithColumnFormattingTest.php ================================================ NumberFormat::FORMAT_DATE_DDMMYYYY, 'B4:B4' => NumberFormat::FORMAT_CURRENCY_EUR, ]; } }; $response = $export->store('with-column-formatting-store.xlsx'); $this->assertTrue($response); $actual = $this->readAsArray(__DIR__ . '/../Data/Disks/Local/with-column-formatting-store.xlsx', 'Xlsx'); $legacyPhpSpreadsheet = !InstalledVersions::satisfies(new VersionParser, 'phpoffice/phpspreadsheet', '^1.28'); $expected = [ ['06/03/2018', null], ['07/03/2018', null], ['08/03/2018', null], ['06/12/2021', $legacyPhpSpreadsheet ? '100 €' : '100.00 €'], ]; $this->assertEquals($expected, $actual); } } ================================================ FILE: tests/Concerns/WithColumnLimitTest.php ================================================ loadLaravelMigrations(['--database' => 'testing']); } public function test_can_import_to_array_with_column_limit() { $import = new class implements ToArray, WithColumnLimit { use Importable; /** * @param array $array */ public function array(array $array) { Assert::assertEquals([ [ 'Patrick Brouwers', ], [ 'Taylor Otwell', ], ], $array); } public function endColumn(): string { return 'A'; } }; $import->import('import-users.xlsx'); } public function test_can_import_to_array_with_column_limit_and_skips_empty_rows() { $import = new class implements ToArray, WithColumnLimit, SkipsEmptyRows { use Importable; /** * @param array $array */ public function array(array $array) { Assert::assertEquals([ [ 'Test1', 'Test2', null, null, ], [ 'Test3', 'Test4', null, null, ], [ 'Test5', 'Test6', null, null, ], ], $array); } public function endColumn(): string { return 'D'; } }; $import->import('import-empty-rows.xlsx'); } } ================================================ FILE: tests/Concerns/WithColumnWidthsTest.php ================================================ 55, ]; } public function array(): array { return [ ['AA'], ['BB'], ]; } }; $export->store('with-column-widths.xlsx'); $spreadsheet = $this->read(__DIR__ . '/../Data/Disks/Local/with-column-widths.xlsx', 'Xlsx'); $this->assertEquals(55, $spreadsheet->getActiveSheet()->getColumnDimension('A')->getWidth()); } } ================================================ FILE: tests/Concerns/WithConditionalSheetsTest.php ================================================ withFactories(__DIR__ . '/../Data/Stubs/Database/Factories'); } public function test_can_select_which_sheets_will_be_imported() { $import = new class implements WithMultipleSheets { use Importable, WithConditionalSheets; public $sheets = []; public function __construct() { $this->init(); } public function init() { $this->sheets = [ 'Sheet1' => new class implements ToArray { public $called = false; public function array(array $array) { $this->called = true; } }, 'Sheet2' => new class implements ToArray { public $called = false; public function array(array $array) { $this->called = true; } }, ]; } /** * @return array */ public function conditionalSheets(): array { return $this->sheets; } }; $import->onlySheets('Sheet1')->import('import-multiple-sheets.xlsx'); $this->assertTrue($import->sheets['Sheet1']->called); $this->assertFalse($import->sheets['Sheet2']->called); $import->init(); $import->onlySheets('Sheet2')->import('import-multiple-sheets.xlsx'); $this->assertTrue($import->sheets['Sheet2']->called); $this->assertFalse($import->sheets['Sheet1']->called); $import->init(); $import->onlySheets(['Sheet1', 'Sheet2'])->import('import-multiple-sheets.xlsx'); $this->assertTrue($import->sheets['Sheet1']->called); $this->assertTrue($import->sheets['Sheet2']->called); $import->init(); $import->onlySheets('Sheet1', 'Sheet2')->import('import-multiple-sheets.xlsx'); $this->assertTrue($import->sheets['Sheet1']->called); $this->assertTrue($import->sheets['Sheet2']->called); } } ================================================ FILE: tests/Concerns/WithCustomCsvSettingsTest.php ================================================ SUT = $this->app->make(Excel::class); } public function test_can_store_csv_export_with_custom_settings() { $export = new class implements FromCollection, WithCustomCsvSettings { /** * @return Collection */ public function collection() { return collect([ ['A1', 'B1'], ['A2', 'B2'], ]); } /** * @return array */ public function getCsvSettings(): array { return [ 'delimiter' => ';', 'enclosure' => '', 'line_ending' => PHP_EOL, 'use_bom' => true, 'include_separator_line' => true, 'excel_compatibility' => false, 'output_encoding' => '', 'test_auto_detect' => false, ]; } }; $this->SUT->store($export, 'custom-csv.csv'); $contents = file_get_contents(__DIR__ . '/../Data/Disks/Local/custom-csv.csv'); $this->assertStringContains('sep=;', $contents); $this->assertStringContains('A1;B1', $contents); $this->assertStringContains('A2;B2', $contents); } public function test_can_store_csv_export_with_custom_encoding() { $export = new class implements FromCollection, WithCustomCsvSettings { /** * @return Collection */ public function collection() { return collect([ ['A1', '€ŠšŽžŒœŸ'], ['A2', 'åßàèòìù'], ]); } /** * @return array */ public function getCsvSettings(): array { return [ 'delimiter' => ';', 'enclosure' => '', 'line_ending' => PHP_EOL, 'use_bom' => false, 'include_separator_line' => true, 'excel_compatibility' => false, 'output_encoding' => 'ISO-8859-15', ]; } }; $this->SUT->store($export, 'custom-csv-iso.csv'); $contents = file_get_contents(__DIR__ . '/../Data/Disks/Local/custom-csv-iso.csv'); Assert::assertEquals('ISO-8859-15', mb_detect_encoding($contents, 'ISO-8859-15', true)); Assert::assertFalse(mb_detect_encoding($contents, 'UTF-8', true)); $contents = mb_convert_encoding($contents, 'UTF-8', 'ISO-8859-15'); $this->assertStringContains('sep=;', $contents); $this->assertStringContains('A1;€ŠšŽžŒœŸ', $contents); $this->assertStringContains('A2;åßàèòìù', $contents); } public function test_can_read_csv_with_auto_detecting_delimiter_semicolon() { $this->assertEquals([ [ ['a1', 'b1'], ], ], (new HeadingRowImport())->toArray('csv-with-other-delimiter.csv')); } public function test_can_read_csv_with_auto_detecting_delimiter_comma() { $this->assertEquals([ [ ['a1', 'b1'], ], ], (new HeadingRowImport())->toArray('csv-with-comma.csv')); } public function test_can_read_csv_import_with_custom_settings() { $import = new class implements WithCustomCsvSettings, ToArray { /** * @return array */ public function getCsvSettings(): array { return [ 'delimiter' => ';', 'enclosure' => '', 'escape_character' => '\\', 'contiguous' => true, 'input_encoding' => 'UTF-8', ]; } /** * @param array $array */ public function array(array $array) { Assert::assertEquals([ ['A1', 'B1'], ['A2', 'B2'], ], $array); } }; $this->SUT->import($import, 'csv-with-other-delimiter.csv'); } public function test_cannot_read_with_wrong_delimiter() { $import = new class implements WithCustomCsvSettings, ToArray { /** * @return array */ public function getCsvSettings(): array { return [ 'delimiter' => ',', ]; } /** * @param array $array */ public function array(array $array) { Assert::assertEquals([ ['A1;B1'], ['A2;B2'], ], $array); } }; $this->SUT->import($import, 'csv-with-other-delimiter.csv'); } } ================================================ FILE: tests/Concerns/WithCustomQuerySizeTest.php ================================================ loadLaravelMigrations(['--database' => 'testing']); $this->loadMigrationsFrom(dirname(__DIR__) . '/Data/Stubs/Database/Migrations'); $this->withFactories(dirname(__DIR__) . '/Data/Stubs/Database/Factories'); factory(Group::class)->times(5)->create()->each(function ($group) { $group->users()->attach(factory(User::class)->times(rand(1, 3))->create()); }); config()->set('excel.exports.chunk_size', 2); } public function test_can_export_with_custom_count() { $export = new FromQueryWithCustomQuerySize(); $export->queue('export-from-query-with-count.xlsx', null, 'Xlsx')->chain([ new AfterQueueExportJob(dirname(__DIR__) . '/Data/Disks/Local/export-from-query-with-count.xlsx'), ]); $actual = $this->readAsArray(dirname(__DIR__) . '/Data/Disks/Local/export-from-query-with-count.xlsx', 'Xlsx'); $this->assertCount(Group::count(), $actual); } } ================================================ FILE: tests/Concerns/WithCustomStartCellTest.php ================================================ SUT = $this->app->make(Excel::class); } public function test_can_store_collection_with_custom_start_cell() { $export = new class implements FromCollection, WithCustomStartCell { /** * @return Collection */ public function collection() { return collect([ ['A1', 'B1'], ['A2', 'B2'], ]); } /** * @return string */ public function startCell(): string { return 'B2'; } }; $this->SUT->store($export, 'custom-start-cell.csv'); $contents = $this->readAsArray(__DIR__ . '/../Data/Disks/Local/custom-start-cell.csv', 'Csv'); $this->assertEquals([ [null, null, null], [null, 'A1', 'B1'], [null, 'A2', 'B2'], ], $contents); } } ================================================ FILE: tests/Concerns/WithCustomValueBinderTest.php ================================================ setValueExplicit( (float) str_replace('%', '', $value) / 100, DataType::TYPE_NUMERIC ); $cell ->getWorksheet() ->getStyle($cell->getCoordinate()) ->getNumberFormat() ->setFormatCode(NumberFormat::FORMAT_PERCENTAGE_00); return true; } // Handle Carbon dates if ($value instanceof Carbon) { $cell->setValueExplicit( Date::dateTimeToExcel($value), DataType::TYPE_NUMERIC ); $cell->getWorksheet() ->getStyle($cell->getCoordinate()) ->getNumberFormat() ->setFormatCode(NumberFormat::FORMAT_DATE_DATETIME); return true; } return parent::bindValue($cell, $value); } }; $export->store('custom-value-binder-export.xlsx'); $spreadsheet = $this->read(__DIR__ . '/../Data/Disks/Local/custom-value-binder-export.xlsx', 'Xlsx'); $sheet = $spreadsheet->getActiveSheet(); // Check if the cell has the Excel date $this->assertSame(Date::dateTimeToExcel(Carbon::now()), $sheet->getCell('A1')->getValue()); // Check if formatted as datetime $this->assertEquals(NumberFormat::FORMAT_DATE_DATETIME, $sheet->getCell('A1')->getStyle()->getNumberFormat()->getFormatCode()); // Check if the cell has the converted percentage $this->assertSame(0.1, $sheet->getCell('B1')->getValue()); // Check if formatted as percentage $this->assertEquals(NumberFormat::FORMAT_PERCENTAGE_00, $sheet->getCell('B1')->getStyle()->getNumberFormat()->getFormatCode()); } public function test_can_set_a_value_binder_on_import() { $import = new class extends DefaultValueBinder implements WithCustomValueBinder, ToArray { /** * {@inheritdoc} */ public function bindValue(Cell $cell, $value) { if ($cell->getCoordinate() === 'B2') { $cell->setValueExplicit($value, DataType::TYPE_STRING); return true; } if ($cell->getRow() === 3) { $date = Carbon::instance(Date::excelToDateTimeObject($value)); $cell->setValueExplicit($date->toDateTimeString(), DataType::TYPE_STRING); return true; } return parent::bindValue($cell, $value); } /** * @param array $array */ public function array(array $array) { Assert::assertSame([ [ 'col1', 'col2', ], [ 1, '2', // Forced to be a string ], [ '2018-08-06 18:31:46', // Convert Excel datetime to datetime strings '2018-08-07 00:00:00', // Convert Excel date to datetime strings ], ], $array); } }; $this->app->make(Excel::class)->import($import, 'value-binder-import.xlsx'); } } ================================================ FILE: tests/Concerns/WithDefaultStylesTest.php ================================================ [ 'fillType' => Fill::FILL_SOLID, 'startColor' => ['argb' => 'fff2f2f2'], ], ]; } public function array(): array { return [ ['A1', 'B1', 'C1'], ['A2', 'B2', 'C2'], ]; } }; $export->store('with-default-styles.xlsx'); $spreadsheet = $this->read(__DIR__ . '/../Data/Disks/Local/with-default-styles.xlsx', 'Xlsx'); $sheet = $spreadsheet->getDefaultStyle(); $this->assertEquals(Fill::FILL_SOLID, $sheet->getFill()->getFillType()); $this->assertEquals('fff2f2f2', $sheet->getFill()->getStartColor()->getARGB()); } } ================================================ FILE: tests/Concerns/WithEventsTest.php ================================================ beforeExport = function ($event) use (&$eventsTriggered) { $this->assertInstanceOf(BeforeExport::class, $event); $this->assertInstanceOf(Writer::class, $event->getWriter()); $eventsTriggered++; }; $event->beforeWriting = function ($event) use (&$eventsTriggered) { $this->assertInstanceOf(BeforeWriting::class, $event); $this->assertInstanceOf(Writer::class, $event->getWriter()); $eventsTriggered++; }; $event->beforeSheet = function ($event) use (&$eventsTriggered) { $this->assertInstanceOf(BeforeSheet::class, $event); $this->assertInstanceOf(Sheet::class, $event->getSheet()); $eventsTriggered++; }; $event->afterSheet = function ($event) use (&$eventsTriggered) { $this->assertInstanceOf(AfterSheet::class, $event); $this->assertInstanceOf(Sheet::class, $event->getSheet()); $eventsTriggered++; }; $this->assertInstanceOf(BinaryFileResponse::class, $event->download('filename.xlsx')); $this->assertEquals(4, $eventsTriggered); } public function test_import_events_get_called() { $import = new ImportWithEvents(); $eventsTriggered = 0; $import->beforeImport = function ($event) use (&$eventsTriggered) { $this->assertInstanceOf(BeforeImport::class, $event); $this->assertInstanceOf(Reader::class, $event->getReader()); $eventsTriggered++; }; $import->afterImport = function ($event) use (&$eventsTriggered) { $this->assertInstanceOf(AfterImport::class, $event); $this->assertInstanceOf(Reader::class, $event->getReader()); $eventsTriggered++; }; $import->beforeSheet = function ($event) use (&$eventsTriggered) { $this->assertInstanceOf(BeforeSheet::class, $event); $this->assertInstanceOf(Sheet::class, $event->getSheet()); $eventsTriggered++; }; $import->afterSheet = function ($event) use (&$eventsTriggered) { $this->assertInstanceOf(AfterSheet::class, $event); $this->assertInstanceOf(Sheet::class, $event->getSheet()); $eventsTriggered++; }; $import->import('import.xlsx'); $this->assertEquals(4, $eventsTriggered); } public function test_import_chunked_events_get_called() { $import = new ImportWithEventsChunksAndBatches(); $beforeImport = 0; $afterImport = 0; $beforeSheet = 0; $afterSheet = 0; $afterBatch = 0; $afterChunk = 0; $import->beforeImport = function (BeforeImport $event) use (&$beforeImport) { $this->assertInstanceOf(Reader::class, $event->getReader()); // Ensure event is fired only once $this->assertEquals(0, $beforeImport, 'Before import called twice'); $beforeImport++; }; $import->afterImport = function (AfterImport $event) use (&$afterImport) { $this->assertInstanceOf(Reader::class, $event->getReader()); $this->assertEquals(0, $afterImport, 'After import called twice'); $afterImport++; }; $import->beforeSheet = function (BeforeSheet $event) use (&$beforeSheet) { $this->assertInstanceOf(Sheet::class, $event->getSheet()); $beforeSheet++; }; $import->afterSheet = function (AfterSheet $event) use (&$afterSheet) { $this->assertInstanceOf(Sheet::class, $event->getSheet()); $afterSheet++; }; $import->afterBatch = function (AfterBatch $event) use ($import, &$afterBatch) { $this->assertEquals( $import->batchSize(), $event->getBatchSize(), 'Wrong Batch size' ); $this->assertEquals( $afterBatch * $import->batchSize() + 1, $event->getStartRow(), 'Wrong batch start row'); $afterBatch++; }; $import->afterChunk = function (AfterChunk $event) use ($import, &$afterChunk) { $this->assertEquals( $event->getStartRow(), $afterChunk * $import->chunkSize() + 1, 'Wrong chunk start row'); $afterChunk++; }; $import->import('import-batches.xlsx'); $this->assertEquals(10, $afterSheet); $this->assertEquals(10, $beforeSheet); $this->assertEquals(50, $afterBatch); $this->assertEquals(10, $afterChunk); } public function test_can_have_invokable_class_as_listener() { $event = new ExportWithEvents(); $event->beforeExport = new BeforeExportListener(function ($event) { $this->assertInstanceOf(BeforeExport::class, $event); $this->assertInstanceOf(Writer::class, $event->getWriter()); }); $this->assertInstanceOf(BinaryFileResponse::class, $event->download('filename.xlsx')); } public function test_can_have_global_event_listeners() { $event = new class { use Exportable; }; $beforeExport = false; Writer::listen(BeforeExport::class, function () use (&$beforeExport) { $beforeExport = true; }); $beforeWriting = false; Writer::listen(BeforeWriting::class, function () use (&$beforeWriting) { $beforeWriting = true; }); $beforeSheet = false; Sheet::listen(BeforeSheet::class, function () use (&$beforeSheet) { $beforeSheet = true; }); $afterSheet = false; Sheet::listen(AfterSheet::class, function () use (&$afterSheet) { $afterSheet = true; }); $this->assertInstanceOf(BinaryFileResponse::class, $event->download('filename.xlsx')); $this->assertTrue($beforeExport, 'Before export event not triggered'); $this->assertTrue($beforeWriting, 'Before writing event not triggered'); $this->assertTrue($beforeSheet, 'Before sheet event not triggered'); $this->assertTrue($afterSheet, 'After sheet event not triggered'); } public function test_can_have_custom_concern_handlers() { // Add a custom concern handler for the given concern. Excel::extend(CustomConcern::class, function (CustomConcern $exportable, Writer $writer) { $writer->getSheetByIndex(0)->append( $exportable->custom() ); }); $exportWithConcern = new class implements CustomConcern { use Exportable; public function custom() { return [ ['a', 'b'], ]; } }; $exportWithConcern->store('with-custom-concern.xlsx'); $actual = $this->readAsArray(__DIR__ . '/../Data/Disks/Local/with-custom-concern.xlsx', 'Xlsx'); $this->assertEquals([ ['a', 'b'], ], $actual); $exportWithoutConcern = new class { use Exportable; }; $exportWithoutConcern->store('without-custom-concern.xlsx'); $actual = $this->readAsArray(__DIR__ . '/../Data/Disks/Local/without-custom-concern.xlsx', 'Xlsx'); $this->assertEquals([[null]], $actual); } public function test_can_have_custom_sheet_concern_handlers() { // Add a custom concern handler for the given concern. Excel::extend(CustomSheetConcern::class, function (CustomSheetConcern $exportable, Sheet $sheet) { $sheet->append( $exportable->custom() ); }, AfterSheet::class); $exportWithConcern = new class implements CustomSheetConcern { use Exportable; public function custom() { return [ ['c', 'd'], ]; } }; $exportWithConcern->store('with-custom-concern.xlsx'); $actual = $this->readAsArray(__DIR__ . '/../Data/Disks/Local/with-custom-concern.xlsx', 'Xlsx'); $this->assertEquals([ ['c', 'd'], ], $actual); $exportWithoutConcern = new class { use Exportable; }; $exportWithoutConcern->store('without-custom-concern.xlsx'); $actual = $this->readAsArray(__DIR__ . '/../Data/Disks/Local/without-custom-concern.xlsx', 'Xlsx'); $this->assertEquals([[null]], $actual); } public function test_export_chunked_events_get_called() { $this->loadLaravelMigrations(['--database' => 'testing']); User::query()->truncate(); User::query()->create([ 'name' => $this->faker->name, 'email' => $this->faker->unique()->safeEmail, 'password' => '$2y$10$TKh8H1.PfQx37YgCzwiKb.KjNyWgaHb9cbcoQgdIVFlYg7B77UdFm', // secret 'remember_token' => Str::random(10), ]); User::query()->create([ 'name' => $this->faker->name, 'email' => $this->faker->unique()->safeEmail, 'password' => '$2y$10$TKh8H1.PfQx37YgCzwiKb.KjNyWgaHb9cbcoQgdIVFlYg7B77UdFm', // secret 'remember_token' => Str::random(10), ]); $export = new ExportWithEventsChunks(); $export->queue('filename.xlsx'); // Chunk size is 1, so we expect 2 chunks to be executed with a total of 2 users $this->assertEquals(2, ExportWithEventsChunks::$calledEvent); } } ================================================ FILE: tests/Concerns/WithFormatDataTest.php ================================================ called = true; Assert::assertSame(44328, $array[0][0]); } }; $import->import('import-format-data.xlsx'); $this->assertTrue($import->called); } public function test_can_import_to_array_with_format_data() { config()->set('excel.imports.read_only', false); $import = new class implements ToArray, WithFormatData { use Importable; public $called = false; /** * @param array $array */ public function array(array $array) { $this->called = true; Assert::assertSame('5/12/2021', $array[0][0]); } }; $import->import('import-format-data.xlsx'); $this->assertTrue($import->called); } public function test_can_import_to_array_with_format_data_and_skips_empty_rows() { config()->set('excel.imports.read_only', false); $import = new class implements ToArray, WithFormatData, SkipsEmptyRows { use Importable; public $called = false; /** * @param array $array */ public function array(array $array) { $this->called = true; Assert::assertSame('5/12/2021', $array[0][0]); } }; $import->import('import-format-data.xlsx'); $this->assertTrue($import->called); } public function test_by_default_import_to_collection() { $import = new class implements ToCollection { use Importable; public $called = false; /** * @param array $row * @return Model|null */ public function collection(collection $collection) { $this->called = true; Assert::assertSame(44328, $collection[0][0]); return null; } }; $import->import('import-format-data.xlsx'); $this->assertTrue($import->called); } public function test_can_import_to_collection_with_format_data() { config()->set('excel.imports.read_only', false); $import = new class implements ToCollection, WithFormatData { use Importable; public $called = false; /** * @param array $row * @return Model|null */ public function collection(collection $collection) { $this->called = true; Assert::assertSame('5/12/2021', $collection[0][0]); return null; } }; $import->import('import-format-data.xlsx'); $this->assertTrue($import->called); } public function test_by_default_import_to_model() { $import = new class implements ToModel { use Importable; public $called = false; /** * @param array $row * @return Model|null */ public function model(array $row) { $this->called = true; Assert::assertSame(44328, $row[0]); return null; } }; $import->import('import-format-data.xlsx'); $this->assertTrue($import->called); } public function test_can_import_to_model_with_format_data() { config()->set('excel.imports.read_only', false); $import = new class implements ToModel, WithFormatData { use Importable; public $called = false; /** * @param array $row * @return Model|null */ public function model(array $row) { $this->called = true; Assert::assertSame('5/12/2021', $row[0]); return null; } }; $import->import('import-format-data.xlsx'); $this->assertTrue($import->called); } } ================================================ FILE: tests/Concerns/WithGroupedHeadingRowTest.php ================================================ loadLaravelMigrations(['--database' => 'testing']); $this->loadMigrationsFrom(dirname(__DIR__) . '/Data/Stubs/Database/Migrations'); } public function test_can_import_to_array_with_grouped_headers() { $import = new class implements ToArray, WithGroupedHeadingRow { use Importable; /** * @param array $array */ public function array(array $array) { Assert::assertEquals([ [ 'name' => 'Patrick Brouwers', 'email' => 'patrick@maatwebsite.nl', 'options' => [ 'laravel', 'excel', ], ], ], $array); } }; $import->import('import-users-with-grouped-headers.xlsx'); } public function test_can_import_oneachrow_with_grouped_headers() { $import = new class implements OnEachRow, WithGroupedHeadingRow { use Importable; /** * @param \Maatwebsite\Excel\Row $row * @return void */ public function onRow(Row $row) { Assert::assertEquals( [ 'name' => 'Patrick Brouwers', 'email' => 'patrick@maatwebsite.nl', 'options' => [ 'laravel', 'excel', ], ], $row->toArray()); } }; $import->import('import-users-with-grouped-headers.xlsx'); } public function test_can_import_to_collection_with_grouped_headers() { $import = new class implements ToCollection, WithGroupedHeadingRow { use Importable; public $called = false; /** * @param Collection $collection */ public function collection(Collection $collection) { $this->called = true; Assert::assertEquals([ [ 'name' => 'Patrick Brouwers', 'email' => 'patrick@maatwebsite.nl', 'options' => [ 'laravel', 'excel', ], ], ], $collection->toArray()); } }; $import->import('import-users-with-grouped-headers.xlsx'); $this->assertTrue($import->called); } public function test_can_import_each_row_to_model_with_grouped_headers() { $import = new class implements ToModel, WithGroupedHeadingRow { use Importable; /** * @param array $row * @return Model */ public function model(array $row): Model { return new User([ 'name' => $row['name'], 'email' => $row['email'], 'password' => 'secret', 'options' => $row['options'], ]); } }; $import->import('import-users-with-grouped-headers.xlsx'); $this->assertDatabaseHas('users', [ 'name' => 'Patrick Brouwers', 'email' => 'patrick@maatwebsite.nl', 'options' => '["laravel","excel"]', ]); } } ================================================ FILE: tests/Concerns/WithHeadingRowTest.php ================================================ loadLaravelMigrations(['--database' => 'testing']); } public function test_can_import_each_row_to_model_with_heading_row() { $import = new class implements ToModel, WithHeadingRow { use Importable; /** * @param array $row * @return Model */ public function model(array $row): Model { return new User([ 'name' => $row['name'], 'email' => $row['email'], 'password' => 'secret', ]); } }; $import->import('import-users-with-headings.xlsx'); $this->assertDatabaseHas('users', [ 'name' => 'Patrick Brouwers', 'email' => 'patrick@maatwebsite.nl', ]); $this->assertDatabaseHas('users', [ 'name' => 'Taylor Otwell', 'email' => 'taylor@laravel.com', ]); } public function test_can_import_each_row_to_model_with_different_heading_row() { $import = new class implements ToModel, WithHeadingRow { use Importable; /** * @param array $row * @return Model */ public function model(array $row): Model { return new User([ 'name' => $row['name'], 'email' => $row['email'], 'password' => 'secret', ]); } /** * @return int */ public function headingRow(): int { return 4; } }; $import->import('import-users-with-different-heading-row.xlsx'); $this->assertDatabaseHas('users', [ 'name' => 'Patrick Brouwers', 'email' => 'patrick@maatwebsite.nl', ]); $this->assertDatabaseHas('users', [ 'name' => 'Taylor Otwell', 'email' => 'taylor@laravel.com', ]); } public function test_can_import_to_array_with_heading_row() { $import = new class implements ToArray, WithHeadingRow { use Importable; /** * @param array $array */ public function array(array $array) { Assert::assertEquals([ [ 'name' => 'Patrick Brouwers', 'email' => 'patrick@maatwebsite.nl', ], [ 'name' => 'Taylor Otwell', 'email' => 'taylor@laravel.com', ], ], $array); } }; $import->import('import-users-with-headings.xlsx'); } public function test_can_import_empty_rows_with_header() { $import = new class() implements ToArray, WithHeadingRow { use Importable; /** * @param array $array */ public function array(array $array) { Assert::assertEmpty($array); } }; $import->import('import-empty-users-with-headings.xlsx'); } public function test_can_import_empty_models_with_header() { $import = new class() implements ToModel, WithHeadingRow { use Importable; /** * @param array $row * @return Model */ public function model(array $row): Model { return new User([ 'name' => $row['name'], 'email' => $row['email'], 'password' => 'secret', ]); } }; $import->import('import-empty-users-with-headings.xlsx'); $this->assertEmpty(User::all()); } public function test_can_cast_empty_headers_to_indexed_int() { $import = new class() implements ToCollection, WithHeadingRow { use Importable; public $called = false; public function collection(Collection $collection) { $this->called = true; Assert::assertEquals([ 0 => 0, 1 => 'email', 2 => 'status', 3 => 3, ], $collection->first()->keys()->toArray()); } }; $import->import('import-users-with-mixed-headings.xlsx'); $this->assertTrue($import->called); } } ================================================ FILE: tests/Concerns/WithHeadingsTest.php ================================================ store('with-heading-store.xlsx'); $this->assertTrue($response); $actual = $this->readAsArray(__DIR__ . '/../Data/Disks/Local/with-heading-store.xlsx', 'Xlsx'); $expected = [ ['A', 'B', 'C'], ['A1', 'B1', 'C1'], ['A2', 'B2', 'C2'], ]; $this->assertEquals($expected, $actual); } public function test_can_export_from_collection_with_multiple_heading_rows() { $export = new class implements FromCollection, WithHeadings { use Exportable; /** * @return Collection */ public function collection() { return collect([ ['A1', 'B1', 'C1'], ['A2', 'B2', 'C2'], ]); } /** * @return array */ public function headings(): array { return [ ['A', 'B', 'C'], ['Aa', 'Bb', 'Cc'], ]; } }; $response = $export->store('with-heading-store.xlsx'); $this->assertTrue($response); $actual = $this->readAsArray(__DIR__ . '/../Data/Disks/Local/with-heading-store.xlsx', 'Xlsx'); $expected = [ ['A', 'B', 'C'], ['Aa', 'Bb', 'Cc'], ['A1', 'B1', 'C1'], ['A2', 'B2', 'C2'], ]; $this->assertEquals($expected, $actual); } public function test_can_export_from_collection_with_heading_row_with_custom_start_cell() { $export = new class implements FromCollection, WithHeadings, WithCustomStartCell { use Exportable; /** * @return Collection */ public function collection() { return collect([ ['A1', 'B1', 'C1'], ['A2', 'B2', 'C2'], ]); } /** * @return array */ public function headings(): array { return ['A', 'B', 'C']; } /** * @return string */ public function startCell(): string { return 'B2'; } }; $response = $export->store('with-heading-store.xlsx'); $this->assertTrue($response); $actual = $this->readAsArray(__DIR__ . '/../Data/Disks/Local/with-heading-store.xlsx', 'Xlsx'); $expected = [ [null, null, null, null], [null, 'A', 'B', 'C'], [null, 'A1', 'B1', 'C1'], [null, 'A2', 'B2', 'C2'], ]; $this->assertEquals($expected, $actual); } } ================================================ FILE: tests/Concerns/WithLimitTest.php ================================================ loadLaravelMigrations(['--database' => 'testing']); } public function test_can_import_a_limited_section_of_rows_to_model_with_different_start_row() { $import = new class implements ToModel, WithStartRow, WithLimit { use Importable; /** * @param array $row * @return Model */ public function model(array $row): Model { return new User([ 'name' => $row[0], 'email' => $row[1], 'password' => 'secret', ]); } /** * @return int */ public function startRow(): int { return 5; } /** * @return int */ public function limit(): int { return 1; } }; $import->import('import-users-with-different-heading-row.xlsx'); $this->assertDatabaseHas('users', [ 'name' => 'Patrick Brouwers', 'email' => 'patrick@maatwebsite.nl', ]); $this->assertDatabaseMissing('users', [ 'name' => 'Taylor Otwell', 'email' => 'taylor@laravel.com', ]); } public function test_can_import_to_array_with_limit() { $import = new class implements ToArray, WithLimit { use Importable; /** * @param array $array */ public function array(array $array) { Assert::assertEquals([ [ 'Patrick Brouwers', 'patrick@maatwebsite.nl', ], ], $array); } /** * @return int */ public function limit(): int { return 1; } }; $import->import('import-users.xlsx'); } public function test_can_import_single_with_heading_row() { $import = new class implements ToArray, WithLimit, WithHeadingRow { use Importable; /** * @param array $array */ public function array(array $array) { Assert::assertEquals([ [ 'name' => 'Patrick Brouwers', 'email' => 'patrick@maatwebsite.nl', ], ], $array); } /** * @return int */ public function limit(): int { return 1; } }; $import->import('import-users-with-headings.xlsx'); } public function test_can_import_multiple_with_heading_row() { $import = new class implements ToArray, WithLimit, WithHeadingRow { use Importable; /** * @param array $array */ public function array(array $array) { Assert::assertEquals([ [ 'name' => 'Patrick Brouwers', 'email' => 'patrick@maatwebsite.nl', ], [ 'name' => 'Taylor Otwell', 'email' => 'taylor@laravel.com', ], ], $array); } /** * @return int */ public function limit(): int { return 2; } }; $import->import('import-users-with-headings.xlsx'); } public function test_can_set_limit_bigger_than_row_size() { $import = new class implements ToArray, WithLimit { use Importable; /** * @param array $array */ public function array(array $array) { Assert::assertCount(2, $array); } /** * @return int */ public function limit(): int { return 10; } }; $import->import('import-users.xlsx'); } } ================================================ FILE: tests/Concerns/WithMappedCellsTest.php ================================================ loadLaravelMigrations(['--database' => 'testing']); } public function test_can_import_with_references_to_cells() { $import = new class implements WithMappedCells, ToArray { use Importable; /** * @return array */ public function mapping(): array { return [ 'name' => 'B1', 'email' => 'B2', ]; } /** * @param array $array */ public function array(array $array) { Assert::assertEquals([ 'name' => 'Patrick Brouwers', 'email' => 'patrick@maatwebsite.nl', ], $array); } }; $import->import('mapped-import.xlsx'); } public function test_can_import_with_nested_references_to_cells() { $import = new class implements WithMappedCells, ToArray { use Importable; /** * @return array */ public function mapping(): array { return [ [ 'name' => 'B1', 'email' => 'B2', ], [ 'name' => 'D1', 'email' => 'D2', ], ]; } /** * @param array $array */ public function array(array $array) { Assert::assertEquals([ [ 'name' => 'Patrick Brouwers', 'email' => 'patrick@maatwebsite.nl', ], [ 'name' => 'Typingbeaver', 'email' => 'typingbeaver@mailbox.org', ], ], $array); } }; $import->import('mapped-import.xlsx'); } public function test_can_import_with_references_to_cells_to_model() { $import = new class implements WithMappedCells, ToModel { use Importable; /** * @return array */ public function mapping(): array { return [ 'name' => 'B1', 'email' => 'B2', ]; } /** * @param array $array * @return User */ public function model(array $array) { Assert::assertEquals([ 'name' => 'Patrick Brouwers', 'email' => 'patrick@maatwebsite.nl', ], $array); $array['password'] = Str::random(); return new User($array); } }; $import->import('mapped-import.xlsx'); $this->assertDatabaseHas('users', [ 'name' => 'Patrick Brouwers', 'email' => 'patrick@maatwebsite.nl', ]); } } ================================================ FILE: tests/Concerns/WithMappingTest.php ================================================ store('with-mapping-store.xlsx'); $this->assertTrue($response); $actual = $this->readAsArray(__DIR__ . '/../Data/Disks/Local/with-mapping-store.xlsx', 'Xlsx'); $expected = [ [ 'mapped-A1', 'mapped-B1', 'mapped-C1', ], [ 'mapped-A2', 'mapped-B2', 'mapped-C2', ], ]; $this->assertEquals($expected, $actual); } public function test_can_return_multiple_rows_in_map() { $export = new class implements FromArray, WithMapping { use Exportable; /** * @return array */ public function array(): array { return [ ['id' => 1], ['id' => 2], ['id' => 3], ]; } /** * @param mixed $row * @return array */ public function map($row): array { return [ [$row['id']], [$row['id']], ]; } }; $response = $export->store('with-mapping-store.xlsx'); $this->assertTrue($response); $actual = $this->readAsArray(__DIR__ . '/../Data/Disks/Local/with-mapping-store.xlsx', 'Xlsx'); $this->assertCount(6, $actual); } public function test_json_array_columns_shouldnt_be_detected_as_multiple_rows() { $export = new class implements FromArray { use Exportable; /** * @return array */ public function array(): array { return [ ['id' => 1, 'json' => ['other_id' => 1]], ['id' => 2, 'json' => ['other_id' => 2]], ['id' => 3, 'json' => ['other_id' => 3]], ]; } }; $response = $export->store('with-mapping-store.xlsx'); $this->assertTrue($response); $actual = $this->readAsArray(__DIR__ . '/../Data/Disks/Local/with-mapping-store.xlsx', 'Xlsx'); $this->assertCount(3, $actual); $this->assertEquals([ [1, \json_encode(['other_id' => 1])], [2, \json_encode(['other_id' => 2])], [3, \json_encode(['other_id' => 3])], ], $actual); } } ================================================ FILE: tests/Concerns/WithMultipleSheetsTest.php ================================================ withFactories(__DIR__ . '/../Data/Stubs/Database/Factories'); } public function test_can_export_with_multiple_sheets_using_collections() { $export = new class implements WithMultipleSheets { use Exportable; /** * @return SheetWith100Rows[] */ public function sheets(): array { return [ new SheetWith100Rows('A'), new SheetWith100Rows('B'), new SheetWith100Rows('C'), ]; } }; $export->store('from-view.xlsx'); $this->assertCount(100, $this->readAsArray(__DIR__ . '/../Data/Disks/Local/from-view.xlsx', 'Xlsx', 0)); $this->assertCount(100, $this->readAsArray(__DIR__ . '/../Data/Disks/Local/from-view.xlsx', 'Xlsx', 1)); $this->assertCount(100, $this->readAsArray(__DIR__ . '/../Data/Disks/Local/from-view.xlsx', 'Xlsx', 2)); } public function test_can_export_multiple_sheets_from_view() { /** @var Collection|User[] $users */ $users = factory(User::class)->times(300)->make(); $export = new class($users) implements WithMultipleSheets { use Exportable; /** * @var Collection */ protected $users; /** * @param Collection $users */ public function __construct(Collection $users) { $this->users = $users; } /** * @return SheetForUsersFromView[] */ public function sheets(): array { return [ new SheetForUsersFromView($this->users->forPage(1, 100)), new SheetForUsersFromView($this->users->forPage(2, 100)), new SheetForUsersFromView($this->users->forPage(3, 100)), ]; } }; $export->store('from-view.xlsx'); $this->assertCount(101, $this->readAsArray(__DIR__ . '/../Data/Disks/Local/from-view.xlsx', 'Xlsx', 0)); $this->assertCount(101, $this->readAsArray(__DIR__ . '/../Data/Disks/Local/from-view.xlsx', 'Xlsx', 1)); $this->assertCount(101, $this->readAsArray(__DIR__ . '/../Data/Disks/Local/from-view.xlsx', 'Xlsx', 2)); } public function test_unknown_sheet_index_will_throw_sheet_not_found_exception() { $this->expectException(\Maatwebsite\Excel\Exceptions\SheetNotFoundException::class); $this->expectExceptionMessage('Your requested sheet index: 9999 is out of bounds. The actual number of sheets is 2.'); $import = new class implements WithMultipleSheets { use Importable; public function sheets(): array { return [ 9999 => new class { }, ]; } }; $import->import('import-multiple-sheets.xlsx'); } public function test_unknown_sheet_name_will_throw_sheet_not_found_exception() { $this->expectException(\Maatwebsite\Excel\Exceptions\SheetNotFoundException::class); $this->expectExceptionMessage('Your requested sheet name [Some Random Sheet Name] is out of bounds.'); $import = new class implements WithMultipleSheets { use Importable; public function sheets(): array { return [ 'Some Random Sheet Name' => new class { }, ]; } }; $import->import('import-multiple-sheets.xlsx'); } public function test_unknown_sheet_name_can_be_ignored() { $import = new class implements WithMultipleSheets, SkipsUnknownSheets { use Importable; public $unknown; public function sheets(): array { return [ 'Some Random Sheet Name' => new class { }, ]; } /** * @param string|int $sheetName */ public function onUnknownSheet($sheetName) { $this->unknown = $sheetName; } }; $import->import('import-multiple-sheets.xlsx'); $this->assertEquals('Some Random Sheet Name', $import->unknown); } public function test_unknown_sheet_indices_can_be_ignored_per_name() { $import = new class implements WithMultipleSheets { use Importable; public function sheets(): array { return [ 'Some Random Sheet Name' => new class implements SkipsUnknownSheets { /** * @param string|int $sheetName */ public function onUnknownSheet($sheetName) { Assert::assertEquals('Some Random Sheet Name', $sheetName); } }, ]; } }; $import->import('import-multiple-sheets.xlsx'); } public function test_unknown_sheet_indices_can_be_ignored() { $import = new class implements WithMultipleSheets, SkipsUnknownSheets { use Importable; public $unknown; public function sheets(): array { return [ 99999 => new class { }, ]; } /** * @param string|int $sheetName */ public function onUnknownSheet($sheetName) { $this->unknown = $sheetName; } }; $import->import('import-multiple-sheets.xlsx'); $this->assertEquals(99999, $import->unknown); } public function test_unknown_sheet_indices_can_be_ignored_per_sheet() { $import = new class implements WithMultipleSheets { use Importable; public function sheets(): array { return [ 99999 => new class implements SkipsUnknownSheets { /** * @param string|int $sheetName */ public function onUnknownSheet($sheetName) { Assert::assertEquals(99999, $sheetName); } }, ]; } }; $import->import('import-multiple-sheets.xlsx'); } public function test_can_import_multiple_sheets() { $import = new class implements WithMultipleSheets { use Importable; public function sheets(): array { return [ new class implements ToArray { public function array(array $array) { Assert::assertEquals([ ['1.A1', '1.B1'], ['1.A2', '1.B2'], ], $array); } }, new class implements ToArray { public function array(array $array) { Assert::assertEquals([ ['2.A1', '2.B1'], ['2.A2', '2.B2'], ], $array); } }, ]; } }; $import->import('import-multiple-sheets.xlsx'); } public function test_can_import_multiple_sheets_by_sheet_name() { $import = new class implements WithMultipleSheets { use Importable; public function sheets(): array { return [ 'Sheet2' => new class implements ToArray { public function array(array $array) { Assert::assertEquals([ ['2.A1', '2.B1'], ['2.A2', '2.B2'], ], $array); } }, 'Sheet1' => new class implements ToArray { public function array(array $array) { Assert::assertEquals([ ['1.A1', '1.B1'], ['1.A2', '1.B2'], ], $array); } }, ]; } }; $import->import('import-multiple-sheets.xlsx'); } public function test_can_import_multiple_sheets_by_sheet_index_and_name() { $import = new class implements WithMultipleSheets { use Importable; public $sheets = []; public function __construct() { $this->sheets = [ 0 => new class implements ToArray { public $called = false; public function array(array $array) { $this->called = true; Assert::assertEquals([ ['1.A1', '1.B1'], ['1.A2', '1.B2'], ], $array); } }, 'Sheet2' => new class implements ToArray { public $called = false; public function array(array $array) { $this->called = true; Assert::assertEquals([ ['2.A1', '2.B1'], ['2.A2', '2.B2'], ], $array); } }, ]; } public function sheets(): array { return $this->sheets; } }; $import->import('import-multiple-sheets.xlsx'); foreach ($import->sheets as $sheet) { $this->assertTrue($sheet->called); } } public function test_can_import_multiple_sheets_by_sheet_name_and_index() { $import = new class implements WithMultipleSheets { use Importable; public $sheets = []; public function __construct() { $this->sheets = [ 'Sheet1' => new class implements ToArray { public $called = false; public function array(array $array) { $this->called = true; Assert::assertEquals([ ['1.A1', '1.B1'], ['1.A2', '1.B2'], ], $array); } }, 1 => new class implements ToArray { public $called = false; public function array(array $array) { $this->called = true; Assert::assertEquals([ ['2.A1', '2.B1'], ['2.A2', '2.B2'], ], $array); } }, ]; } public function sheets(): array { return $this->sheets; } }; $import->import('import-multiple-sheets.xlsx'); foreach ($import->sheets as $sheet) { $this->assertTrue($sheet->called); } } } ================================================ FILE: tests/Concerns/WithPropertiesTest.php ================================================ 'A', 'lastModifiedBy' => 'B', 'title' => 'C', 'description' => 'D', 'subject' => 'E', 'keywords' => 'F', 'category' => 'G', 'manager' => 'H', 'company' => 'I', ]; } }; $export->store('with-properties.xlsx'); $spreadsheet = $this->read(__DIR__ . '/../Data/Disks/Local/with-properties.xlsx', 'Xlsx'); $props = $spreadsheet->getProperties(); $this->assertEquals('A', $props->getCreator()); $this->assertEquals('B', $props->getLastModifiedBy()); $this->assertEquals('C', $props->getTitle()); $this->assertEquals('D', $props->getDescription()); $this->assertEquals('E', $props->getSubject()); $this->assertEquals('F', $props->getKeywords()); $this->assertEquals('G', $props->getCategory()); $this->assertEquals('H', $props->getManager()); $this->assertEquals('I', $props->getCompany()); } public function test_it_merges_with_default_properties() { config()->set('excel.exports.properties.title', 'Default Title'); config()->set('excel.exports.properties.description', 'Default Description'); $export = new class implements WithProperties { use Exportable; public function properties(): array { return [ 'description' => 'Custom Description', ]; } }; $export->store('with-properties.xlsx'); $spreadsheet = $this->read(__DIR__ . '/../Data/Disks/Local/with-properties.xlsx', 'Xlsx'); $props = $spreadsheet->getProperties(); $this->assertEquals('Default Title', $props->getTitle()); $this->assertEquals('Custom Description', $props->getDescription()); } public function test_it_ignores_empty_properties() { $export = new class implements WithProperties { use Exportable; public function properties(): array { return [ 'description' => '', ]; } }; $export->store('with-properties.xlsx'); $spreadsheet = $this->read(__DIR__ . '/../Data/Disks/Local/with-properties.xlsx', 'Xlsx'); $props = $spreadsheet->getProperties(); $this->assertSame('Unknown Creator', $props->getCreator()); $this->assertSame('Untitled Spreadsheet', $props->getTitle()); $this->assertSame('', $props->getDescription()); } } ================================================ FILE: tests/Concerns/WithReadFilterTest.php ================================================ toArray('import-users.xlsx'); } } ================================================ FILE: tests/Concerns/WithSkipDuplicatesTest.php ================================================ loadLaravelMigrations(['--database' => 'testing']); } public function test_can_skip_duplicate_models_in_batches() { User::create([ 'name' => 'Funny Banana', 'email' => 'patrick@maatwebsite.nl', 'password' => 'password', ]); DB::connection()->enableQueryLog(); $import = new class implements ToModel, WithBatchInserts, WithSkipDuplicates { use Importable; /** * @param array $row * @return Model|null */ public function model(array $row) { return new User([ 'name' => $row[0], 'email' => $row[1], 'password' => 'secret', ]); } /** * @return string|array */ public function uniqueBy() { return 'email'; } /** * @return int */ public function batchSize(): int { return 2; } }; $import->import('import-users.xlsx'); $this->assertCount(1, DB::getQueryLog()); DB::connection()->disableQueryLog(); $this->assertDatabaseHas('users', [ 'name' => 'Funny Banana', 'email' => 'patrick@maatwebsite.nl', 'password' => 'password', ]); $this->assertDatabaseHas('users', [ 'name' => 'Taylor Otwell', 'email' => 'taylor@laravel.com', 'password' => 'secret', ]); $this->assertEquals(2, User::count()); } public function test_can_skip_duplicate_models_in_rows() { User::create([ 'name' => 'Funny Potato', 'email' => 'patrick@maatwebsite.nl', 'password' => 'password', ]); DB::connection()->enableQueryLog(); $import = new class implements ToModel, WithSkipDuplicates { use Importable; /** * @param array $row * @return Model|Model[]|null */ public function model(array $row) { return new User([ 'name' => $row[0], 'email' => $row[1], 'password' => 'secret', ]); } /** * @return string|array */ public function uniqueBy() { return 'email'; } }; $import->import('import-users.xlsx'); $this->assertCount(2, DB::getQueryLog()); DB::connection()->disableQueryLog(); $this->assertDatabaseHas('users', [ 'name' => 'Funny Potato', 'email' => 'patrick@maatwebsite.nl', 'password' => 'password', ]); $this->assertDatabaseHas('users', [ 'name' => 'Taylor Otwell', 'email' => 'taylor@laravel.com', 'password' => 'secret', ]); $this->assertEquals(2, User::count()); } } ================================================ FILE: tests/Concerns/WithStartRowTest.php ================================================ loadLaravelMigrations(['--database' => 'testing']); } public function test_can_import_each_row_to_model_with_different_start_row() { $import = new class implements ToModel, WithStartRow { use Importable; /** * @param array $row * @return Model */ public function model(array $row): Model { return new User([ 'name' => $row[0], 'email' => $row[1], 'password' => 'secret', ]); } /** * @return int */ public function startRow(): int { return 5; } }; $import->import('import-users-with-different-heading-row.xlsx'); $this->assertDatabaseHas('users', [ 'name' => 'Patrick Brouwers', 'email' => 'patrick@maatwebsite.nl', ]); $this->assertDatabaseHas('users', [ 'name' => 'Taylor Otwell', 'email' => 'taylor@laravel.com', ]); } public function test_can_import_to_array_with_start_row() { $import = new class implements ToArray, WithStartRow { use Importable; /** * @param array $array */ public function array(array $array) { Assert::assertEquals([ [ 'Patrick Brouwers', 'patrick@maatwebsite.nl', ], [ 'Taylor Otwell', 'taylor@laravel.com', ], ], $array); } /** * @return int */ public function startRow(): int { return 5; } }; $import->import('import-users-with-different-heading-row.xlsx'); } } ================================================ FILE: tests/Concerns/WithStrictNullComparisonTest.php ================================================ store('with-strict-null-comparison-store.xlsx'); $this->assertTrue($response); $actual = $this->readAsArray(__DIR__ . '/../Data/Disks/Local/with-strict-null-comparison-store.xlsx', 'Xlsx'); $expected = [ ['string', 0.0, 0.0, 0.0, 'string'], ['string', 0.0, 0.0, 0.0, 'string'], ]; $this->assertEquals($expected, $actual); } public function test_exported_zero_values_are_null_when_not_exporting_with_strict_null_comparison() { $export = new class implements FromCollection, WithHeadings { use Exportable; /** * @return Collection */ public function collection() { return collect([ ['string', 0, 0.0, 'string'], ]); } /** * @return array */ public function headings(): array { return ['string', 0, 0.0, 'string']; } }; $response = $export->store('without-strict-null-comparison-store.xlsx'); $this->assertTrue($response); $actual = $this->readAsArray(__DIR__ . '/../Data/Disks/Local/without-strict-null-comparison-store.xlsx', 'Xlsx'); $expected = [ ['string', null, null, 'string'], ['string', null, null, 'string'], ]; $this->assertEquals($expected, $actual); } public function test_exports_trailing_empty_cells() { $export = new class implements FromCollection, WithStrictNullComparison { use Exportable; /** * @return Collection */ public function collection() { return collect([ ['a1', '', '', 'd1', ''], ['a2', '', '', 'd2', ''], ]); } }; $response = $export->store('empty-cells.csv'); $this->assertTrue($response); $file = __DIR__ . '/../Data/Disks/Local/empty-cells.csv'; $actual = $this->readAsArray($file, 'Csv'); $expected = [ ['a1', null, null, 'd1'], ['a2', null, null, 'd2'], ]; $this->assertEquals($expected, $actual); $contents = file_get_contents($file); $this->assertStringContains('"a1","","","d1",""', $contents); $this->assertStringContains('"a2","","","d2",""', $contents); } public function test_exports_trailing_empty_cells_by_setting_config_strict_null_comparison() { config()->set('excel.exports.strict_null_comparison', false); $export = new class implements FromCollection { use Exportable; /** * @return Collection */ public function collection() { return collect([ ['a1', '', '', 'd1', ''], ['a2', '', '', 'd2', ''], ]); } }; $file = __DIR__ . '/../Data/Disks/Local/empty-cells-config.csv'; $export->store('empty-cells-config.csv'); $contents = file_get_contents($file); $this->assertStringContains('"a1","","","d1"', $contents); config()->set('excel.exports.strict_null_comparison', true); $export->store('empty-cells-config.csv'); $contents = file_get_contents($file); $this->assertStringContains('"a1","","","d1",""', $contents); } } ================================================ FILE: tests/Concerns/WithStylesTest.php ================================================ ['font' => ['italic' => true]], 'B2' => ['font' => ['bold' => true]], 'C' => ['font' => ['size' => 16]], ]; } public function array(): array { return [ ['A1', 'B1', 'C1'], ['A2', 'B2', 'C2'], ]; } }; $export->store('with-styles.xlsx'); $spreadsheet = $this->read(__DIR__ . '/../Data/Disks/Local/with-styles.xlsx', 'Xlsx'); $sheet = $spreadsheet->getActiveSheet(); $this->assertTrue($sheet->getStyle('A1')->getFont()->getItalic()); $this->assertTrue($sheet->getStyle('B1')->getFont()->getItalic()); $this->assertTrue($sheet->getStyle('B2')->getFont()->getBold()); $this->assertFalse($sheet->getStyle('A2')->getFont()->getBold()); $this->assertEquals(16, $sheet->getStyle('C2')->getFont()->getSize()); } } ================================================ FILE: tests/Concerns/WithTitleTest.php ================================================ store('with-title-store.xlsx'); $this->assertTrue($response); $spreadsheet = $this->read(__DIR__ . '/../Data/Disks/Local/with-title-store.xlsx', 'Xlsx'); $this->assertEquals('given-title', $spreadsheet->getProperties()->getTitle()); $this->assertEquals('given-title', $spreadsheet->getActiveSheet()->getTitle()); } public function test_can_export_sheet_title_when_longer_than_max_length() { $export = new class implements WithTitle, WithMultipleSheets { use Exportable; /** * @return string */ public function title(): string { return '12/3456789123/45678912345/678912345/6789'; } public function sheets(): array { return [$this]; } }; $response = $export->store('with-title-store.xlsx'); $this->assertTrue($response); $spreadsheet = $this->read(__DIR__ . '/../Data/Disks/Local/with-title-store.xlsx', 'Xlsx'); $this->assertEquals('12/3456789123/45678912345/678912345/6789', $spreadsheet->getProperties()->getTitle()); $this->assertEquals('1234567891234567891234567891234', $spreadsheet->getActiveSheet()->getTitle()); } } ================================================ FILE: tests/Concerns/WithUpsertsTest.php ================================================ markTestSkipped('The upsert feature is available on Laravel 8.10+'); } parent::setUp(); $this->loadLaravelMigrations(['--database' => 'testing']); } public function test_can_upsert_models_in_batches() { User::create([ 'name' => 'Funny Banana', 'email' => 'patrick@maatwebsite.nl', 'password' => 'password', ]); DB::connection()->enableQueryLog(); $import = new class implements ToModel, WithBatchInserts, WithUpserts { use Importable; /** * @param array $row * @return Model|null */ public function model(array $row) { return new User([ 'name' => $row[0], 'email' => $row[1], 'password' => 'secret', ]); } /** * @return string|array */ public function uniqueBy() { return 'email'; } /** * @return int */ public function batchSize(): int { return 2; } }; $import->import('import-users.xlsx'); $this->assertCount(1, DB::getQueryLog()); DB::connection()->disableQueryLog(); $this->assertDatabaseHas('users', [ 'name' => 'Patrick Brouwers', 'email' => 'patrick@maatwebsite.nl', 'password' => 'secret', ]); $this->assertDatabaseHas('users', [ 'name' => 'Taylor Otwell', 'email' => 'taylor@laravel.com', 'password' => 'secret', ]); $this->assertEquals(2, User::count()); } public function test_can_upsert_models_in_rows() { User::create([ 'name' => 'Funny Potato', 'email' => 'patrick@maatwebsite.nl', 'password' => 'password', ]); DB::connection()->enableQueryLog(); $import = new class implements ToModel, WithUpserts { use Importable; /** * @param array $row * @return Model|Model[]|null */ public function model(array $row) { return new User([ 'name' => $row[0], 'email' => $row[1], 'password' => 'secret', ]); } /** * @return string|array */ public function uniqueBy() { return 'email'; } }; $import->import('import-users.xlsx'); $this->assertCount(2, DB::getQueryLog()); DB::connection()->disableQueryLog(); $this->assertDatabaseHas('users', [ 'name' => 'Patrick Brouwers', 'email' => 'patrick@maatwebsite.nl', 'password' => 'secret', ]); $this->assertDatabaseHas('users', [ 'name' => 'Taylor Otwell', 'email' => 'taylor@laravel.com', 'password' => 'secret', ]); $this->assertEquals(2, User::count()); } public function test_can_upsert_models_in_batches_with_defined_upsert_columns() { User::create([ 'name' => 'Funny Banana', 'email' => 'patrick@maatwebsite.nl', 'password' => 'password', ]); DB::connection()->enableQueryLog(); $import = new class implements ToModel, WithBatchInserts, WithUpserts, WithUpsertColumns { use Importable; /** * @param array $row * @return Model|null */ public function model(array $row) { return new User([ 'name' => $row[0], 'email' => $row[1], 'password' => 'secret', ]); } /** * @return string|array */ public function uniqueBy() { return 'email'; } /** * @return array */ public function upsertColumns() { return ['name']; } /** * @return int */ public function batchSize(): int { return 2; } }; $import->import('import-users.xlsx'); $this->assertCount(1, DB::getQueryLog()); DB::connection()->disableQueryLog(); $this->assertDatabaseHas('users', [ 'name' => 'Patrick Brouwers', 'email' => 'patrick@maatwebsite.nl', 'password' => 'password', ]); $this->assertDatabaseHas('users', [ 'name' => 'Taylor Otwell', 'email' => 'taylor@laravel.com', 'password' => 'secret', ]); $this->assertEquals(2, User::count()); } public function test_can_upsert_models_in_rows_with_defined_upsert_columns() { User::create([ 'name' => 'Funny Potato', 'email' => 'patrick@maatwebsite.nl', 'password' => 'password', ]); DB::connection()->enableQueryLog(); $import = new class implements ToModel, WithUpserts, WithUpsertColumns { use Importable; /** * @param array $row * @return Model|Model[]|null */ public function model(array $row) { return new User([ 'name' => $row[0], 'email' => $row[1], 'password' => 'secret', ]); } /** * @return string|array */ public function uniqueBy() { return 'email'; } /** * @return array */ public function upsertColumns() { return ['name']; } }; $import->import('import-users.xlsx'); $this->assertCount(2, DB::getQueryLog()); DB::connection()->disableQueryLog(); $this->assertDatabaseHas('users', [ 'name' => 'Patrick Brouwers', 'email' => 'patrick@maatwebsite.nl', 'password' => 'password', ]); $this->assertDatabaseHas('users', [ 'name' => 'Taylor Otwell', 'email' => 'taylor@laravel.com', 'password' => 'secret', ]); $this->assertEquals(2, User::count()); } } ================================================ FILE: tests/Concerns/WithValidationTest.php ================================================ loadLaravelMigrations(['--database' => 'testing']); $this->loadMigrationsFrom(dirname(__DIR__) . '/Data/Stubs/Database/Migrations'); } public function test_can_validate_rows() { $import = new class implements ToModel, WithValidation { use Importable; /** * @param array $row * @return Model|null */ public function model(array $row) { return new User([ 'name' => $row[0], 'email' => $row[1], 'password' => 'secret', ]); } /** * @return array */ public function rules(): array { return [ '1' => Rule::in(['patrick@maatwebsite.nl']), ]; } }; try { $import->import('import-users.xlsx'); } catch (ValidationException $e) { $this->validateFailure($e, 2, '1', [ 'The selected 1(field)? is invalid.', ]); $this->assertRegex( '/There was an error on row 2. The selected 1 (field)?is invalid./', $e->errors()[0][0] ); } $this->assertInstanceOf(ValidationException::class, $e ?? null); } public function test_can_validate_rows_with_closure_validation_rules() { $import = new class implements ToModel, WithValidation { use Importable; /** * @param array $row * @return Model|null */ public function model(array $row) { return new User([ 'name' => $row[0], 'email' => $row[1], 'password' => 'secret', ]); } /** * @return array */ public function rules(): array { return [ '1' => function ($attribute, $value, $onFail) { if ($value !== 'patrick@maatwebsite.nl') { $onFail(sprintf('Value in column 1 is not an allowed e-mail.')); } }, ]; } }; try { $import->import('import-users.xlsx'); } catch (ValidationException $e) { $this->validateFailure($e, 2, '1', [ 'Value in column 1 is not an allowed e-mail.', ]); $this->assertRegex( '/There was an error on row 2. Value in column 1 is not an allowed e-mail./', $e->errors()[0][0] ); } $this->assertInstanceOf(ValidationException::class, $e ?? null); } public function test_can_validate_rows_with_custom_validation_rule_objects() { $import = new class implements ToModel, WithValidation { use Importable; /** * @param array $row * @return Model|null */ public function model(array $row) { return new User([ 'name' => $row[0], 'email' => $row[1], 'password' => 'secret', ]); } /** * @return array */ public function rules(): array { return [ '1' => new class implements \Illuminate\Contracts\Validation\Rule { /** * @param string $attribute * @param mixed $value * @return bool */ public function passes($attribute, $value) { return $value === 'patrick@maatwebsite.nl'; } /** * Get the validation error message. * * @return string|array */ public function message() { return 'Value is not an allowed e-mail.'; } }, ]; } }; try { $import->import('import-users.xlsx'); } catch (ValidationException $e) { $this->validateFailure($e, 2, '1', [ 'Value is not an allowed e-mail.', ]); $this->assertRegex( '/There was an error on row 2. Value is not an allowed e-mail./', $e->errors()[0][0] ); } $this->assertInstanceOf(ValidationException::class, $e ?? null); } public function test_can_validate_rows_with_conditionality() { $import = new class implements ToModel, WithValidation { use Importable; /** * @param array $row * @return Model|null */ public function model(array $row) { return new User([ 'name' => $row[0], 'email' => $row[1], 'password' => 'secret', ]); } /** * @return array */ public function rules(): array { return [ 'conditional_required_column' => 'required_if:1,patrick@maatwebsite.nl', ]; } }; try { $import->import('import-users.xlsx'); } catch (ValidationException $e) { $this->validateFailure($e, 1, 'conditional_required_column', [ 'The conditional_required_column field is required when 1.1 is patrick@maatwebsite.nl.', ]); } $this->assertInstanceOf(ValidationException::class, $e ?? null); } public function test_can_validate_rows_with_unless_conditionality() { $import = new class implements ToModel, WithValidation { use Importable; /** * @param array $row * @return Model|null */ public function model(array $row) { return new User([ 'name' => $row[0], 'email' => $row[1], 'password' => 'secret', ]); } /** * @return array */ public function rules(): array { return [ 'conditional_required_unless_column' => 'required_unless:1,patrick@maatwebsite.nl', ]; } }; try { $import->import('import-users.xlsx'); } catch (ValidationException $e) { $this->validateFailure($e, 2, 'conditional_required_unless_column', [ 'The conditional_required_unless_column field is required unless 2.1 is in patrick@maatwebsite.nl.', ]); } $this->assertInstanceOf(ValidationException::class, $e ?? null); } public function test_can_validate_rows_with_combined_rules_with_colons() { $import = new class implements ToModel, WithValidation { use Importable; /** * @param array $row * @return Model|null */ public function model(array $row) { return new User([ 'name' => $row[0], 'email' => $row[1], 'password' => 'secret', ]); } /** * @return array */ public function rules(): array { return [ '1' => 'required_with:0|unique:users,email', ]; } }; $import->import('import-users.xlsx'); $this->assertDatabaseHas('users', [ 'email' => 'patrick@maatwebsite.nl', ]); try { $import->import('import-users.xlsx'); } catch (ValidationException $e) { $this->validateFailure($e, 1, '1', [ 'The 1 has already been taken.', ]); } $this->assertInstanceOf(ValidationException::class, $e ?? null); } public function test_can_validate_with_custom_attributes() { $import = new class implements ToModel, WithValidation { use Importable; /** * @param array $row * @return Model|null */ public function model(array $row) { return new User([ 'name' => $row[0], 'email' => $row[1], 'password' => 'secret', ]); } /** * @return array */ public function rules(): array { return [ '1' => Rule::in(['patrick@maatwebsite.nl']), ]; } /** * @return array */ public function customValidationAttributes() { return ['1' => 'email']; } }; try { $import->import('import-users.xlsx'); } catch (ValidationException $e) { $this->validateFailure($e, 2, 'email', [ 'The selected email is invalid.', ]); } $this->assertInstanceOf(ValidationException::class, $e ?? null); } public function test_can_validate_with_custom_attributes_pointing_to_another_attribute() { $import = new class implements ToModel, WithValidation { use Importable; /** * @param array $row * @return Model|null */ public function model(array $row) { return new User([ 'name' => $row[0], 'email' => $row[1], 'password' => 'secret', ]); } /** * @return array */ public function rules(): array { return [ '1' => ['required'], '2' => ['required_with:*.1'], ]; } /** * @return array */ public function customValidationAttributes() { return ['1' => 'email', '2' => 'password']; } }; try { $import->import('import-users.xlsx'); } catch (ValidationException $e) { $this->validateFailure($e, 1, 'password', [ 'The password field is required when email is present.', ]); } $this->assertInstanceOf(ValidationException::class, $e ?? null); } public function test_can_validate_with_custom_message() { $import = new class implements ToModel, WithValidation { use Importable; /** * @param array $row * @return Model|null */ public function model(array $row) { return new User([ 'name' => $row[0], 'email' => $row[1], 'password' => 'secret', ]); } /** * @return array */ public function rules(): array { return [ '1' => Rule::in(['patrick@maatwebsite.nl']), ]; } /** * @return array */ public function customValidationMessages() { return [ '1.in' => 'Custom message for :attribute.', ]; } }; try { $import->import('import-users.xlsx'); } catch (ValidationException $e) { $this->validateFailure($e, 2, '1', [ 'Custom message for 1.', ]); } $this->assertInstanceOf(ValidationException::class, $e ?? null); } public function test_can_validate_rows_with_headings() { $import = new class implements ToModel, WithHeadingRow, WithValidation { use Importable; /** * @param array $row * @return Model|null */ public function model(array $row) { return new User([ 'name' => $row['name'], 'email' => $row['email'], 'password' => 'secret', ]); } /** * @return array */ public function rules(): array { return [ 'email' => Rule::in(['patrick@maatwebsite.nl']), ]; } }; try { $import->import('import-users-with-headings.xlsx'); } catch (ValidationException $e) { $this->validateFailure($e, 3, 'email', [ 'The selected email is invalid.', ]); } $this->assertInstanceOf(ValidationException::class, $e ?? null); } public function test_can_validate_rows_with_grouped_headings() { $import = new class implements ToModel, WithGroupedHeadingRow, WithValidation { use Importable; /** * Prepare the data for validation. * * @param array $row * @param int $index * @return array */ public function prepareForValidation(array $row, int $index) { if ($index === 2) { Assert::assertIsArray($row['options']); $row['options'] = 'not an array'; } return $row; } /** * @param array $row * @return Model|null */ public function model(array $row) { return new User([ 'name' => $row['name'], 'email' => $row['email'], 'password' => 'secret', 'options' => $row['options'], ]); } /** * @return array */ public function rules(): array { return [ 'options' => 'array', ]; } }; try { $import->import('import-users-with-grouped-headers.xlsx'); } catch (ValidationException $e) { $this->validateFailure($e, 2, 'options', [ 'The options( field)? must be an array.', ]); } $this->assertInstanceOf(ValidationException::class, $e ?? null); } public function test_can_validate_rows_in_batches() { $import = new class implements ToModel, WithHeadingRow, WithBatchInserts, WithValidation { use Importable; /** * @param array $row * @return Model|null */ public function model(array $row) { return new User([ 'name' => $row['name'], 'email' => $row['email'], 'password' => 'secret', ]); } /** * @return int */ public function batchSize(): int { return 2; } /** * @return array */ public function rules(): array { return [ 'email' => Rule::in(['patrick@maatwebsite.nl']), ]; } }; try { $import->import('import-users-with-headings.xlsx'); } catch (ValidationException $e) { $this->validateFailure($e, 3, 'email', [ 'The selected email is invalid.', ]); } $this->assertInstanceOf(ValidationException::class, $e ?? null); } public function test_can_validate_using_oneachrow() { $import = new class implements OnEachRow, WithHeadingRow, WithValidation { use Importable; /** * @param Row $row * @return Model|null */ public function onRow(Row $row) { $values = $row->toArray(); return new User([ 'name' => $values['name'], 'email' => $values['email'], 'password' => 'secret', ]); } /** * @return array */ public function rules(): array { return [ 'email' => Rule::in(['patrick@maatwebsite.nl']), ]; } }; try { $import->import('import-users-with-headings.xlsx'); } catch (ValidationException $e) { $this->validateFailure($e, 3, 'email', [ 'The selected email is invalid.', ]); } $this->assertInstanceOf(ValidationException::class, $e ?? null); } public function test_can_validate_using_collection() { $import = new class implements ToCollection, WithHeadingRow, WithValidation { use Importable; public function collection(Collection $rows) { // } /** * @return array */ public function rules(): array { return [ 'email' => Rule::in(['patrick@maatwebsite.nl']), ]; } }; try { $import->import('import-users-with-headings.xlsx'); } catch (ValidationException $e) { $this->validateFailure($e, 3, 'email', [ 'The selected email is invalid.', ]); } $this->assertInstanceOf(ValidationException::class, $e ?? null); } public function test_can_validate_using_array() { $import = new class implements ToArray, WithHeadingRow, WithValidation { use Importable; public function array(array $rows) { // } /** * @return array */ public function rules(): array { return [ 'email' => Rule::in(['patrick@maatwebsite.nl']), ]; } }; try { $import->import('import-users-with-headings.xlsx'); } catch (ValidationException $e) { $this->validateFailure($e, 3, 'email', [ 'The selected email is invalid.', ]); } $this->assertInstanceOf(ValidationException::class, $e ?? null); } public function test_can_configure_validator() { $import = new class implements ToModel, WithValidation { use Importable; /** * @param array $row * @return Model|null */ public function model(array $row) { return new User([ 'name' => $row[0], 'email' => $row[1], 'password' => 'secret', ]); } /** * @return array */ public function rules(): array { return [ '1' => 'email', ]; } /** * Configure the validator. * * @param \Illuminate\Contracts\Validation\Validator $validator * @return void */ public function withValidator($validator) { $validator->sometimes('*.1', Rule::in(['patrick@maatwebsite.nl']), function () { return true; }); } }; try { $import->import('import-users.xlsx'); } catch (ValidationException $e) { $this->validateFailure($e, 2, '1', [ 'The selected 1 is invalid.', ]); $this->assertRegex( '/There was an error on row 2. The selected 1 (field)?is invalid./', $e->errors()[0][0] ); } $this->assertInstanceOf(ValidationException::class, $e ?? null); } public function test_can_prepare_using_toarray() { $import = new class implements ToArray, WithValidation { use Importable; /** * @return array */ public function rules(): array { return [ '1' => 'email', ]; } /** * Prepare the data for validation. * * @param array $row * @param int $index * @return array */ public function prepareForValidation(array $row, int $index) { if ($index === 2) { $row[1] = 'not an email'; } return $row; } /** * @param array $array * @return array */ public function array(array $array) { return []; } }; try { $import->import('import-users.xlsx'); } catch (ValidationException $e) { $this->validateFailure($e, 2, '1', [ 'The 1( field)? must be a valid email address.', ]); $this->assertRegex( '/There was an error on row 2. The 1( field)? must be a valid email address./', $e->errors()[0][0] ); } $this->assertInstanceOf(ValidationException::class, $e ?? null); } public function test_can_prepare_using_tocollection() { $import = new class implements ToCollection, WithValidation { use Importable; /** * @return array */ public function rules(): array { return [ '1' => 'email', ]; } /** * Prepare the data for validation. * * @param array $row * @param int $index * @return array */ public function prepareForValidation(array $row, int $index) { if ($index === 2) { $row[1] = 'not an email'; } return $row; } /** * @param \Illuminate\Support\Collection $collection * @return mixed */ public function collection(Collection $collection) { return collect(); } }; try { $import->import('import-users.xlsx'); } catch (ValidationException $e) { $this->validateFailure($e, 2, '1', [ 'The 1( field)? must be a valid email address.', ]); $this->assertRegex( '/There was an error on row 2. The 1( field)? must be a valid email address./', $e->errors()[0][0] ); } $this->assertInstanceOf(ValidationException::class, $e ?? null); } public function test_can_prepare_using_tomodel() { $import = new class implements ToModel, WithValidation { use Importable; /** * @return array */ public function rules(): array { return [ '1' => 'email', ]; } /** * Prepare the data for validation. * * @param array $row * @param int $index * @return array */ public function prepareForValidation(array $row, int $index) { if ($index === 2) { $row[1] = 'not an email'; } return $row; } /** * @param array $row * @return \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Model[]|null */ public function model(array $row) { return new User([ 'name' => $row[0], 'email' => $row[1], 'password' => 'secret', ]); } }; try { $import->import('import-users.xlsx'); } catch (ValidationException $e) { $this->validateFailure($e, 2, '1', [ 'The 1( field)? must be a valid email address.', ]); $this->assertRegex( '/There was an error on row 2. The 1( field)? must be a valid email address./', $e->errors()[0][0] ); } $this->assertInstanceOf(ValidationException::class, $e ?? null); } public function test_can_prepare_using_oneachrow() { $import = new class implements OnEachRow, WithValidation { use Importable; /** * @return array */ public function rules(): array { return [ '1' => 'email', ]; } /** * Prepare the data for validation. * * @param array $row * @param int $index * @return array */ public function prepareForValidation(array $row, int $index) { if ($index === 2) { $row[1] = 'not an email'; } return $row; } /** * @param \Maatwebsite\Excel\Row $row * @return void */ public function onRow(Row $row) { User::query()->create([ 'name' => $row[0], 'email' => $row[1], 'password' => 'secret', ]); } }; try { $import->import('import-users.xlsx'); } catch (ValidationException $e) { $this->validateFailure($e, 2, '1', [ 'The 1( field)? must be a valid email address.', ]); $this->assertRegex( '/There was an error on row 2. The 1( field)? must be a valid email address./', $e->errors()[0][0] ); } $this->assertInstanceOf(ValidationException::class, $e ?? null); } public function test_can_prepare_using_skipsemptyrows() { $import = new class implements OnEachRow, WithValidation, SkipsEmptyRows { use Importable; /** * @return array */ public function rules(): array { return [ '1' => 'email', ]; } /** * Prepare the data for validation. * * @param array $row * @param int $index * @return array */ public function prepareForValidation(array $row, int $index) { if ($index === 2) { $row[1] = 'not an email'; } return $row; } /** * @param \Maatwebsite\Excel\Row $row * @return void */ public function onRow(Row $row) { User::query()->create([ 'name' => $row[0], 'email' => $row[1], 'password' => 'secret', ]); } }; try { $import->import('import-users.xlsx'); } catch (ValidationException $e) { $this->validateFailure($e, 2, '1', [ 'The 1( field)? must be a valid email address.', ]); $this->assertRegex( '/There was an error on row 2. The 1( field)? must be a valid email address./', $e->errors()[0][0] ); } $this->assertInstanceOf(ValidationException::class, $e ?? null); } /** * @param ValidationException $e * @param int $row * @param string $attribute * @param array $messages */ private function validateFailure(ValidationException $e, int $row, string $attribute, array $messages) { $failures = $e->failures(); $failure = head($failures); $this->assertEquals($row, $failure->row()); $this->assertEquals($attribute, $failure->attribute()); $this->assertEquals($row, $failure->jsonSerialize()['row']); $this->assertEquals($attribute, $failure->jsonSerialize()['attribute']); $this->assertRegex('/' . $messages[0] . '/', $failure->errors()[0]); $this->assertRegex('/' . $messages[0] . '/', $failure->jsonSerialize()['errors'][0]); } } ================================================ FILE: tests/Data/Disks/.gitignore ================================================ * !.gitignore !Local/ !Test/ ================================================ FILE: tests/Data/Stubs/AfterQueueExportJob.php ================================================ filePath = $filePath; } public function handle() { TestCase::assertFileExists($this->filePath); } } ================================================ FILE: tests/Data/Stubs/AfterQueueImportJob.php ================================================ totalRows = $totalRows; } public function handle() { Assert::assertEquals($this->totalRows, DB::table('groups')->count('id')); } } ================================================ FILE: tests/Data/Stubs/BeforeExportListener.php ================================================ assertions = $assertions; } public function __invoke() { ($this->assertions)(...func_get_args()); } } ================================================ FILE: tests/Data/Stubs/ChainedJobStub.php ================================================ define(Group::class, function (Faker $faker) { return [ 'name' => $faker->word, ]; }); ================================================ FILE: tests/Data/Stubs/Database/Factories/UserFactory.php ================================================ define(User::class, function (Faker $faker) { return [ 'name' => $faker->name, 'email' => $faker->unique()->safeEmail, 'password' => '$2y$10$TKh8H1.PfQx37YgCzwiKb.KjNyWgaHb9cbcoQgdIVFlYg7B77UdFm', // secret 'remember_token' => Str::random(10), ]; }); ================================================ FILE: tests/Data/Stubs/Database/Group.php ================================================ belongsToMany(User::class); } } ================================================ FILE: tests/Data/Stubs/Database/Migrations/0000_00_00_000000_create_groups_table.php ================================================ increments('id'); $table->string('name'); $table->timestamps(); }); } /** * Reverse the migrations. */ public function down() { Schema::dropIfExists('groups'); } } ================================================ FILE: tests/Data/Stubs/Database/Migrations/0000_00_00_000001_create_group_user_table.php ================================================ increments('id'); $table->unsignedInteger('group_id')->index(); $table->unsignedInteger('user_id')->index(); }); } /** * Reverse the migrations. */ public function down() { Schema::dropIfExists('group_user'); } } ================================================ FILE: tests/Data/Stubs/Database/Migrations/0000_00_00_000002_add_group_id_to_users_table.php ================================================ unsignedInteger('group_id')->index()->nullable(); }); } /** * Reverse the migrations. */ public function down() { Schema::table('users', function (Blueprint $table) { $table->dropColumn('group_id'); }); } } ================================================ FILE: tests/Data/Stubs/Database/Migrations/0000_00_00_000002_add_options_to_users.php ================================================ text('options')->nullable(); }); } /** * Reverse the migrations. */ public function down() { Schema::table('users', function (Blueprint $table) { $table->dropColumn('options'); }); } } ================================================ FILE: tests/Data/Stubs/Database/User.php ================================================ 'array', ]; /** * @var array */ protected $hidden = ['password', 'email_verified_at', 'options', 'group_id']; public function groups(): BelongsToMany { return $this->belongsToMany(Group::class); } public function group(): BelongsTo { return $this->belongsTo(Group::class); } /** * Laravel Scout under <=8 provides only * — NullEngine, that is searches nothing and not applicable for tests and * — AlgoliaEngine, that is 3-d party dependent and not applicable for tests too. * * The only test-ready engine is DatabaseEngine that comes with Scout >8 * * Then running tests we will examine engine and skip test until DatabaseEngine is provided. * * @see QueuedQueryExportTest::can_queue_scout_export() * @see FromQueryTest::can_export_from_scout() */ public function searchableUsing(): Engine { return class_exists('\Laravel\Scout\Engines\DatabaseEngine') ? new DatabaseEngine() : new NullEngine(); } } ================================================ FILE: tests/Data/Stubs/EloquentCollectionWithMappingExport.php ================================================ 'Patrick', 'lastname' => 'Brouwers', ]), ]); } /** * @param User $user * @return array */ public function map($user): array { return [ $user->firstname, $user->lastname, ]; } } ================================================ FILE: tests/Data/Stubs/EloquentLazyCollectionExport.php ================================================ 'Patrick', 'lastname' => 'Brouwers', ], [ 'firstname' => 'Patrick', 'lastname' => 'Brouwers', ], [ 'firstname' => 'Patrick', 'lastname' => 'Brouwers', ], [ 'firstname' => 'Patrick', 'lastname' => 'Brouwers', ], ])->lazy(); } } ================================================ FILE: tests/Data/Stubs/EloquentLazyCollectionQueuedExport.php ================================================ 'Patrick', 'lastname' => 'Brouwers', ], [ 'firstname' => 'Patrick', 'lastname' => 'Brouwers', ], [ 'firstname' => 'Patrick', 'lastname' => 'Brouwers', ], [ 'firstname' => 'Patrick', 'lastname' => 'Brouwers', ], ])->lazy(); } } ================================================ FILE: tests/Data/Stubs/EmptyExport.php ================================================ $this->beforeExport ?? function () { }, BeforeWriting::class => $this->beforeWriting ?? function () { }, BeforeSheet::class => $this->beforeSheet ?? function () { }, AfterSheet::class => $this->afterSheet ?? function () { }, ]; } } ================================================ FILE: tests/Data/Stubs/ExportWithEventsChunks.php ================================================ function (AfterChunk $event) { ExportWithEventsChunks::$calledEvent++; Assert::assertInstanceOf(ExportWithEventsChunks::class, $event->getConcernable()); }, ]; } public function query(): Builder { return User::query(); } public function chunkSize(): int { return 1; } } ================================================ FILE: tests/Data/Stubs/ExportWithRegistersEventListeners.php ================================================ users(); } /** * @param mixed $row * @return array */ public function map($row): array { return [ $row->name, $row->email, ]; } /** * @return int */ public function chunkSize(): int { return 10; } } ================================================ FILE: tests/Data/Stubs/FromNestedArraysQueryExport.php ================================================ name, '']; $count = 0; foreach ($row->users as $user) { if ($count === 0) { $sub_row[1] = $user['email']; } else { $sub_row = ['', $user['email']]; } $rows[] = $sub_row; $count++; } if ($count === 0) { $rows[] = $sub_row; } return $rows; } } ================================================ FILE: tests/Data/Stubs/FromNonEloquentQueryExport.php ================================================ select('name')->orderBy('id'); } /** * @return int */ public function chunkSize(): int { return 10; } } ================================================ FILE: tests/Data/Stubs/FromQueryWithCustomQuerySize.php ================================================ join('group_user', 'groups.id', '=', 'group_user.group_id') ->select('groups.*', DB::raw('count(group_user.user_id) as number_of_users')) ->groupBy('groups.id') ->orderBy('number_of_users'); return $query; } /** * @return int */ public function querySize(): int { return Group::has('users')->count(); } /** * @param Group $row * @return array */ public function map($row): array { return [ $row->id, $row->name, $row->number_of_users, ]; } } ================================================ FILE: tests/Data/Stubs/FromUsersQueryExport.php ================================================ with([ 'groups' => function ($query) { $query->where('name', 'Group 1'); }, ])->withCount('groups'); } /** * @param mixed $row * @return array */ public function map($row): array { return [ $row->name, $row->groups_count, $row->groups->implode('name', ', '), ]; } } ================================================ FILE: tests/Data/Stubs/FromUsersQueryExportWithMapping.php ================================================ function (BeforeSheet $event) { $event->sheet->chunkSize(10); }, ]; } /** * @param User $row * @return array */ public function map($row): array { return [ 'name' => $row->name, ]; } } ================================================ FILE: tests/Data/Stubs/FromUsersQueryExportWithPrepareRows.php ================================================ map(function ($user) { $user->name .= '_prepared_name'; return $user; })->toArray(); } } ================================================ FILE: tests/Data/Stubs/FromUsersQueryWithJoinExport.php ================================================ query = User::query(); } /** * @return Builder|EloquentBuilder|Relation */ public function query() { return $this->query ->join( 'group_user', 'group_user.user_id', '=', 'users.id' ) ->select('users.*', 'group_user.group_id as gid'); } /** * @return int */ public function chunkSize(): int { return 10; } } ================================================ FILE: tests/Data/Stubs/FromUsersScoutExport.php ================================================ users = $users; } /** * @return SheetForUsersFromView[] */ public function sheets(): array { return [ new SheetForUsersFromView($this->users->forPage(1, 100)), new SheetForUsersFromView($this->users->forPage(2, 100)), new SheetForUsersFromView($this->users->forPage(3, 100)), ]; } } ================================================ FILE: tests/Data/Stubs/ImportWithEvents.php ================================================ $this->beforeImport ?? function () { }, AfterImport::class => $this->afterImport ?? function () { }, BeforeSheet::class => $this->beforeSheet ?? function () { }, AfterSheet::class => $this->afterSheet ?? function () { }, ]; } } ================================================ FILE: tests/Data/Stubs/ImportWithEventsChunksAndBatches.php ================================================ $this->afterBatch ?? function () { }, AfterChunk::class => $this->afterChunk ?? function () { }, ]; } public function model(array $row) { } public function batchSize(): int { return 100; } public function chunkSize(): int { return 500; } } ================================================ FILE: tests/Data/Stubs/ImportWithRegistersEventListeners.php ================================================ $row[0], 'email' => $row[1], 'password' => 'secret', ]); } /** * @return int */ public function chunkSize(): int { return 1; } /** * @return array */ public function registerEvents(): array { return [ BeforeImport::class => function (BeforeImport $event) { Assert::assertInstanceOf(Reader::class, $event->reader); $this->before = true; }, AfterImport::class => function (AfterImport $event) { Assert::assertInstanceOf(Reader::class, $event->reader); $this->after = true; }, ]; } } ================================================ FILE: tests/Data/Stubs/QueuedExport.php ================================================ getMessage()); app()->bind('queue-has-failed-from-queue-export-job', function () { return true; }); } /** * @return array */ public function registerEvents(): array { return [ BeforeExport::class => function () { throw new Exception('catch exception from QueueExport job'); }, ]; } } ================================================ FILE: tests/Data/Stubs/QueuedExportWithFailedHook.php ================================================ 'Patrick', 'lastname' => 'Brouwers', ]), ]); } /** * @param User $user * @return array */ public function map($user): array { throw new Exception('we expect this'); } /** * @param Exception $exception */ public function failed(Exception $exception) { Assert::assertEquals('we expect this', $exception->getMessage()); app()->bind('queue-has-failed', function () { return true; }); } } ================================================ FILE: tests/Data/Stubs/QueuedExportWithLocalePreferences.php ================================================ locale = $locale; } /** * @return Collection */ public function collection() { return collect([ new User([ 'firstname' => 'Patrick', 'lastname' => 'Brouwers', ]), ]); } /** * @return string|null */ public function preferredLocale() { return $this->locale; } /** * @param iterable $rows * @return iterable */ public function prepareRows($rows) { Assert::assertEquals('ru', app()->getLocale()); app()->bind('queue-has-correct-locale', function () { return true; }); return $rows; } } ================================================ FILE: tests/Data/Stubs/QueuedImport.php ================================================ $row[0], ]); } /** * @return int */ public function batchSize(): int { return 100; } /** * @return int */ public function chunkSize(): int { return 100; } } ================================================ FILE: tests/Data/Stubs/QueuedImportWithFailure.php ================================================ $row[0], ]); } /** * @return int */ public function chunkSize(): int { return 100; } } ================================================ FILE: tests/Data/Stubs/QueuedImportWithMiddleware.php ================================================ $row[0], ]); } public function middleware() { return [function () { throw new \Exception('Job reached middleware method'); }]; } /** * @return int */ public function chunkSize(): int { return 100; } } ================================================ FILE: tests/Data/Stubs/QueuedImportWithRetryUntil.php ================================================ $row[0], ]); } /** * @return int */ public function chunkSize(): int { return 100; } /** * Determine the time at which the job should timeout. * * @return \DateTime */ public function retryUntil() { throw new \Exception('Job reached retryUntil method'); return now()->addSeconds(5); } } ================================================ FILE: tests/Data/Stubs/SheetForUsersFromView.php ================================================ users = $users; } /** * @return View */ public function view(): View { return view('users', [ 'users' => $this->users, ]); } } ================================================ FILE: tests/Data/Stubs/SheetWith100Rows.php ================================================ title = $title; } /** * @return Collection */ public function collection() { $collection = new Collection; for ($i = 0; $i < 100; $i++) { $row = new Collection(); for ($j = 0; $j < 5; $j++) { $row[] = $this->title() . '-' . $i . '-' . $j; } $collection->push($row); } return $collection; } /** * @return string */ public function title(): string { return $this->title; } /** * @param BeforeWriting $event */ public static function beforeWriting(BeforeWriting $event) { TestCase::assertInstanceOf(Writer::class, $event->writer); } } ================================================ FILE: tests/Data/Stubs/ShouldQueueExport.php ================================================ Name Email @foreach($users as $user) {{ $user->name }} {{ $user->email }} @endforeach ================================================ FILE: tests/Data/Stubs/WithMappingExport.php ================================================ getProperties() will be called via __call on the ->getDelegate() TestCase::assertInstanceOf(Properties::class, $event->writer->getProperties()); } }; $export->download('some-file.xlsx'); } public function test_can_use_writer_macros() { $called = false; Writer::macro('test', function () use (&$called) { $called = true; }); $export = new class implements WithEvents { use RegistersEventListeners, Exportable; public static function beforeExport(BeforeExport $event) { // call macro method $event->writer->test(); } }; $export->download('some-file.xlsx'); $this->assertTrue($called); } public function test_can_use_sheet_macros() { $called = false; Sheet::macro('test', function () use (&$called) { $called = true; }); $export = new class implements WithEvents { use RegistersEventListeners, Exportable; public static function beforeSheet(BeforeSheet $event) { // call macro method $event->sheet->test(); } }; $export->download('some-file.xlsx'); $this->assertTrue($called); } } ================================================ FILE: tests/ExcelFakeTest.php ================================================ assertInstanceOf(ExcelFake::class, $this->app->make('excel')); } public function test_can_assert_against_a_fake_downloaded_export() { ExcelFacade::fake(); $response = ExcelFacade::download($this->givenExport(), 'downloaded-filename.csv'); $this->assertInstanceOf(BinaryFileResponse::class, $response); ExcelFacade::assertDownloaded('downloaded-filename.csv'); ExcelFacade::assertDownloaded('downloaded-filename.csv', function (FromCollection $export) { return $export->collection()->contains('foo'); }); ExcelFacade::matchByRegex(); ExcelFacade::assertDownloaded('/\w{10}-\w{8}\.csv/'); } public function test_can_assert_against_a_fake_stored_export() { ExcelFacade::fake(); $response = ExcelFacade::store($this->givenExport(), 'stored-filename.csv', 's3'); $this->assertTrue($response); ExcelFacade::assertStored('stored-filename.csv', 's3'); ExcelFacade::assertStored('stored-filename.csv', 's3', function (FromCollection $export) { return $export->collection()->contains('foo'); }); ExcelFacade::matchByRegex(); ExcelFacade::assertStored('/\w{6}-\w{8}\.csv/', 's3'); } public function test_can_assert_regex_against_a_fake_stored_export_with_multiple_files() { ExcelFacade::fake(); $response = ExcelFacade::store($this->givenExport(), 'stored-filename-one.csv', 's3'); $this->assertTrue($response); $response = ExcelFacade::store($this->givenExport(), 'stored-filename-two.csv', 's3'); $this->assertTrue($response); ExcelFacade::matchByRegex(); ExcelFacade::assertStored('/\w{6}-\w{8}-one\.csv/', 's3'); ExcelFacade::assertStored('/\w{6}-\w{8}-two\.csv/', 's3'); } public function test_a_callback_can_be_passed_as_the_second_argument_when_asserting_against_a_faked_stored_export() { ExcelFacade::fake(); $response = ExcelFacade::store($this->givenExport(), 'stored-filename.csv'); $this->assertTrue($response); ExcelFacade::assertStored('stored-filename.csv'); ExcelFacade::assertStored('stored-filename.csv', function (FromCollection $export) { return $export->collection()->contains('foo'); }); ExcelFacade::matchByRegex(); ExcelFacade::assertStored('/\w{6}-\w{8}\.csv/'); } public function test_can_assert_against_a_fake_queued_export() { ExcelFacade::fake(); $response = ExcelFacade::queue($this->givenExport(), 'queued-filename.csv', 's3'); $this->assertInstanceOf(PendingDispatch::class, $response); ExcelFacade::assertQueued('queued-filename.csv', 's3'); ExcelFacade::assertQueued('queued-filename.csv', 's3', function (FromCollection $export) { return $export->collection()->contains('foo'); }); ExcelFacade::matchByRegex(); ExcelFacade::assertQueued('/\w{6}-\w{8}\.csv/', 's3'); } public function test_can_assert_against_a_fake_implicitly_queued_export() { ExcelFacade::fake(); $response = ExcelFacade::store($this->givenQueuedExport(), 'queued-filename.csv', 's3'); $this->assertInstanceOf(PendingDispatch::class, $response); ExcelFacade::assertStored('queued-filename.csv', 's3'); ExcelFacade::assertQueued('queued-filename.csv', 's3'); ExcelFacade::assertQueued('queued-filename.csv', 's3', function (FromCollection $export) { return $export->collection()->contains('foo'); }); ExcelFacade::matchByRegex(); ExcelFacade::assertQueued('/\w{6}-\w{8}\.csv/', 's3'); } public function test_can_assert_against_a_fake_queued_export_with_chain() { ExcelFacade::fake(); ExcelFacade::queue( $this->givenQueuedExport(), 'queued-filename.csv', 's3' )->chain([ new ChainedJobStub(), ]); ExcelFacade::assertQueuedWithChain([ new ChainedJobStub(), ]); } public function test_can_assert_against_a_fake_raw_export() { ExcelFacade::fake(); $response = ExcelFacade::raw($this->givenExport(), \Maatwebsite\Excel\Excel::XLSX); $this->assertIsString($response); ExcelFacade::assertExportedInRaw(get_class($this->givenExport())); ExcelFacade::assertExportedInRaw(get_class($this->givenExport()), function (FromCollection $export) { return $export->collection()->contains('foo'); }); } public function test_can_assert_against_a_fake_import() { ExcelFacade::fake(); ExcelFacade::import($this->givenImport(), 'stored-filename.csv', 's3'); ExcelFacade::assertImported('stored-filename.csv', 's3'); ExcelFacade::assertImported('stored-filename.csv', 's3', function (ToModel $import) { return $import->model([]) instanceof User; }); ExcelFacade::matchByRegex(); ExcelFacade::assertImported('/\w{6}-\w{8}\.csv/', 's3'); } public function test_can_assert_against_a_fake_import_with_uploaded_file() { ExcelFacade::fake(); ExcelFacade::import($this->givenImport(), $this->givenUploadedFile(__DIR__ . '/Data/Disks/Local/import.xlsx')); ExcelFacade::assertImported('import.xlsx'); ExcelFacade::assertImported('import.xlsx', function (ToModel $import) { return $import->model([]) instanceof User; }); ExcelFacade::matchByRegex(); ExcelFacade::assertImported('/\w{6}\.xlsx/'); } public function test_can_assert_against_a_fake_queued_import() { ExcelFacade::fake(); $response = ExcelFacade::queueImport($this->givenQueuedImport(), 'queued-filename.csv', 's3'); $this->assertInstanceOf(PendingDispatch::class, $response); ExcelFacade::assertImported('queued-filename.csv', 's3'); ExcelFacade::assertQueued('queued-filename.csv', 's3'); ExcelFacade::assertQueued('queued-filename.csv', 's3', function (ToModel $import) { return $import->model([]) instanceof User; }); ExcelFacade::matchByRegex(); ExcelFacade::assertQueued('/\w{6}-\w{8}\.csv/', 's3'); } public function test_can_assert_against_a_fake_implicitly_queued_import() { ExcelFacade::fake(); $response = ExcelFacade::import($this->givenQueuedImport(), 'queued-filename.csv', 's3'); $this->assertInstanceOf(PendingDispatch::class, $response); ExcelFacade::assertImported('queued-filename.csv', 's3'); ExcelFacade::assertQueued('queued-filename.csv', 's3'); ExcelFacade::assertQueued('queued-filename.csv', 's3', function (ToModel $import) { return $import->model([]) instanceof User; }); ExcelFacade::matchByRegex(); ExcelFacade::assertQueued('/\w{6}-\w{8}\.csv/', 's3'); } public function test_can_assert_against_a_fake_queued_import_with_chain() { ExcelFacade::fake(); ExcelFacade::queueImport( $this->givenQueuedImport(), 'queued-filename.csv', 's3' )->chain([ new ChainedJobStub(), ]); ExcelFacade::assertQueuedWithChain([ new ChainedJobStub(), ]); } public function test_a_callback_can_be_passed_as_the_second_argument_when_asserting_against_a_faked_queued_export() { ExcelFacade::fake(); $response = ExcelFacade::queue($this->givenExport(), 'queued-filename.csv'); $this->assertInstanceOf(PendingDispatch::class, $response); ExcelFacade::assertQueued('queued-filename.csv'); ExcelFacade::assertQueued('queued-filename.csv', function (FromCollection $export) { return $export->collection()->contains('foo'); }); ExcelFacade::matchByRegex(); ExcelFacade::assertQueued('/\w{6}-\w{8}\.csv/'); } /** * @return FromCollection */ private function givenExport() { return new class implements FromCollection { /** * @return Collection */ public function collection() { return collect(['foo', 'bar']); } }; } /** * @return FromCollection */ private function givenQueuedExport() { return new class implements FromCollection, ShouldQueue { /** * @return Collection */ public function collection() { return collect(['foo', 'bar']); } }; } /** * @return object */ private function givenImport() { return new class implements ToModel { /** * @param array $row * @return Model|null */ public function model(array $row) { return new User([]); } }; } /** * @return object */ private function givenQueuedImport() { return new class implements ToModel, ShouldQueue { /** * @param array $row * @return Model|null */ public function model(array $row) { return new User([]); } }; } } ================================================ FILE: tests/ExcelServiceProviderTest.php ================================================ app->make(TransactionManager::class)->extend('handler', function () { return new CustomTransactionHandler; }); $this->assertInstanceOf(CustomTransactionHandler::class, $this->app->make(TransactionManager::class)->driver('handler')); } public function test_is_bound() { $this->assertTrue($this->app->bound('excel')); } public function test_has_aliased() { $this->assertTrue($this->app->isAlias(Excel::class)); $this->assertEquals('excel', $this->app->getAlias(Excel::class)); } public function test_registers_console_commands() { /** @var Kernel $kernel */ $kernel = $this->app->make(Kernel::class); $commands = $kernel->all(); $this->assertArrayHasKey('make:export', $commands); $this->assertArrayHasKey('make:import', $commands); } public function test_sets_php_spreadsheet_settings() { $driver = config('excel.cache.driver'); $this->assertEquals('memory', $driver); if (InstalledVersions::satisfies(new VersionParser, 'psr/simple-cache', '^3.0')) { $this->assertInstanceOf( MemoryCache::class, Settings::getCache() ); } else { $this->assertInstanceOf( MemoryCacheDeprecated::class, Settings::getCache() ); } } } ================================================ FILE: tests/ExcelTest.php ================================================ SUT = $this->app->make(Excel::class); } public function test_can_download_an_export_object_with_facade() { $export = new EmptyExport(); $response = ExcelFacade::download($export, 'filename.xlsx'); $this->assertInstanceOf(BinaryFileResponse::class, $response); $this->assertEquals('attachment; filename=filename.xlsx', str_replace('"', '', $response->headers->get('Content-Disposition'))); } public function test_can_download_an_export_object() { $export = new EmptyExport(); $response = $this->SUT->download($export, 'filename.xlsx'); $this->assertInstanceOf(BinaryFileResponse::class, $response); $this->assertEquals('attachment; filename=filename.xlsx', str_replace('"', '', $response->headers->get('Content-Disposition'))); } public function test_can_store_an_export_object_on_default_disk() { $export = new EmptyExport; $name = 'filename.xlsx'; $path = FileHelper::absolutePath($name, 'local'); @unlink($path); $this->assertFileMissing($path); $response = $this->SUT->store($export, $name); $this->assertTrue($response); $this->assertFileExists($path); } public function test_can_store_an_export_object_on_another_disk() { $export = new EmptyExport; $name = 'filename.xlsx'; $path = FileHelper::absolutePath($name, 'test'); @unlink($path); $this->assertFileMissing($path); $response = $this->SUT->store($export, $name, 'test'); $this->assertTrue($response); $this->assertFileExists($path); } public function test_can_store_csv_export_with_default_settings() { $export = new EmptyExport; $name = 'filename.csv'; $path = FileHelper::absolutePath($name, 'local'); @unlink($path); $this->assertFileMissing($path); $response = $this->SUT->store($export, $name); $this->assertTrue($response); $this->assertFileExists($path); } public function test_can_get_raw_export_contents() { $export = new EmptyExport; $response = $this->SUT->raw($export, Excel::XLSX); $this->assertNotEmpty($response); } public function test_can_store_tsv_export_with_default_settings() { $export = new EmptyExport; $name = 'filename.tsv'; $path = FileHelper::absolutePath($name, 'local'); @unlink($path); $this->assertFileMissing($path); $response = $this->SUT->store($export, $name); $this->assertTrue($response); $this->assertFileExists($path); } public function test_can_store_csv_export_with_custom_settings() { $export = new class implements WithEvents, FromCollection, WithCustomCsvSettings { use RegistersEventListeners; /** * @return Collection */ public function collection() { return collect([ ['A1', 'B1'], ['A2', 'B2'], ]); } /** * @return array */ public function getCsvSettings(): array { return [ 'line_ending' => PHP_EOL, 'enclosure' => '"', 'delimiter' => ';', 'include_separator_line' => true, 'excel_compatibility' => false, ]; } }; $this->SUT->store($export, 'filename.csv'); $contents = file_get_contents(__DIR__ . '/Data/Disks/Local/filename.csv'); $this->assertStringContains('sep=;', $contents); $this->assertStringContains('"A1";"B1"', $contents); $this->assertStringContains('"A2";"B2"', $contents); } public function test_cannot_use_from_collection_and_from_view_on_same_export() { $this->expectException(\Maatwebsite\Excel\Exceptions\ConcernConflictException::class); $this->expectExceptionMessage('Cannot use FromQuery, FromArray or FromCollection and FromView on the same sheet'); $export = new class implements FromCollection, FromView { use Exportable; /** * @return Collection */ public function collection() { return collect(); } /** * @return View */ public function view(): View { return view('users'); } }; $export->download('filename.csv'); } public function test_can_import_a_simple_xlsx_file_to_array() { $import = new class { use Importable; }; $this->assertEquals([ [ ['test', 'test'], ['test', 'test'], ], ], $import->toArray('import.xlsx')); } public function test_can_import_a_simple_xlsx_file_to_collection() { $import = new class { use Importable; }; $this->assertEquals(new Collection([ new Collection([ new Collection(['test', 'test']), new Collection(['test', 'test']), ]), ]), $import->toCollection('import.xlsx')); } public function test_can_import_a_simple_xlsx_file_to_collection_without_import_object() { $this->assertEquals(new Collection([ new Collection([ new Collection(['test', 'test']), new Collection(['test', 'test']), ]), ]), ExcelFacade::toCollection(null, 'import.xlsx')); } public function test_can_import_a_simple_xlsx_file() { $import = new class implements ToArray { /** * @param array $array */ public function array(array $array) { Assert::assertEquals([ ['test', 'test'], ['test', 'test'], ], $array); } }; $imported = $this->SUT->import($import, 'import.xlsx'); $this->assertInstanceOf(Importer::class, $imported); } public function test_can_import_a_tsv_file() { $import = new class implements ToArray, WithCustomCsvSettings { /** * @param array $array */ public function array(array $array) { Assert::assertEquals([ 'tconst', 'titleType', 'primaryTitle', 'originalTitle', 'isAdult', 'startYear', 'endYear', 'runtimeMinutes', 'genres', ], $array[0]); } /** * @return array */ public function getCsvSettings(): array { return [ 'delimiter' => "\t", ]; } }; $imported = $this->SUT->import($import, 'import-titles.tsv'); $this->assertInstanceOf(Importer::class, $imported); } public function test_can_chain_imports() { $import1 = new class implements ToArray { /** * @param array $array */ public function array(array $array) { Assert::assertEquals([ ['test', 'test'], ['test', 'test'], ], $array); } }; $import2 = new class implements ToArray { /** * @param array $array */ public function array(array $array) { Assert::assertEquals([ ['test', 'test'], ['test', 'test'], ], $array); } }; $imported = $this->SUT ->import($import1, 'import.xlsx') ->import($import2, 'import.xlsx'); $this->assertInstanceOf(Importer::class, $imported); } public function test_can_import_a_simple_xlsx_file_from_uploaded_file() { $import = new class implements ToArray { /** * @param array $array */ public function array(array $array) { Assert::assertEquals([ ['test', 'test'], ['test', 'test'], ], $array); } }; $this->SUT->import($import, $this->givenUploadedFile(__DIR__ . '/Data/Disks/Local/import.xlsx')); } public function test_can_import_a_simple_xlsx_file_from_real_path() { $import = new class implements ToArray { /** * @param array $array */ public function array(array $array) { Assert::assertEquals([ ['test', 'test'], ['test', 'test'], ], $array); } }; $this->SUT->import($import, __DIR__ . '/Data/Disks/Local/import.xlsx'); } public function test_import_will_throw_error_when_no_reader_type_could_be_detected_when_no_extension() { $this->expectException(\Maatwebsite\Excel\Exceptions\NoTypeDetectedException::class); $import = new class implements ToArray { /** * @param array $array */ public function array(array $array) { Assert::assertEquals([ ['test', 'test'], ['test', 'test'], ], $array); } }; $this->SUT->import($import, UploadedFile::fake()->create('import')); } public function test_import_will_throw_error_when_no_reader_type_could_be_detected_with_unknown_extension() { $this->expectException(\Maatwebsite\Excel\Exceptions\NoTypeDetectedException::class); $import = new class implements ToArray { /** * @param array $array */ public function array(array $array) { // } }; $this->SUT->import($import, 'unknown-reader-type.zip'); } public function test_can_import_without_extension_with_explicit_reader_type() { $import = new class implements ToArray { /** * @param array $array */ public function array(array $array) { Assert::assertEquals([ ['test', 'test'], ['test', 'test'], ], $array); } }; $this->SUT->import( $import, $this->givenUploadedFile(__DIR__ . '/Data/Disks/Local/import.xlsx', 'import'), null, Excel::XLSX ); } } ================================================ FILE: tests/HeadingRowImportTest.php ================================================ toArray('import-users-with-headings.xlsx'); $this->assertEquals([ [ ['name', 'email'], ], ], $headings); } public function test_can_import_only_heading_row_with_custom_heading_row_formatter() { HeadingRowFormatter::extend('custom', function ($value) { return 'custom-' . $value; }); HeadingRowFormatter::default('custom'); $import = new HeadingRowImport(); $headings = $import->toArray('import-users-with-headings.xlsx'); $this->assertEquals([ [ ['custom-name', 'custom-email'], ], ], $headings); } public function test_can_import_only_heading_row_with_custom_heading_row_formatter_with_key() { HeadingRowFormatter::extend('custom', function ($value, $key) { return $key; }); HeadingRowFormatter::default('custom'); $import = new HeadingRowImport(); $headings = $import->toArray('import-users-with-headings.xlsx'); $this->assertEquals([ [ [0, 1], ], ], $headings); } public function test_can_import_only_heading_row_with_custom_row_number() { $import = new HeadingRowImport(2); $headings = $import->toArray('import-users-with-headings.xlsx'); $this->assertEquals([ [ ['patrick_brouwers', 'patrick_at_maatwebsitenl'], ], ], $headings); } public function test_can_import_only_heading_row_for_multiple_sheets() { $import = new HeadingRowImport(); $headings = $import->toArray('import-multiple-sheets.xlsx'); $this->assertEquals([ [ ['1a1', '1b1'], // slugged first row of sheet 1 ], [ ['2a1', '2b1'], // slugged first row of sheet 2 ], ], $headings); } public function test_can_import_only_heading_row_for_multiple_sheets_with_key() { HeadingRowFormatter::extend('custom', function ($value, $key) { return $key; }); HeadingRowFormatter::default('custom'); $import = new HeadingRowImport(); $headings = $import->toArray('import-multiple-sheets.xlsx'); $this->assertEquals([ [ [0, 1], // slugged first row of sheet 1 ], [ [0, 1], // slugged first row of sheet 2 ], ], $headings); } public function test_can_import_only_heading_row_for_multiple_sheets_with_custom_row_number() { $import = new HeadingRowImport(2); $headings = $import->toArray('import-multiple-sheets.xlsx'); $this->assertEquals([ [ ['1a2', '1b2'], // slugged 2nd row of sheet 1 ], [ ['2a2', '2b2'], // slugged 2nd row of sheet 2 ], ], $headings); } public function test_can_import_heading_row_with_custom_formatter_defined_in_config() { HeadingRowFormatter::extend('custom2', function ($value) { return 'custom2-' . $value; }); config()->set('excel.imports.heading_row.formatter', 'custom2'); $import = new HeadingRowImport(); $headings = $import->toArray('import-users-with-headings.xlsx'); $this->assertEquals([ [ ['custom2-name', 'custom2-email'], ], ], $headings); } } ================================================ FILE: tests/Helpers/FileHelper.php ================================================ assertContains(InteractsWithQueue::class, class_uses(ReadChunk::class)); } public function test_append_data_to_sheet_job_can_interact_with_queue() { $this->assertContains(InteractsWithQueue::class, class_uses(AppendDataToSheet::class)); } public function test_append_query_to_sheet_job_can_interact_with_queue() { $this->assertContains(InteractsWithQueue::class, class_uses(AppendQueryToSheet::class)); } public function test_append_view_to_sheet_job_can_interact_with_queue() { $this->assertContains(InteractsWithQueue::class, class_uses(AppendViewToSheet::class)); } public function test_queue_export_job_can_interact_with_queue() { $this->assertContains(InteractsWithQueue::class, class_uses(QueueExport::class)); } } ================================================ FILE: tests/Mixins/DownloadCollectionTest.php ================================================ 'test', 'column_2' => 'test'], ['column_1' => 'test2', 'column_2' => 'test2'], ]); $response = $collection->downloadExcel('collection-download.xlsx', Excel::XLSX); $array = $this->readAsArray($response->getFile()->getPathName(), Excel::XLSX); // First row are not headings $firstRow = collect($array)->first(); $this->assertEquals(['test', 'test'], $firstRow); $this->assertInstanceOf(BinaryFileResponse::class, $response); $this->assertEquals( 'attachment; filename=collection-download.xlsx', str_replace('"', '', $response->headers->get('Content-Disposition')) ); } public function test_can_download_a_collection_with_headers_as_excel() { $collection = new Collection([ ['column_1' => 'test', 'column_2' => 'test'], ['column_1' => 'test', 'column_2' => 'test'], ]); $response = $collection->downloadExcel('collection-headers-download.xlsx', Excel::XLSX, true); $array = $this->readAsArray($response->getFile()->getPathName(), Excel::XLSX); $this->assertEquals(['column_1', 'column_2'], collect($array)->first()); } public function test_can_download_collection_with_headers_with_hidden_eloquent_attributes() { $collection = new Collection([ new User(['name' => 'Patrick', 'password' => 'my_password']), ]); $response = $collection->downloadExcel('collection-headers-download.xlsx', Excel::XLSX, true); $array = $this->readAsArray($response->getFile()->getPathName(), Excel::XLSX); $this->assertEquals(['name'], collect($array)->first()); } public function test_can_download_collection_with_headers_when_making_attributes_visible() { $user = new User(['name' => 'Patrick', 'password' => 'my_password']); $user->makeVisible(['password']); $collection = new Collection([ $user, ]); $response = $collection->downloadExcel('collection-headers-download.xlsx', Excel::XLSX, true); $array = $this->readAsArray($response->getFile()->getPathName(), Excel::XLSX); $this->assertEquals(['name', 'password'], collect($array)->first()); } public function test_can_set_custom_response_headers() { $collection = new Collection([ ['column_1' => 'test', 'column_2' => 'test'], ['column_1' => 'test2', 'column_2' => 'test2'], ]); $responseHeaders = [ 'CUSTOMER-HEADER-1' => 'CUSTOMER-HEADER1-VAL', 'CUSTOMER-HEADER-2' => 'CUSTOMER-HEADER2-VAL', ]; /** @var BinaryFileResponse $response */ $response = $collection->downloadExcel('collection-download.xlsx', Excel::XLSX, false, $responseHeaders); $this->assertTrue($response->headers->contains('CUSTOMER-HEADER-1', 'CUSTOMER-HEADER1-VAL')); $this->assertTrue($response->headers->contains('CUSTOMER-HEADER-2', 'CUSTOMER-HEADER2-VAL')); } } ================================================ FILE: tests/Mixins/DownloadQueryMacroTest.php ================================================ loadLaravelMigrations(['--database' => 'testing']); $this->withFactories(__DIR__ . '/../Data/Stubs/Database/Factories'); factory(User::class)->times(100)->create(); } public function test_can_download_a_query_as_excel() { $response = User::downloadExcel('query-download.xlsx', Excel::XLSX); $array = $this->readAsArray($response->getFile()->getPathName(), Excel::XLSX); $this->assertCount(100, $array); $this->assertInstanceOf(BinaryFileResponse::class, $response); $this->assertEquals( 'attachment; filename=query-download.xlsx', str_replace('"', '', $response->headers->get('Content-Disposition')) ); } public function test_can_download_a_collection_with_headers_as_excel() { $response = User::downloadExcel('collection-headers-download.xlsx', Excel::XLSX, true); $array = $this->readAsArray($response->getFile()->getPathName(), Excel::XLSX); $this->assertCount(101, $array); $this->assertEquals(['id', 'name', 'email', 'remember_token', 'created_at', 'updated_at'], collect($array)->first()); } } ================================================ FILE: tests/Mixins/ImportAsMacroTest.php ================================================ loadLaravelMigrations(['--database' => 'testing']); } public function test_can_import_directly_into_a_model_with_mapping() { User::query()->truncate(); User::importAs('import-users.xlsx', function (array $row) { return [ 'name' => $row[0], 'email' => $row[1], 'password' => 'secret', ]; }); $this->assertCount(2, User::all()); $this->assertEquals([ 'patrick@maatwebsite.nl', 'taylor@laravel.com', ], User::query()->pluck('email')->all()); } } ================================================ FILE: tests/Mixins/ImportMacroTest.php ================================================ loadLaravelMigrations(['--database' => 'testing']); } public function test_can_import_directly_into_a_model() { User::query()->truncate(); User::creating(function ($user) { $user->password = 'secret'; }); User::import('import-users-with-headings.xlsx'); $this->assertCount(2, User::all()); $this->assertEquals([ 'patrick@maatwebsite.nl', 'taylor@laravel.com', ], User::query()->pluck('email')->all()); } } ================================================ FILE: tests/Mixins/StoreCollectionTest.php ================================================ storeExcel('collection-store.xlsx'); $this->assertTrue($response); $this->assertFileExists(__DIR__ . '/../Data/Disks/Local/collection-store.xlsx'); } public function test_can_store_a_collection_as_excel_on_non_default_disk() { $collection = new Collection([ ['column_1' => 'test', 'column_2' => 'test'], ['column_1' => 'test2', 'column_2' => 'test2'], ]); $response = $collection->storeExcel('collection-store.xlsx', null, Excel::XLSX); $file = __DIR__ . '/../Data/Disks/Local/collection-store.xlsx'; $this->assertTrue($response); $this->assertFileExists($file); $array = $this->readAsArray($file, Excel::XLSX); // First row are not headings $firstRow = collect($array)->first(); $this->assertEquals(['test', 'test'], $firstRow); $this->assertEquals([ ['test', 'test'], ['test2', 'test2'], ], collect($array)->values()->all()); } public function test_can_store_a_collection_with_headings_as_excel() { $collection = new Collection([ ['column_1' => 'test', 'column_2' => 'test'], ['column_1' => 'test', 'column_2' => 'test'], ]); $response = $collection->storeExcel('collection-headers-store.xlsx', null, Excel::XLSX, true); $file = __DIR__ . '/../Data/Disks/Local/collection-headers-store.xlsx'; $this->assertTrue($response); $this->assertFileExists($file); $array = $this->readAsArray($file, Excel::XLSX); $this->assertEquals(['column_1', 'column_2'], collect($array)->first()); $this->assertEquals([ ['test', 'test'], ['test', 'test'], ], collect($array)->except(0)->values()->all()); } public function test_can_store_a_model_collection_with_headings_as_excel() { $this->withFactories(__DIR__ . '/../Data/Stubs/Database/Factories'); $collection = factory(User::class, 2)->make(); $response = $collection->storeExcel('model-collection-headers-store.xlsx', null, Excel::XLSX, true); $file = __DIR__ . '/../Data/Disks/Local/model-collection-headers-store.xlsx'; $this->assertTrue($response); $this->assertFileExists($file); $array = $this->readAsArray($file, Excel::XLSX); $this->assertEquals(['name', 'email', 'remember_token'], collect($array)->first()); } } ================================================ FILE: tests/Mixins/StoreQueryMacroTest.php ================================================ loadLaravelMigrations(['--database' => 'testing']); $this->withFactories(__DIR__ . '/../Data/Stubs/Database/Factories'); factory(User::class)->times(100)->create(); } public function test_can_download_a_query_as_excel() { $response = User::storeExcel('query-store.xlsx', null, Excel::XLSX); $this->assertTrue($response); $this->assertFileExists(__DIR__ . '/../Data/Disks/Local/query-store.xlsx'); $array = $this->readAsArray(__DIR__ . '/../Data/Disks/Local/query-store.xlsx', Excel::XLSX); $this->assertCount(100, $array); } public function test_can_download_a_query_as_excel_on_different_disk() { $response = User::storeExcel('query-store.xlsx', 'test', Excel::XLSX); $this->assertTrue($response); $this->assertFileExists(__DIR__ . '/../Data/Disks/Test/query-store.xlsx'); $array = $this->readAsArray(__DIR__ . '/../Data/Disks/Test/query-store.xlsx', Excel::XLSX); $this->assertCount(100, $array); } public function test_can_store_a_query_with_headers_as_excel() { $response = User::storeExcel('query-headers-store.xlsx', null, Excel::XLSX, true); $this->assertTrue($response); $this->assertFileExists(__DIR__ . '/../Data/Disks/Local/query-headers-store.xlsx'); $array = $this->readAsArray(__DIR__ . '/../Data/Disks/Local/query-headers-store.xlsx', Excel::XLSX); $this->assertCount(101, $array); } } ================================================ FILE: tests/QueuedExportTest.php ================================================ queue('queued-export.xlsx')->chain([ new AfterQueueExportJob(__DIR__ . '/Data/Disks/Local/queued-export.xlsx'), ]); } public function test_can_queue_an_export_and_store_on_different_disk() { $export = new QueuedExport(); $export->queue('queued-export.xlsx', 'test')->chain([ new AfterQueueExportJob(__DIR__ . '/Data/Disks/Test/queued-export.xlsx'), ]); } public function test_can_queue_export_with_remote_temp_disk() { config()->set('excel.temporary_files.remote_disk', 'test'); // Delete the local temp file before each append job // to simulate using a shared remote disk, without // having a dependency on a local temp file. $jobs = 0; Queue::before(function (JobProcessing $event) use (&$jobs) { if ($event->job->resolveName() === AppendDataToSheet::class) { /** @var TemporaryFile $tempFile */ $tempFile = $this->inspectJobProperty($event->job, 'temporaryFile'); $this->assertInstanceOf(RemoteTemporaryFile::class, $tempFile); // Should exist remote $this->assertTrue( $tempFile->exists() ); // File was deleted locally $this->assertFalse( file_exists($tempFile->getLocalPath()) ); $jobs++; } }); $export = new QueuedExport(); $export->queue('queued-export.xlsx')->chain([ new AfterQueueExportJob(__DIR__ . '/Data/Disks/Local/queued-export.xlsx'), ]); $array = $this->readAsArray(__DIR__ . '/Data/Disks/Local/queued-export.xlsx', Excel::XLSX); $this->assertCount(100, $array); $this->assertEquals(3, $jobs); } public function test_can_queue_export_with_remote_temp_disk_and_prefix() { config()->set('excel.temporary_files.remote_disk', 'test'); config()->set('excel.temporary_files.remote_prefix', 'tmp/'); $export = new QueuedExport(); $export->queue('queued-export.xlsx')->chain([ new AfterQueueExportJob(__DIR__ . '/Data/Disks/Local/queued-export.xlsx'), ]); } public function test_can_implicitly_queue_an_export() { $export = new ShouldQueueExport(); $export->store('queued-export.xlsx', 'test')->chain([ new AfterQueueExportJob(__DIR__ . '/Data/Disks/Test/queued-export.xlsx'), ]); } public function test_can_queue_export_with_mapping_on_eloquent_models() { $export = new EloquentCollectionWithMappingExport(); $export->queue('queued-export.xlsx')->chain([ new AfterQueueExportJob(__DIR__ . '/Data/Disks/Local/queued-export.xlsx'), ]); $actual = $this->readAsArray(__DIR__ . '/Data/Disks/Local/queued-export.xlsx', 'Xlsx'); $this->assertEquals([ ['Patrick', 'Brouwers'], ], $actual); } public function test_can_catch_failures() { $export = new QueuedExportWithFailedHook(); try { $export->queue('queued-export.xlsx'); } catch (Throwable $e) { } $this->assertTrue(app('queue-has-failed')); } public function test_can_catch_failures_on_queue_export_job() { $export = new QueuedExportWithFailedEvents(); try { $export->queue('queued-export.xlsx'); } catch (Throwable $e) { } $this->assertTrue(app('queue-has-failed-from-queue-export-job')); } public function test_can_set_locale_on_queue_export_job() { $currentLocale = app()->getLocale(); $export = new QueuedExportWithLocalePreferences('ru'); $export->queue('queued-export.xlsx'); $this->assertTrue(app('queue-has-correct-locale')); $this->assertEquals($currentLocale, app()->getLocale()); } public function test_can_queue_export_not_flushing_the_cache() { config()->set('excel.cache.driver', 'illuminate'); Cache::put('test', 'test'); $export = new QueuedExport(); $export->queue('queued-export.xlsx')->chain([ new AfterQueueExportJob(__DIR__ . '/Data/Disks/Local/queued-export.xlsx'), ]); $array = $this->readAsArray(__DIR__ . '/Data/Disks/Local/queued-export.xlsx', Excel::XLSX); $this->assertCount(100, $array); $this->assertEquals('test', Cache::get('test')); } } ================================================ FILE: tests/QueuedImportTest.php ================================================ loadLaravelMigrations(['--database' => 'testing']); $this->loadMigrationsFrom(__DIR__ . '/Data/Stubs/Database/Migrations'); } public function test_cannot_queue_import_that_does_not_implement_should_queue() { $this->expectException(\InvalidArgumentException::class); $this->expectExceptionMessage('Importable should implement ShouldQueue to be queued.'); $import = new class { use Importable; }; $import->queue('import-batches.xlsx'); } public function test_can_queue_an_import() { $import = new QueuedImport(); $chain = $import->queue('import-batches.xlsx')->chain([ new AfterQueueImportJob(5000), ]); $this->assertInstanceOf(PendingDispatch::class, $chain); } public function test_can_queue_an_import_with_batch_cache_and_file_store() { config()->set('queue.default', 'sync'); config()->set('excel.cache.driver', 'batch'); config()->set('excel.cache.illuminate.store', 'file'); config()->set('excel.cache.batch.memory_limit', 80); // Reset the cache settings $this->app->make(SettingsProvider::class)->provide(); $import = new QueuedImport(); $chain = $import->queue('import-batches.xlsx'); $this->assertInstanceOf(PendingDispatch::class, $chain); } public function test_can_queue_import_with_remote_temp_disk() { config()->set('excel.temporary_files.remote_disk', 'test'); // Delete the local temp file before each read chunk job // to simulate using a shared remote disk, without // having a dependency on a local temp file. Queue::before(function (JobProcessing $event) { if ($event->job->resolveName() === ReadChunk::class) { /** @var TemporaryFile $tempFile */ $tempFile = $this->inspectJobProperty($event->job, 'temporaryFile'); $this->assertInstanceOf(RemoteTemporaryFile::class, $tempFile); // Should exist remote $this->assertTrue( $tempFile->exists() ); $this->assertTrue( unlink($tempFile->getLocalPath()) ); } }); $import = new QueuedImport(); $chain = $import->queue('import-batches.xlsx')->chain([ new AfterQueueImportJob(5000), ]); $this->assertInstanceOf(PendingDispatch::class, $chain); } public function test_can_keep_extension_for_temp_file_on_remote_disk() { config()->set('excel.temporary_files.remote_disk', 'test'); Queue::before(function (JobProcessing $event) { if ($event->job->resolveName() === ReadChunk::class) { /** @var TemporaryFile $tempFile */ $tempFile = $this->inspectJobProperty($event->job, 'temporaryFile'); $this->assertStringContains('.xlsx', $tempFile->getLocalPath()); } }); (new QueuedImport())->queue('import-batches.xlsx'); } public function test_can_queue_import_with_remote_temp_disk_and_prefix() { config()->set('excel.temporary_files.remote_disk', 'test'); config()->set('excel.temporary_files.remote_prefix', 'tmp/'); $import = new QueuedImport(); $chain = $import->queue('import-batches.xlsx')->chain([ new AfterQueueImportJob(5000), ]); $this->assertInstanceOf(PendingDispatch::class, $chain); } public function test_can_automatically_delete_temp_file_on_failure_when_using_remote_disk() { config()->set('excel.temporary_files.remote_disk', 'test'); $tempFile = ''; Queue::exceptionOccurred(function (JobExceptionOccurred $event) use (&$tempFile) { if ($event->job->resolveName() === ReadChunk::class) { $tempFile = $this->inspectJobProperty($event->job, 'temporaryFile'); } }); try { (new QueuedImportWithFailure())->queue('import-batches.xlsx'); } catch (Throwable $e) { $this->assertEquals('Something went wrong in the chunk', $e->getMessage()); } $this->assertFalse($tempFile->existsLocally()); $this->assertTrue($tempFile->exists()); } public function test_cannot_automatically_delete_temp_file_on_failure_when_using_local_disk() { $tempFile = ''; Queue::exceptionOccurred(function (JobExceptionOccurred $event) use (&$tempFile) { if ($event->job->resolveName() === ReadChunk::class) { $tempFile = $this->inspectJobProperty($event->job, 'temporaryFile'); } }); try { (new QueuedImportWithFailure())->queue('import-batches.xlsx'); } catch (Throwable $e) { $this->assertEquals('Something went wrong in the chunk', $e->getMessage()); } $this->assertTrue($tempFile->exists()); } public function test_can_force_remote_download_and_deletion_for_each_chunk_on_queue() { config()->set('excel.temporary_files.remote_disk', 'test'); config()->set('excel.temporary_files.force_resync_remote', true); Bus::fake([AfterImportJob::class]); Queue::after(function (JobProcessed $event) { if ($event->job->resolveName() === ReadChunk::class) { $tempFile = $this->inspectJobProperty($event->job, 'temporaryFile'); // Should not exist locally after each chunk $this->assertFalse( $tempFile->existsLocally() ); } }); (new QueuedImport())->queue('import-batches.xlsx'); } public function test_can_define_middleware_method_on_queued_import() { try { (new QueuedImportWithMiddleware())->queue('import-batches.xlsx'); } catch (Throwable $e) { $this->assertEquals('Job reached middleware method', $e->getMessage()); } } public function test_can_define_retry_until_method_on_queued_import() { try { (new QueuedImportWithRetryUntil())->queue('import-batches.xlsx'); } catch (Throwable $e) { $this->assertEquals('Job reached retryUntil method', $e->getMessage()); } } public function test_can_define_max_exceptions_property_on_queued_import() { $maxExceptionsCount = 0; Queue::exceptionOccurred(function (JobExceptionOccurred $event) use (&$maxExceptionsCount) { if ($event->job->resolveName() === ReadChunk::class) { $maxExceptionsCount = $this->inspectJobProperty($event->job, 'maxExceptions'); } }); try { $import = new QueuedImportWithFailure(); $import->maxExceptions = 3; $import->queue('import-batches.xlsx'); } catch (Throwable $e) { $this->assertEquals('Something went wrong in the chunk', $e->getMessage()); } $this->assertEquals(3, $maxExceptionsCount); } } ================================================ FILE: tests/QueuedQueryExportTest.php ================================================ loadLaravelMigrations(['--database' => 'testing']); $this->withFactories(__DIR__ . '/Data/Stubs/Database/Factories'); factory(User::class)->times(100)->create([]); } public function test_can_queue_an_export() { $export = new FromUsersQueryExport(); $export->queue('queued-query-export.xlsx')->chain([ new AfterQueueExportJob(__DIR__ . '/Data/Disks/Local/queued-query-export.xlsx'), ]); $actual = $this->readAsArray(__DIR__ . '/Data/Disks/Local/queued-query-export.xlsx', 'Xlsx'); $this->assertCount(100, $actual); // 6 of the 7 columns in export, excluding the "hidden" password column. $this->assertCount(6, $actual[0]); } public function test_can_queue_an_export_with_batch_cache_and_file_store() { config()->set('queue.default', 'sync'); config()->set('excel.cache.driver', 'batch'); config()->set('excel.cache.illuminate.store', 'file'); config()->set('excel.cache.batch.memory_limit', 80); // Reset the cache settings $this->app->make(SettingsProvider::class)->provide(); $export = new FromUsersQueryExport(); $export->queue('queued-query-export.xlsx')->chain([ new AfterQueueExportJob(__DIR__ . '/Data/Disks/Local/queued-query-export.xlsx'), ]); $actual = $this->readAsArray(__DIR__ . '/Data/Disks/Local/queued-query-export.xlsx', 'Xlsx'); $this->assertCount(100, $actual); } public function test_can_queue_an_export_with_mapping() { $export = new FromUsersQueryExportWithMapping(); $export->queue('queued-query-export-with-mapping.xlsx')->chain([ new AfterQueueExportJob(__DIR__ . '/Data/Disks/Local/queued-query-export-with-mapping.xlsx'), ]); $actual = $this->readAsArray(__DIR__ . '/Data/Disks/Local/queued-query-export-with-mapping.xlsx', 'Xlsx'); $this->assertCount(100, $actual); // Only 1 column when using map() $this->assertCount(1, $actual[0]); $this->assertEquals(User::value('name'), $actual[0][0]); } public function test_can_queue_scout_export() { if (!class_exists('\Laravel\Scout\Engines\DatabaseEngine')) { $this->markTestSkipped('Laravel Scout is too old'); return; } $export = new FromUsersScoutExport(); $export->queue('queued-scout-export.xlsx')->chain([ new AfterQueueExportJob(__DIR__ . '/Data/Disks/Local/queued-scout-export.xlsx'), ]); $actual = $this->readAsArray(__DIR__ . '/Data/Disks/Local/queued-scout-export.xlsx', 'Xlsx'); $this->assertCount(100, $actual); // 6 of the 7 columns in export, excluding the "hidden" password column. $this->assertCount(6, $actual[0]); } } ================================================ FILE: tests/QueuedViewExportTest.php ================================================ loadLaravelMigrations(['--database' => 'testing']); $this->withFactories(__DIR__ . '/Data/Stubs/Database/Factories'); } public function test_can_queue_an_export() { $users = factory(User::class)->times(100)->create([]); $export = new SheetForUsersFromView($users); $export->queue('queued-view-export.xlsx')->chain([ new AfterQueueExportJob(__DIR__ . '/Data/Disks/Local/queued-view-export.xlsx'), ]); $actual = $this->readAsArray(__DIR__ . '/Data/Disks/Local/queued-view-export.xlsx', 'Xlsx'); $this->assertCount(101, $actual); } public function test_can_export_multiple_sheets_from_view() { /** @var Collection|User[] $users */ $users = factory(User::class)->times(300)->make(); $export = new FromViewExportWithMultipleSheets($users); $export->queue('queued-multiple-view-export.xlsx')->chain([ new AfterQueueExportJob(__DIR__ . '/Data/Disks/Local/queued-multiple-view-export.xlsx'), ]); $contents = $this->readAsArray(__DIR__ . '/Data/Disks/Local/queued-multiple-view-export.xlsx', 'Xlsx', 0); $expected = $users->forPage(1, 100)->map(function (User $user) { return [ $user->name, $user->email, ]; })->prepend(['Name', 'Email'])->toArray(); $this->assertEquals(101, sizeof($contents)); $this->assertEquals($expected, $contents); $contents = $this->readAsArray(__DIR__ . '/Data/Disks/Local/queued-multiple-view-export.xlsx', 'Xlsx', 2); $expected = $users->forPage(3, 100)->map(function (User $user) { return [ $user->name, $user->email, ]; })->prepend(['Name', 'Email'])->toArray(); $this->assertEquals(101, sizeof($contents)); $this->assertEquals($expected, $contents); } } ================================================ FILE: tests/TemporaryFileTest.php ================================================ defaultDirectoryPermissions = substr(sprintf('%o', fileperms($path)), -4); $filePath = $path . DIRECTORY_SEPARATOR . 'file-permissions'; touch($filePath); $this->defaultFilePermissions = substr(sprintf('%o', fileperms($filePath)), -4); @unlink($filePath); @rmdir($path); } public function test_can_use_default_rights() { $path = FileHelper::absolutePath('rights-test', 'local'); FileHelper::recursiveDelete($path); config()->set('excel.temporary_files.local_path', $path); $temporaryFileFactory = app(TemporaryFileFactory::class); $temporaryFile = $temporaryFileFactory->makeLocal(null, 'txt'); $temporaryFile->put('data-set'); $this->assertFileExists($temporaryFile->getLocalPath()); $this->assertEquals($this->defaultDirectoryPermissions, substr(sprintf('%o', fileperms(dirname($temporaryFile->getLocalPath()))), -4)); $this->assertEquals($this->defaultFilePermissions, substr(sprintf('%o', fileperms($temporaryFile->getLocalPath())), -4)); } public function test_can_use_dir_rights() { $path = FileHelper::absolutePath('rights-test', 'local'); FileHelper::recursiveDelete($path); config()->set('excel.temporary_files.local_path', $path); config()->set('excel.temporary_files.local_permissions.dir', 0700); $temporaryFileFactory = app(TemporaryFileFactory::class); $temporaryFile = $temporaryFileFactory->makeLocal(null, 'txt'); $temporaryFile->put('data-set'); $this->assertFileExists($temporaryFile->getLocalPath()); $this->assertEquals('0700', substr(sprintf('%o', fileperms(dirname($temporaryFile->getLocalPath()))), -4)); $this->assertEquals($this->defaultFilePermissions, substr(sprintf('%o', fileperms($temporaryFile->getLocalPath())), -4)); } public function test_can_use_file_rights() { $path = FileHelper::absolutePath('rights-test', 'local'); FileHelper::recursiveDelete($path); config()->set('excel.temporary_files.local_path', $path); config()->set('excel.temporary_files.local_permissions.file', 0600); $temporaryFileFactory = app(TemporaryFileFactory::class); $temporaryFile = $temporaryFileFactory->makeLocal(null, 'txt'); $temporaryFile->put('data-set'); $this->assertFileExists($temporaryFile->getLocalPath()); $this->assertEquals($this->defaultDirectoryPermissions, substr(sprintf('%o', fileperms(dirname($temporaryFile->getLocalPath()))), -4)); $this->assertEquals('0600', substr(sprintf('%o', fileperms($temporaryFile->getLocalPath())), -4)); } } ================================================ FILE: tests/TestCase.php ================================================ load($filePath); } /** * @param string $filePath * @param string|null $filename * @return File */ public function givenUploadedFile(string $filePath, ?string $filename = null): File { $filename = $filename ?? basename($filePath); // Create temporary file. $newFilePath = tempnam(sys_get_temp_dir(), 'import-'); // Copy the existing file to a temporary file. copy($filePath, $newFilePath); return new File($filename, fopen($newFilePath, 'r')); } /** * @param string $filePath * @param string $writerType * @param int|null $sheetIndex * @return array * * @throws \PhpOffice\PhpSpreadsheet\Exception */ protected function readAsArray(string $filePath, string $writerType, ?int $sheetIndex = null) { $spreadsheet = $this->read($filePath, $writerType); if (null === $sheetIndex) { $sheet = $spreadsheet->getActiveSheet(); } else { $sheet = $spreadsheet->getSheet($sheetIndex); } return $sheet->toArray(); } /** * @param \Illuminate\Foundation\Application $app * @return array */ protected function getPackageProviders($app) { return [ ExcelServiceProvider::class, ]; } /** * @param \Illuminate\Foundation\Application $app */ protected function getEnvironmentSetUp($app) { $app['config']->set('filesystems.disks.local.root', __DIR__ . '/Data/Disks/Local'); $app['config']->set('filesystems.disks.test', [ 'driver' => 'local', 'root' => __DIR__ . '/Data/Disks/Test', ]); $app['config']->set('database.default', 'testing'); $app['config']->set('database.connections.testing', [ 'driver' => 'mysql', 'host' => env('DB_HOST'), 'port' => env('DB_PORT'), 'database' => env('DB_DATABASE'), 'username' => env('DB_USERNAME'), 'password' => env('DB_PASSWORD'), ]); $app['config']->set('view.paths', [ __DIR__ . '/Data/Stubs/Views', ]); } /** * @param Job $job * @param string $property * @return mixed */ protected function inspectJobProperty(Job $job, string $property) { $dict = (array) unserialize($job->payload()['data']['command']); $class = $job->resolveName(); return $dict[$property] ?? $dict["\0*\0$property"] ?? $dict["\0$class\0$property"]; } /** * @param string $needle * @param string $haystack * @param string $message */ protected function assertStringContains(string $needle, string $haystack, string $message = '') { if (method_exists($this, 'assertStringContainsString')) { $this->assertStringContainsString($needle, $haystack, $message); } else { static::assertThat($haystack, new StringContains($needle, false), $message); } } /** * @param string $path */ protected function assertFileMissing(string $path) { if (method_exists($this, 'assertFileDoesNotExist')) { $this->assertFileDoesNotExist($path); } else { $this->assertFileNotExists($path); } } protected function assertRegex(string $pattern, string $string) { if (method_exists($this, 'assertMatchesRegularExpression')) { $this->assertMatchesRegularExpression($pattern, $string); } else { $this->assertRegExp($pattern, $string); } } } ================================================ FILE: tests/Validators/RowValidatorTest.php ================================================ validator = new RowValidator(app(Factory::class)); } public function test_format_rule_with_array_input() { $rules = ['rule1', 'rule2']; $result = $this->callPrivateMethod('formatRule', [$rules]); $this->assertEquals($rules, $result); } public function test_format_rule_with_object_input() { $rule = new \stdClass(); $result = $this->callPrivateMethod('formatRule', [$rule]); $this->assertEquals($rule, $result); } public function test_format_rule_with_callable_input() { $rule = function () { return 'callable'; }; $result = $this->callPrivateMethod('formatRule', [$rule]); $this->assertEquals($rule, $result); } public function test_format_rule_with_required_without_all() { $rule = 'required_without_all:first_name,last_name'; $result = $this->callPrivateMethod('formatRule', [$rule]); $this->assertEquals('required_without_all:*.first_name,*.last_name', $result); } public function test_format_rule_with_required_without() { $rule = 'required_without:first_name'; $result = $this->callPrivateMethod('formatRule', [$rule]); $this->assertEquals('required_without:*.first_name', $result); } public function test_format_rule_with_string_input_not_matching_pattern() { $rule = 'rule'; $result = $this->callPrivateMethod('formatRule', [$rule]); $this->assertEquals($rule, $result); } /** * Call a private function. * * @param string $name * @param array $args * @return mixed */ public function callPrivateMethod(string $name, array $args) { $method = new \ReflectionMethod(RowValidator::class, $name); $method->setAccessible(true); return $method->invokeArgs($this->validator, $args); } }