วิธีเลือก package export Excel ใน PHP ให้เหมาะกับงาน

รายงานเล็กที่ต้องจัดหน้าตาใช้ Laravel Excel หรือ PhpSpreadsheet ไฟล์หลายหมื่นแถวขึ้นไปใช้ OpenSpout ที่เขียนทีละแถว ส่วนไฟล์ให้ระบบอื่นอ่านใช้ CSV ผ่าน league/csv และเลิกใช้ PHPExcel ได้แล้ว

เผยแพร่
เวลาอ่าน
5 นาที
ในโพสต์นี้ 6 หัวข้อ
  1. ไฟล์ xlsx กับ csv ต่างกันตรงที่ csv เก็บแค่ข้อความ
  2. package แบ่งเป็นสองแบบ เก็บทั้งไฟล์ไว้ใน memory หรือเขียนทีละแถว
  3. แต่ละ package ทำอะไรได้ ณ ตุลาคม 2569
  4. ตัวเลขจากการลองเขียน 100,000 แถว
  5. เลือกตามงานที่ทำ
  6. งานจริงมักเริ่มจากตัวหนึ่ง แล้วค่อยเพิ่มอีกตัว

ระบบหลังร้านแทบทุกระบบมีปุ่ม "Export Excel" สักที่ ตอนเริ่มทำเรามักหยิบ package ที่คุ้นมือมาใช้ก่อน แล้วค่อยมาเจอทีหลังว่าไฟล์ใหญ่แล้ว memory หมด เลขบัตรประชาชนกลายเป็น 1.23E+12 หรือเปิด CSV แล้วภาษาไทยอ่านไม่ออก โพสต์นี้เล่าว่าแต่ละ package ทำงานต่างกันตรงไหน แล้วงานแบบไหนควรใช้ตัวไหน

ไฟล์ xlsx กับ csv ต่างกันตรงที่ csv เก็บแค่ข้อความ

CSV เป็นไฟล์ข้อความธรรมดา หนึ่งบรรทัดคือหนึ่งแถว คั่นช่องด้วย comma ไม่มีสี ไม่มีตัวหนา ไม่มีหลายชีท และไม่รู้ว่าช่องไหนเป็นวันที่หรือตัวเลข โปรแกรมที่เปิดจะเดาเอาเอง ข้อดีคือเล็ก เขียนเร็ว และระบบไหนก็อ่านได้

XLSX เป็นไฟล์ zip ที่ข้างในมี XML หลายไฟล์ เก็บได้ทั้งชนิดข้อมูลของแต่ละช่อง รูปแบบตัวเลข สี เส้นขอบ หลายชีท สูตร และกราฟ เหมาะกับรายงานที่คนจะเปิดอ่าน

ถ้าคนรับไฟล์เป็นคน ส่วนใหญ่ควรเป็น xlsx ถ้าคนรับเป็นโปรแกรม เช่นไฟล์ส่งธนาคาร หรือ feed ให้ระบบอื่น CSV มักเหมาะกว่า แต่ CSV ภาษาไทยมีเรื่อง encoding ต้องดูให้ดี (อ่านต่อที่ CSV ภาษาไทยเปิดใน Excel แล้วอ่านไม่ออก)

package แบ่งเป็นสองแบบ เก็บทั้งไฟล์ไว้ใน memory หรือเขียนทีละแถว

ความต่างที่มีผลที่สุดคือวิธีเขียนไฟล์

สองวิธีเขียนไฟล์ Excel เก็บทั้งไฟล์ไว้ใน memory หรือเขียนทีละแถว

PhpSpreadsheet ต้องมีทุก cell อยู่ใน memory ก่อนเขียนไฟล์ ส่วน OpenSpout เขียนแถวลงไฟล์แล้วก็ลืมแถวนั้นไปเลย

  • เก็บทั้งไฟล์ไว้ใน memory (PhpSpreadsheet และ Laravel Excel ที่ใช้ PhpSpreadsheet อยู่ข้างใน) ทำได้ทุกอย่าง แก้ช่องไหนก็ได้ทุกเวลา ใส่สูตร กราฟ conditional formatting และปรับความกว้างคอลัมน์อัตโนมัติ แต่เอกสารของ PhpSpreadsheet บอกว่าใช้ memory ราว 1 KB ต่อ cell (1.6 KB บน PHP 64-bit) รายงาน 50,000 แถว 40 คอลัมน์ก็เป็นสองล้าน cell แล้ว
  • เขียนทีละแถว (OpenSpout, league/csv) ส่งแถวลงไฟล์ทันทีแล้วไม่ต้องจำ ไฟล์ใหญ่แค่ไหน memory ก็แทบเท่าเดิม แลกกับการย้อนกลับไปแก้แถวที่เขียนไปแล้วไม่ได้ และบางอย่างทำไม่ได้เลย เช่นปรับความกว้างคอลัมน์ตามข้อมูล

แต่ละ package ทำอะไรได้ ณ ตุลาคม 2569

Package เวอร์ชันล่าสุด เขียนแบบ เหมาะกับ
PHPExcel 1.8.2 (หยุดพัฒนาแล้ว) memory ไม่ควรใช้แล้ว
PhpSpreadsheet 5.10 memory รายงานที่ต้องการทุกฟีเจอร์
Laravel Excel (maatwebsite/excel) 4.0 memory งาน Laravel ทั่วไป
OpenSpout 5.13 ทีละแถว ไฟล์ใหญ่ ยังจัด style ได้
league/csv 9.29 ทีละแถว CSV ทุกแบบ
spatie/simple-excel 3.11 ทีละแถว อ่านเขียนแบบง่าย ๆ
fast-excel 5.18 ทีละแถว export collection ง่าย ๆ
fast-excel-writer 6.19 ทีละแถว ไฟล์ใหญ่ที่ยังอยากได้ autosize
xlswriter (PHP extension) 3.0 ทั้งสองแบบ ไฟล์ใหญ่มาก ติดตั้ง extension ได้

ทีละตัวสั้น ๆ

  • PHPExcel ประกาศเลิกพัฒนาตั้งแต่ปี 2017 และ archive repo ไปเมื่อต้นปี 2019 ฐานข้อมูลช่องโหว่ของ Packagist ยังแสดงว่าเวอร์ชันสุดท้ายมีช่องโหว่ราว 23 รายการ ทั้ง XXE, SSRF และ XSS ซึ่งจะไม่มีใครแก้อีก ระบบเก่าที่ยังใช้อยู่ควรย้ายไป PhpSpreadsheet ซึ่งเป็นตัวต่อจากทีมเดียวกัน
  • PhpSpreadsheet คือเครื่องยนต์ที่หลายตัวในตารางนี้ใช้อยู่ข้างใน ทำได้ครบที่สุด ทั้งอ่านและเขียน xlsx, xls, ods, csv มีตัวคำนวณสูตรในตัว ปีนี้มีช่องโหว่ออกมาหลายตัว จึงควรอยู่บนเวอร์ชันที่ยังได้แพตช์ ดูได้จาก SECURITY.md ของโปรเจกต์
  • Laravel Excel ห่อ PhpSpreadsheet ให้เขียนเป็นคลาส export หนึ่งคลาสต่อหนึ่งไฟล์ ดึงจาก query, ส่งเข้า queue, ดาวน์โหลด ได้ในบรรทัดเดียว เวอร์ชัน 4.0 ออกเมื่อสิงหาคม 2569 ต้องใช้ PHP 8.3 และ Laravel 12 ขึ้นไป ส่วน 3.1 ตอนนี้รับแค่แก้ช่องโหว่ รายละเอียดอยู่ในวิธีเขียน export ด้วย Laravel Excel
  • OpenSpout เป็นตัวต่อจาก box/spout ที่เลิกพัฒนาไปแล้ว FAQ ของโปรเจกต์บอกว่าใช้ memory ไม่ถึง 3 MB ไม่ว่าไฟล์จะใหญ่แค่ไหน จัดตัวหนา สีพื้น รูปแบบตัวเลข merge cell และ freeze แถวหัวตารางได้ แต่ไม่มี autosize, conditional formatting และกราฟ เวอร์ชันล่าสุดต้องใช้ PHP 8.4 ขึ้นไป วิธีใช้อยู่ในวิธีเขียน Excel ไฟล์ใหญ่ด้วย OpenSpout
  • league/csv สำหรับ CSV อย่างเดียว เขียนตรงลง stream มีตัวช่วยใส่ BOM และกัน formula injection ให้
  • spatie/simple-excel กับ fast-excel ใช้ OpenSpout อยู่ข้างใน ให้ API สั้นกว่า เหมาะกับ export ข้อมูลเรียบ ๆ ที่ไม่ต้องจัดหน้าตามาก fast-excel ยังผูกกับ OpenSpout 4 อยู่ จึงขยับไป OpenSpout 5 ไม่ได้
  • fast-excel-writer กับ xlswriter เขียนทีละแถวเหมือนกันแต่มี autosize และกราฟด้วย xlswriter เป็น extension ภาษา C ต้องติดตั้งผ่าน PECL หรือ PIE ซึ่งบน shared hosting ส่วนใหญ่ทำไม่ได้

ตัวเลขจากการลองเขียน 100,000 แถว

เราลองเขียนไฟล์ 100,000 แถว 10 คอลัมน์ มีข้อความไทยปนตัวเลข บนเครื่องพัฒนาเครื่องเดียว PHP 8.4 รันรอบเดียว ตัวเลขจึงใช้เทียบกันคร่าว ๆ ไม่ใช่ benchmark

Package เวลา Memory สูงสุด
league/csv (CSV) 0.27 วินาที 2 MB
OpenSpout 5.13 2.5 วินาที 2 MB
fast-excel-writer 6.19 2.8 วินาที 4 MB
PhpSpreadsheet 5.10 11.9 วินาที 683 MB

PhpSpreadsheet ช้ากว่าราวห้าเท่า และใช้ memory มากกว่าหลายร้อยเท่า ไฟล์ขนาดนี้ยังรันจบได้ถ้าตั้ง memory_limit ไว้สูง แต่ถ้าโตเป็นหลายแสนแถว หรือมีคนกด export พร้อมกันหลายคน เซิร์ฟเวอร์จะเริ่มรับไม่ไหว เรื่อง memory มีรายละเอียดอีกมาก อ่านต่อได้ที่ export Excel แล้ว memory หมด

เลือกตามงานที่ทำ

งาน ใช้
รายงานไม่กี่พันแถว ต้องมีหัวตารางสี ยอดรวม หลายชีท ใน Laravel Laravel Excel
รายงานที่ต้องมีสูตร กราฟ หรือ conditional formatting PhpSpreadsheet (ตรง ๆ หรือผ่าน Laravel Excel)
export หลายหมื่นถึงหลายแสนแถว OpenSpout หรือ spatie/simple-excel
ไฟล์ส่งธนาคาร SAP หรือระบบอื่น league/csv
นำเข้าไฟล์ Excel ขนาดใหญ่ OpenSpout reader หรือ spatie/simple-excel ที่อ่านทีละแถว
ระบบเก่าบน PHPExcel ย้ายไป PhpSpreadsheet ก่อน แล้วค่อยเลือกตามงาน

ระบบเดียวใช้สองตัวคู่กันได้ เช่น ใช้ Laravel Excel เป็นหลัก แล้วสลับเฉพาะรายงานที่ใหญ่มากไปเขียนด้วย OpenSpout

งานจริงมักเริ่มจากตัวหนึ่ง แล้วค่อยเพิ่มอีกตัว

อย่างตอนที่เราทำระบบรายงานจาก SAP ที่ส่ง Excel ให้ทุกฝ่ายตามเวลา ก็เริ่มจาก Laravel Excel ทุกรายงาน เพราะต้องจัดหน้าตาให้เหมือนรายงานเดิมของลูกค้า จนรายงานบางตัวโตเป็นหลายหมื่นแถวกว่าร้อยคอลัมน์ ตัวหนึ่งใช้ memory ราว 2.6 GB และใช้เวลา 210 วินาที พอเปลี่ยนเฉพาะตัวนั้นไปเขียนด้วย OpenSpout ก็เหลือราว 380 MB และ 38 วินาที ที่เหลือยังใช้ Laravel Excel ต่อ เพราะเป็นรายงานเล็กที่ต้องการ conditional formatting

กติกาที่ใช้ได้ดีคือ เริ่มจากตัวที่จัดหน้าตาได้ครบ แล้ววัด memory ของรายงานจริงไว้ ถ้ารายงานไหนเกินราวหมื่นแถว หรือ memory ใกล้ 1 GB ค่อยย้ายรายงานนั้นไปเขียนทีละแถว

ตอนถัดไปของซีรีส์ Export Excel และ CSV จาก Laravel

อ่านต่อ

ถูกอ้างถึงใน

ล่าสุด

ดูทั้งหมด →