วิธีใช้ภาษาไทยบน 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. สั่ง
      composer require barryvdh/laravel-dompdf
    2. แก้ไขไฟล์ config/app.php โดยเพิ่ม
      Barryvdh\DomPDF\ServiceProvider::class,

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

      และเพิ่ม

      'PDF' => Barryvdh\DomPDF\Facade::class,
      

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

  4. สร้างหน้าทดลองสำหรับใช้ PDF (ในขั้นนี้ต้องประยุกต์เอาเองตามความต้องการ หากมีอยู่แล้วข้ามไปข้อ 4 ได้เลย)
    1. สร้าง view สำหรับทดสอบ โดยในตัวอย่างของผมคือ invoice.blade.php ใน resources/views/pdf โดยมีเนื้อหาดังนี้
      <html>
      <head>
      </head>
      <body>
      <h1>ใบแจ้งหนี้สำหรับ คุณ{{ $name }}</h1>
      ขอขอบคุณในการสั่งซื้อ
      </body>
      </html>
    2. สร้าง route หรือ controller ในการสร้าง pdf (เนื่องจากเป็นตัวอย่าง เลยทำเป็น route ง่ายๆ)
      <?php
      
      Route::get('/pdf', function () {
          $data = [
              'name'=>'อะไรสักอย่าง ไม่รู้นามสกุลอะไร'
          ];
          $pdf = PDF::loadView('pdf.invoice', $data);
          return @$pdf->stream();
      });
      

      ในบรรทัดที่ 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 จะได้เป็น
    <html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
    </head>
    <body>
    <h1>ใบแจ้งหนี้สำหรับ {{ $name }}</h1>
    ขอขอบคุณในการสั่งซื้อ
    
    </body>
    </html>
  3. เพิ่ม tag style เพื่อประกาศ fonts แบบ runtime และบอกให้ทั้งเอกสารใช้ fonts นั้น วิธีการคือให้แก้ไข  invoice.blade.php ให้เป็นดังนี้
    <html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
        <style>
            @font-face {
                font-family: 'THSarabunNew';
                font-style: normal;
                font-weight: normal;
                src: url("{{ public_path('fonts/THSarabunNew.ttf') }}") format('truetype');
            }
    
            body {
                font-family: "THSarabunNew";
            }
        </style>
    </head>
    <body>
    <h1>ใบแจ้งหนี้สำหรับ {{ $name }}</h1>
    ขอขอบคุณในการสั่งซื้อ
    
    </body>
    </html>

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

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

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

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

    1. เจอ Error ประมาณว่า Failed to open stream ดังภาพนี้ ให้กลับสร้าง folder ชื่อ fonts ใน storage ก่อนนะครับ ผมเขียนไว้แล้ว ข้อ 2 ด้านบน
      หากพบ error failed to open stream ให้ไปสร้าง folder ชื่อ fonts ใน storage ก่อนนะครับ
    2. ถ้าทำตามด้านบนแล้วยังไม่ได้ ให้ลองเปลี่ยนจาก public_path ไปใช้เป็น asset แทนครับ (แต่แบบ asset จะใช้กับ php artisan serve ไม่ได้)
  4. จากข้อก่อนหน้าจะเห็นได้ว่า เราได้ว่ามีบางส่วนมาเป็นภาษาไทยแล้ว แต่บางส่วนยังเป็นเครื่องหมายตกใจอยู่ สาเหตุเป็นเพราะตอนประกาศ font ในข้อก่อนหน้านั้น เราประกาศว่าใช้เฉพาะกับข้อความปกติเท่านั้น (บรรทัด 7-8 ในข้อก่อนหน้า) แต่ h1 นั้นจะให้ข้อความที่เป็นตัวหนา (font-weight: bold;) ดังนั้นเราจึงต้องประกาศเพิ่มให้ครบครับ จึงต้องแก้ invoice.blade.php เป็นดังนี้
    <html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
        <style>
            @font-face {
                font-family: 'THSarabunNew';
                font-style: normal;
                font-weight: normal;
                src: url("{{ public_path('fonts/THSarabunNew.ttf') }}") format('truetype');
            }
            @font-face {
                font-family: 'THSarabunNew';
                font-style: normal;
                font-weight: bold;
                src: url("{{ public_path('fonts/THSarabunNew Bold.ttf') }}") format('truetype');
            }
            @font-face {
                font-family: 'THSarabunNew';
                font-style: italic;
                font-weight: normal;
                src: url("{{ public_path('fonts/THSarabunNew Italic.ttf') }}") format('truetype');
            }
            @font-face {
                font-family: 'THSarabunNew';
                font-style: italic;
                font-weight: bold;
                src: url("{{ public_path('fonts/THSarabunNew BoldItalic.ttf') }}") format('truetype');
            }
    
            body {
                font-family: "THSarabunNew";
            }
        </style>
    </head>
    <body>
    <h1>ใบแจ้งหนี้สำหรับ {{ $name }}</h1>
    ขอขอบคุณในการสั่งซื้อ
    
    </body>
    </html>

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

ข้อควรระวัง

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

ข้อแนะนำอื่นๆ

  1. หากนำไปใช้จริง ตัวเซิฟเวอร์ควรจะลง OPCache ไว้ด้วย เพื่อความรวดเร็วในการสร้าง PDF

แก้ปัญหา 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 ขึ้นมา แล้วใส่บรรทัดนี้ลงไปครับ

php_flag opcache.enable Off

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

php_flag xcache.cacher Off