วิธีใช้ภาษาไทยบน Laravel ด้วย laravel-dompdf โดยการเพิ่ม font ภาษาไทยลงไป

บทความนี้เกิดจากคำถามในกลุ่ม Laravel Thailand ซึ่งพบบ่อยพอสมควรว่าจะสร้าง PDF ภาษาไทยยังไง ก็เลยเขียนขึ้นมาซะเลย โดยในบทความนี้จะใช้ laravel-dompdf เป็นหลัก

วิธีการเพิ่ม font ภาษาไทยลง laravel-dompdf

เตรียมโปรเจคสำหรับทดสอบ

ส่วนนี้จะสอนลง laravel-dompdf กับสร้าง PDF แบบพังๆขึ้นมา 1 ตัวเพื่อใช้ทดสอบ ถ้าทำเป็นแล้วข้ามไปได้เลยครับ

  1. ก่อนอื่นก็ต้องลง Laravel แบบปกติก่อน (ไม่สอนนะ น่าจะทำกันเองเป็น) ในเวอร์ชันนี้ผมใช้ Laravel 5.4 แต่คิดว่าตระกูล 5.x สามารถใช้ได้เหมือนกันหมด
  2. สร้างโฟลเดอร์ storage/fonts และแก้ไข permission ให้สามารถเขียนได้ (โดยทั่วไปคือ 0777)
  3. ติดตั้ง laravel-dompdf โดยมีขั้นตอนดังนี้
    1. สั่ง
    2. แก้ไขไฟล์ config/app.php โดยเพิ่ม

      ลงไปในส่วนของ providers

      และเพิ่ม

      ลงในส่วนของ aliases

  4. สร้างหน้าทดลองสำหรับใช้ PDF (ในขั้นนี้ต้องประยุกต์เอาเองตามความต้องการ หากมีอยู่แล้วข้ามไปข้อ 4 ได้เลย)
    1. สร้าง view สำหรับทดสอบ โดยในตัวอย่างของผมคือ invoice.blade.php ใน resources/views/pdf โดยมีเนื้อหาดังนี้

    2. สร้าง route หรือ controller ในการสร้าง pdf (เนื่องจากเป็นตัวอย่าง เลยทำเป็น route ง่ายๆ)

      ในบรรทัดที่ 8 จำเป็นต้องใส่ @ นำหน้า เพื่อปิด error บางตัว ที่อาจจะเกิดจากการหา fonts ไม่เจอ

    3. หลังจากที่ทำตามข้างบนแล้วจะพบว่าเมื่อเข้าไปภาษาไทยยังเป็นเครื่องหมายตกใจอยู่ เพราะข้างบนนั้นยังไม่ได้ทำให้รองรับภาษาไทย
      laravel-dompdf ยังไม่รองรับภาษาไทย

ปรับ laravel dompdf ให้สามารถใช้งาน font ภาษาไทยได้

ในขั้นนี้จะเป็นขั้นตอนจริงๆ เพื่อทำให้ laravel-dompdf รองรับภาษาไทยในการ render ต้องทำตามขั้นตอนต่อไปนี้เพื่อให้รองรับภาษาไทย โดยขั้นตอนนี้เป็นแบบง่าย ซึ่งในการสร้าง pdf ครั้งแรกสุดอาจจะทำให้ช้าได้ โดย font ไทยในครั้งนี้ผมเลือกใช้ THSarabunNew ครับ (ถ้าอยากดาวโหลดมาทำการทดสอบ สามารถดาวโหลดได้จากที่นี่)

  1. ให้สร้างโฟลเดอร์ชื่อ fonts ใน public และนำ font ทั้งหมดไปใส่ไว้ ดังรูป
  2. แก้ไขในส่วนของ views โดยเพิ่ม tag meta ลงไปใน head ของ invoice.blade.php จะได้เป็น
  3. เพิ่ม tag style เพื่อประกาศ fonts แบบ runtime และบอกให้ทั้งเอกสารใช้ fonts นั้น วิธีการคือให้แก้ไข  invoice.blade.php ให้เป็นดังนี้

    โดยส่วนของการประกาศ fonts คือบรรทัดที่ 6 – 10 ส่วนสำคัญคือบรรทัดที่ 10 ที่เป็นการบอกว่า fonts อยู่ที่ไหน

    และส่วนที่บอกว่า ให้ใช้ fonts ทั้งเอกสารคือบรรทัดที่ 12 – 14 โดยเราสามารถแก้ไขได้เหมือน css ปกติเลยว่าส่วนไหนใช้ fonts ไหน

    โดยเมื่อเราทำตามขั้นตอนด้านบนแล้วจะได้ดังภาพ ใส่ font ภาษาไทยให้ laravel-dompdf แล้ว สำเร็จบางส่วน

    ปัญหาที่พบบ่อย

    1. ในขั้นตอนนี้หากท่านพบปัญหาหน้าเว็บโหลดนานกว่าเดิมมาก (หลัก 10 วินาทีขึ้นไป) ให้เลื่อนไปดูวิธีแก้ไขด้านล่างก่อน ค่อยกลับมาทำต่อครับ
    2. เจอ Error ประมาณว่า Failed to open stream ดังภาพนี้ ให้กลับสร้าง folder ชื่อ fonts ใน storage ก่อนนะครับ ผมเขียนไว้แล้ว ข้อ 2 ด้านบน
      หากพบ error failed to open stream ให้ไปสร้าง folder ชื่อ fonts ใน storage ก่อนนะครับ
  4. จากข้อก่อนหน้าจะเห็นได้ว่า เราได้ว่ามีบางส่วนมาเป็นภาษาไทยแล้ว แต่บางส่วนยังเป็นเครื่องหมายตกใจอยู่ สาเหตุเป็นเพราะตอนประกาศ font ในข้อก่อนหน้านั้น เราประกาศว่าใช้เฉพาะกับข้อความปกติเท่านั้น (บรรทัด 7-8 ในข้อก่อนหน้า) แต่ h1 นั้นจะให้ข้อความที่เป็นตัวหนา (font-weight: bold;) ดังนั้นเราจึงต้องประกาศเพิ่มให้ครบครับ จึงต้องแก้ invoice.blade.php เป็นดังนี้

    ทีนี้พอเราสร้าง pdf ใหม่จะเห็นว่า สามารถใช้ภาษาไทยได้สมบูรณ์แล้ว laravel-dompdf รองรับภาษาไทยสมบูรณ์แล้ว

ข้อควรระวัง

ในการสร้าง pdf ครั้งแรกมันจะสร้างได้ช้าพอสมควรเนื่องจากมันต้องไป download font มา (จากเครื่องตัวเองนั่นแหละ) จากนั้นมาแปลง fonts เป็น format ของมัน และ cache ลงใน storage/fonts ทำให้ครั้งแรกช้ากว่าปกติพอสมควร แต่หลังจากนั้นจะความเร็วปกติ (เพราะ cache ไว้แล้ว)

 วิธีแก้ปัญหากรณีใส่ font-face แล้วสร้าง PDF นานกว่าปกติ

สาเหตุที่พบเจอบ่อยคือ php artisan serv ในการทดสอบ ซึ่งตัว php artisan serv นั้นมีข้อจำกัดคือมันสามารถประมวลผล request ได้ทีละอันเท่านั้น ดังนั้นเมื่อมันยังสร้าง PDF ไม่เสร็จ มันก็ไม่สามารถให้บริการอย่างอื่นได้ ทำให้ไม่สามารถดาวโหลด fonts ที่ถูกอ้างถึงได้ วิธีแก้ไขมี 3 แบบคือ

  1. สำหรับตระกูล unix ทั้งหมด (mac OSX, Linux, FreeBSD, Unix และอื่นๆ ที่ไม่ใช่ windows) แนะนำให้ใช้วิธีนี้เพราะมีประสิทธิภาพมากกว่า คือให้แก้การอ้างถึง font จากเดิมเป็น http ให้เป็น local file แทน โดยเปลี่ยนจากคำสั่ง asset เป็น public_path และเติม file:// ลงไปหน้าสุดของ url เช่น จากเดิม

    ให้แก้เป็น

     
  2. ไม่แนะนำเท่าไหร่ แต่ใช้แก้ปัญหาเฉพาะหน้าได้ คือเอา fonts ไปฝากไว้ที่อื่น (วิธีนี้ถ้าใช้จริง อาจจะมีปัญหาหาก network ช้าไม่สามารถโหลด font ได้ ถึงแม้มันจะ cache ไว้ช่วยให้โหลดแค่ครั้งแรกครั้งเดียว แต่ถ้ามันโหลดไม่สำเร็จตั้งแต่แรกก็ไม่มี cache ให้นะครับ)
  3. วิธีที่ 3 สามารถใช้ได้ทั่ว คือหา Web Server มาทำแทนซะ จะเป็น XAMPP หรือใช้ Homestead ก็ได้ หรือแม้แต่ IIS ของ windows ก็ได้ เลือกได้ตามสบายใจ แต่ไม่มีสอนครับ งมเอาเอง บายจ้า

หากทำตามด้านบนแล้วยังไม่หายให้ลองใช้พวก OPCache มาช่วยเพื่อปรับปรุงประสิทธิภาพของ PHP ดู ซึ่งจะทำให้ PHP เร็วขึ้นเยอะและกิน CPU น้อยลงด้วย

แก้ปัญหา Laravel Error บน Appservhosting

พอดีวันนี้เจอปัญหาพิเศษ ที่เกิดขึ้นบน Share Host บางตัว (ในตัวอย่างนี้คือ Appservhosting แต่เชื่อว่าบาง Host ก็น่าจะเป็นเช่นกัน) จากโพสนี้  ซึ่งผมเห็นว่าเป็นเคสที่น่าสนใจมาก เลยเอามาแบ่งปันกันครับ

ปัญหาที่เกิดขึ้นคือ เมื่ออับโหลดขึ้นไปแล้ว Laravel ไม่ทำงาน โดยการตรวจสอบเบื้องต้นคือ

  • ผมได้ตรวจเวอร์ชัน PHP และ PHP Extension ที่ Laravel ต้องการแล้ว ซุึ่งปกติดีครับ
  • Permission ของ storage และ bootstrap/cache สามารถเขียนได้ปกติ

ต่อไปคือข้อบ่งชี้ว่าเป็นสาเหตุเดียวกัน

  • ผมได้ลองแก้ไขไฟล์ index.php และไฟล์อื่น เพื่อใส่ die(‘a’); เข้าไปเรื่อยๆ เพื่อหาว่ามันพังที่บรรทัดไหนกันแน่ แต่ปรากฏว่า เลื่อนบรรทัดไปมา เหมือนบรรทัดที่พังเปลี่ยนไปเรื่อยๆ (อันนี้เงิบมากๆ เพราะผมขยับไป 1 บรรทัดมันพัง เลยลงไปใส่ใน function ที่พัง แต่ผ่านตลอด แล้ววนกลับมา die ที่เดิมมันไม่พังเฉยเลย)
  • Error Logs ไม่มีเขียนอะไรเลย (อาจจะมีเขียน แต่ผมใช้ .htaccess เปลี่ยนที่อยู่ logs มัน เพราะที่เก่าไปอยู่ในที่ๆผมเข้าถึงไม่ได้ เพราะ share host ไม่เปิด)
  • ผมใช้  curl -v url > /dev/null เพื่อดูว่ามันมีอะไรที่ผมไม่เห็นไหม ประกฏว่ามันไม่ได้่ error 500 แต่มันตัด connection ไปดื้อๆ เลย ข้อนี้เลยค่อนข้างเดาได้ว่าน่าจะเป็นที่ระดับ php ไม่ได้เป็นที่ระดับ Code

สรุปผลการคาดเดาของผมคือ น่าจะเป็นปัญหาที่น่าจะเคยเจอกันบ่อยๆคือ OP Cache ของ PHP น่าจะเกิดปัญหา ซึ่งในที่นี้คือ Zend OPcache v7.0.6-dev ซึ่งวิธีแก้ปัญหาคือปิดมันครับ โดยการเปิด .htaccess ขึ้นมา แล้วใส่บรรทัดนี้ลงไปครับ

โดยในสมัยก่อนนั้น ผมเคยใช้ XCache ก็มีปัญหาคล้ายๆกัน แต่คนละแบบ ของ XCache เมื่อก่อน มันไป Cache routes.php ของคนอื่นมา มันเลยพัง ซึ่งวิธีปิดคล้ายๆกันคือใส่บรรทัดนี้ลงไป .htaccess