Byte Ebi's Logo

Byte Ebi 🍤

每天一小口,蝦米變鯨魚

匯入 Excel 或是 csv 檔案到 Laravel

使用 Laravel-excel 套件匯入 Excel 或是 csv 檔案,並寫入資料庫

Ray

在後台介面尚未建置完成,或是使用者有大量資料不想逐筆新增
而是要透過表單一次性匯入,這時候我們就會用到 Laravel-excel 這個套件
同時也分享一次匯入大量資料的時候遇到的坑

目前大部分開發者使用的套件都是:Laravel-excel
支援大多數匯入時候會使用的檔案格式,並且包含很多常用功能
官方文件寫的也是清楚並且有範例程式碼

安裝方式

使用指令安裝,會透過 auto-discovered 註冊進 ServiceProvider 和 Facade
目前最新版本是:Documentation 3.1

composer require maatwebsite/excel

如果需要加入自定義的 config 就執行指令產生config/excel.php

composer require maatwebsite/excel
php artisan vendor:publish --provider="Maatwebsite\Excel\ExcelServiceProvider" --tag=config

定義檔案讀入格式

因為 Laravel 自帶的 collection 類型太好用
所以我們也想把讀取到的表單資料轉成 collection 格式

首先要先建立資料要載入之後的物件定義,我們假設要匯入的是商品規格的 csv
所以就建立一個名為app/Imports/ProductImport.php的檔案

<?php

namespace App\Imports;

use Illuminate\Support\Collection;
use Maatwebsite\Excel\Concerns\ToCollection;

class ProductImport implements ToCollection
{
    public $collection;

    public function collection(Collection $collection)
    {
        $this->collection = $collection->transform(function ($row) {
            return collect([
                'name'          => $row[1],
                'main_image'    => $row[2],
                'author_name'   => $row[3],
                'material'      => $row[4],
                'specification' => $row[5],
                'description'   => $row[6],
            ]);
        });
    }
}

可以看到我們使用了ToCollection這個 interface
並且定義了collection和讀取到的表單內容對應

讀取 csv 檔案

在 controller 呼叫當初藉由 auto-discovered 註冊到 Facade 的Excel來讀取上傳的檔案

首先是建立物件ProductImport,接著透過 Facade 呼叫Excel套件並使用載入功能
載入之後透過 collection 的 shift() 方法去除第一列的 header 資料

public function uploadCsv(Request $request)
{
    $import = new ProductImport();
    Excel::import($import, $request->file('file'));
    $productCollection = $import->collection;
    $productCollection->shift();
}

如此就可以將上傳的 csv 檔案載入成 collection 來做後續操作

上傳測試

用 Postman 要測試,只要在 Body 頁籤選擇form-data類性之後
key可以名稱的後面下拉選單可以選擇 file 格式

postman body


常見問題

413 Payload Too Large

這個是來自 Nginx 的錯誤訊息,意思是:上傳的檔案太大了,Server 端忍不住
參考:413 Payload Too Large

解決方法以 PHP 開發環境為例

Nginx config 調整

預設是 2M,如果要傳超過的檔案就必須調整

打開nginx.conf在 server 段落內加上

client_max_body_size 10M;

如此一來請求才會傳入 nginx 內交給 php 執行後續內容

php.ini 調整

php.ini 是 php 的各項設定
其中我們要修改的就是upload_max_filesize這項,預設是 2M
將其設定的跟 nginx 一樣

upload_max_filesize = 10M;

如果是使用 Docker 建立環境,則在建立客製的php.ini檔案之後
將其掛載到容器內指定位置即可

- ./php/config/php.ini:/usr/local/etc/php/conf.d/php.ini

以上調整都完成之後,對有調整的容器下指令重啟或是重新載入設定檔
並進入容器內確認設定都有被載入
PHP 就用 phpinfo()、nginx 就用 nginx -T

Error: 1390 too many placeholders

當你成功把檔案上傳,並且開始執行程式
在途中把資料印出來檢查都照你預期的方式執行
於是你開始使用 query builder 組裝要執行的 SQL 語句

突然!報出一條錯誤訊息

1390 Prepared statement contains too many placeholders

這個是來自於 MySQL 的報錯,因為我們使用了:Prepared statement
而 Prepared statement 只支援 65535 (2^16-1) 個參數設定
如果你今天想要一口氣寫入超級多資料,不論是欄位還是筆數
只要超過這個數字,就會無法組裝進而報錯

知道發生的原因之後就很好想出解決辦法了!
那就是不要一次寫入太多資料…廢話

dont do it meme

言歸正傳,想要解決這個問題我們可以先將很大量的資料
比如說內含一萬筆商品內容的陣列切分成 500 筆一組的的多個陣列
一次執行 500 筆資料的寫入或修改,如此一來就可以避免 too many placeholders 的問題

在 PHP 中我們會使用 array_chunk 來分割

$productChunks = array_chunk($products, 500);

執行完之後會得到一個把原始資料每 500 筆切為一包的新陣列
接著就可以用迴圈把每一組的 500 筆取出來執行
原本是一個迴圈跑 10,000 筆資料,現在是一次 500 筆跑 20 次

這就是使用 Laravel-excel 套件來讀取 csv 並且寫入資料庫的方法
至於匯出檔案就留到下次有實際案例再來說明

最新文章

Category

Tag