รีวิว Lumen เบื้องต้น

Lumen เป็น Framework ที่ไวอย่างน่าแปลกใจ ยิ่งเห็นยังใช้ Symfony แล้วยิ่งแปลกใจ ตอนนี้เลยคิดได้อย่างเดียว Laravel เป็น Full framework ที่ระบบมันใหญ่จริงๆ

พอเป็น Lumen ที่ย่อหลายๆ อย่างเขามา ไม่มีระบบ Route ระบบ Request แล้วโยกมาใส่ใน Foundation Application แล้วระบบเบาขึ้นจม รวมถึงตัดระบบ Facade ทิ้ง ซึ่งก็ดีเพราะไม่จำเป็นต้องใช้แล้ว เพราะตอนนี้ Lumen สามารถทำ Dependency Injection ในระดับ Method ได้แล้ว (เขียน unittest ระบบ controller สบายล่ะ)

สมัยก่อนตอน Laravel4 ออกใหม่ ผมว้าวกับ Facade เอามากๆ แต่พอผมยิ่งศึกษา Design pattern หลายๆ แบบแล้วกลับพบว่า Facade ทำให้โปรแกรมเมอร์นิสัยเสีย เล่นเอาว่าคนใช้ L4 หลายคนไล่ไม่เป็นด้วยซ้ำว่า Class แท้จริงหลัง Facade คืออะไร มันสะดวกไปครับ คุม Code quality ยากมาก

นอกเรื่องไป กลับมาต่อ Lumen นอกจากปิด Facade แล้ว Eloquent ก็ไม่เปิดแบบ default ด้วย ซึ่งผมว่าดีนะ เพราะ Eloquent ก็เป็นอีกตัวที่ตอนแรกผมว้าว แต่ตอนนี้ผมพบว่าการใช้ ORM “ย้ำว่า ORM นั้นหมายถึงทุกตัวที่บนโลกนี้มี”

ตอนนี้ ORM ยังขาดสิ่งที่เรียกว่า Data layer ซึ่งทำให้ระบบเรียกใช้ Storage เกินความจำเป็น ยกตัวอย่าง Table posts มีลูกเป็น Table comments แต่นอนว่า ORM เรียก posts has many comments ได้อย่างสบาย แต่หลังจากนั้น comments เรียก posts ตรงๆ ไม่ได้ เพราะ ORM ทำให้ data กลายเป็น Hierarchy เมื่อจะมีการเรียกใช้ในลักษณะย้อนกลับ ทางเลือกมีสองอย่าง หนึ่ง query ใหม่ หรือสองก็ต้องโยน data ทั้งก้อนนั้นแหละเข้าไป ซึ่งนั้นคือการโยน posts ทั้งก้อนเข้าไป ทำให้การคุม story ของ method หรือ class นั้นๆ มันผิดไปเลย เช่น คุณอยากได้ระบบกดไลค์ comments แต่คุณดันโยน posts เข้าไปพร้อม comment id เพียงเพราะเดี๋ยวคุณต้องทำการ touch post นั้นๆ ให้อันดับมันสูงขึ้น

….. ผมว่าผมนอกเรื่องอีกล่ะ เอาเป็นว่าเรื่องพวกนี้อาจแก้ไขได้ด้วย design pattern ที่ดีกว่านี้แต่ผมยังหาไม่เจอ ดังนั้นสำหรับ Lumen ที่ปิด Eloquent ไว้ผมมองว่าดี เพราะผมอาจไม่ใช้ หรือไม่ก็ PDO ดิบๆ แทน

นอกนั้น Lumen สำหรับคนที่ใช้ Laravel มา ผมบอกได้เลยว่าปรับตัวน้อยมาก Config บางตัวอาจหายไป แต่นอกนั้นถือว่าดี และทำงานได้ครบไม่แพ้ Laravel เลย

ส่วนข้อเสียที่อยากติคือ ตอนนี้คุณใช้ Lumen ได้ระบบ root path เท่านั้น ไม่สามารถใช้ใน subdirectory ได้ ซึ่งสำหรับคนที่จำเป็นจริงๆ ผมแนะนำ ให้ทำดังนี้ คือทำการ extend Laravel\Lumen\Application แล้วแก้ method getPathInfo() ในคลาสใหม่ให้เป็นรูปแบบนี้

    /**
     * Get the current HTTP path info.
     *
     * @return string
     */
    public function getPathInfo()
    {
        return str_replace(dirname($_SERVER['SCRIPT_NAME']), '', parent::getPathInfo());
    }

แล้วทำการแก้ /bootstrap/app.php

$app = new Laravel\Lumen\Application;

ให้ไปใช้ class ใหม่ที่คุณสร้าง เท่านี้ก็จะทำงานได้เหมือนอย่างที่ Laravel เป็นแล้วครับ ส่วนใครคิดว่ามันเป็นบั๊ก แนะนำอ่าน https://github.com/laravel/lumen/pull/4 และ https://github.com/laravel/lumen-framework/pull/21 จะพบว่าเขาไม่คิดจะแก้ เพราะนี้คือความตั้งใจนะครับ

สรุป ณ ตอนนี้ Lumen น่าใช้ดีครับ ถ้าไม่นับว่า Phalcon2 พึ่งออก และคุณไม่ได้เช้า VPS เลยไม่สามารถติด Phalcon ได้ ผมแนะนำให้ใช้ Lumen ครับ

อย่าทำตัวเป็นทาสเฟรมเวิร์ค Framework-Argostic

ในยุคสมัยนี้ทุกคนคงเหมือนกันว่ามันไม่มีใครแล้วที่จะเขียนโค้ดเริ่มจากศูนย์เลย (Make it from scratch.) จะหันซ้ายหันขวา ไม่ว่าจะเป็น developer ในภาษาใดๆ ก็มักจะเขียนอิง framework อย่างน้อยตัวหนึ่ง หรือบางทีอาจถึงขั้นปวารณาระตัวเองเป็นสาวกกันเลยทีเดียว ผมเองก็ด้วยคนหนึ่งเช่นกัน ซึ่งสำหรับผมคือ Laravel นั้นเอง

แล้วปัญหาที่มันเกิดขึ้นตามมาคือ คุณกลายเป็นทาสของ framework นั้นๆ ไปซะแล้ว ทำไม? คุณลองคิดดูว่ามันให้อะไรกับคุณบ้าง การใช้งานที่ง่ายขึ้น / helpers ที่มีมาพร้อมใช้ / ระบบ security เพื่อป้องกัน logic code / คุณสามารถใช้งานได้เกือบจะทันทีโดยไม่ต้องลงแรงอะไร และมันก็ทำงานได้ดีเลยทีเดียว!

แล้วการที่ framework ที่ความสะดวกสบายขนาดนี้มันเป็นเรื่องไม่ดีเหรอ?

จริงๆ ไม่ใช่ว่ามันจะไม่ดีนะครับ คุณควรใช้มันซะด้วยซ้ำ เพราะมันถูกทดสอบมาอย่างดี ชุดคำสั่งมันก็มีประสิทธิภาพและเพิ่มความเร็วในการทำงานด้วย เพียงแต่ว่า… มันทำให้คุณขี้เกียจขึ้นมา แล้วไอ้ความ ขี้เกียจ นี้แหละที่ทำให้โค้ดที่คุณเขียนเองมันทำงานเจาะจงกับ framework นั้นๆ มากเกินไป

เราลองมาดูตัวอย่างโค้ดสักอันกัน (แน่นอนว่ามันเป็น PHP และผมอิง Laravel 4 ให้เป็นตัวอย่าง)

class NewsController extends Controller {
  
  public function getRecentNews() {
    return View::make('news.recent', array(
      'news' => News::orderBy('id', 'desc')->paginate(10)
    ));
  }
  
}

ลองมาคิดว่าถ้าลูกค้าคุณต้องการแสดงข่าวบนหน้าเว็บหลายๆ หน้า คุณก็สามารถคัดลอกคำสั่งไปวางที่ controller ต่างๆ ได้เลย มันเร็วและง่าย แต่แล้วลูกค้าคุณเกิดอยากเปลี่ยนการลำดับของการแสดงข่าวขึ้นมา นั้นแหละ ปัญหาที่เกิดขึ้น เป็นหนี้ทางเทคนิคที่คุณต้องไปชดใช้ด้วยการ search-replace เองทั้งหมด ปัญหาไม่ได้เกิดจากการที่ลูกค้าไม่ได้บอกตั้งแต่แรก แต่มันเกิดจากไอ้ “ทางลัด” ง่ายๆ ที่คุณทำมันไว้

แล้วจะแก้ไขกันยังไงดี?

เราลองมาดูคำสั่งใหม่ที่ผมเขียนเพื่อใช้งานแทนข้างบนบนกันดู

class NewsController extends AppController {
  
  protected $viewFactory;
  protected $newsRepository;
  
  public function __construct($viewFactory, $newsRepository) {
    $this->viewFactory = $viewFactory;
    $this->newsRepository = $newsRepository;
  }
  
  public function getRecentNews() {
    return $this->viewFactory->make('news.recent', array(
      'news' => $this->newsRepository->getRecent()
    ));
  }
  
}

class AppController extends Controller {
  
  // ...
  
}

คุณจะเห็นว่ามันให้ความสามารถเหมือนกับโค้ดก่อนหน้านี้ และยังมีสิ่งอื่นๆ เพิ่มขึ้นมา ได้แก่

  • มันไมไ่ด้จำเป็นต้องพึ่งพา framework ใดๆ อีกแล้ว
  • มันสามารถทำ Dependency Injection ได้ ข้อดีเสริมคือ คำสั่งของเรามันทดสอบได้ (testable) และมันยังทำแบบจำลองได้ (mockable) ซึ่งมัน win เอามากๆ
  • พฤติกรรมในคำสั่งระดับ method นั้นสามารถแก้ไขได้ง่ายๆ แล้ว ลืมเรื่องไป search-replace หลายๆ จุดได้เลย

เราลองมาวิเคราะห์คำสั่งเพิ่มเติมกันดูแบบทีล่ะขั้นกัน

1. ที่ระดับ controller เราใช้ AppController แทน Controller ที่เป็นคำสั่งหลักจาก Laravel 4 ซึ่งเพิ่มระดับชั้น (Layer) เพิ่มขึ้นมาอีก ทำคำสั่งของเราไม่ถูกผูกกับ framework ใดๆ จึงทำให้โค้ดของคุณสามารถเคลื่อนย้ายง่ายๆ ด้วยการเข้าไปแก้ไขใน AppController แทนที่จะต้องมาแก้ทุก controller ในระบบทั้งหมด รวมถึงคำสั่งพิเศษที่คุณต้องใช้บ่อยๆ คุณยังนำมันไปเขียนไว้ใน AppController ได้อีกด้วย

2. ในส่วน NewsRepository กับ ViewFactory นั้นเป็นการทำ class ของ model กับ framework ไปยัดใส่ในกลุ่มที่เรียกว่า repository กับ factory ซึ่งเรื่องนี้ช่วยได้การแก้ไขพฤติกรรมของคำสั่งเฉพาะ เช่น $newRepository->getRecent() นั้นมันง่ายขึ้น คุณแค่เข้าไปแก้ใน getRecent() เท่านั้น รวมถึงโค้ดนี้ยังสามารถทดสอบได้ และยังปั้น mock class instance เขาไปได้ด้วย แต่เรื่องนี้เอาไว้เป็นบทความคราวหน้าแทนล่ะกัน (ไม่สัญญาว่าจะเขียนนะครับ XD )

เพิ่มอีกนิดให้มันเข้าท่าด้วย type hinting

ในตอนนี้คุณสามารถคำ class instance ใดๆ เข้าไปยังโค้ดของคุณได้แล้ว แต่เรามาเพิ่ม type hint เพื่อจำกัด class ที่จะเข้าไปได้กันดีกว่า

ยกตัวอย่าง

public function __construct($viewFactory, $newsRepository)

คุณสามารถเขียนเป็นแบบนี้ได้ (ซึ่งในตัวอย่างผมไมไ่ด้เขียน grouping namespace ให้ถูกหลัก เรื่องนี้ไว้คราวอื่นนะครับ)

public function __construct(\ViewFactory $viewFactory, 
                            \NewsRepository $newsRepository)

ซึ่งจากอันนี้แปลว่าตัวแปล $viewFactory นั้นจำเป็นต้องเป็น class ชื่อว่า ViewFactory เท่านั้น ซึ่งแบบนี้เราจะมั่นใจได้แน่นอนว่า class instance ที่เขามานั้นมีคุณสมบัติหรือความสามารถที่เราต้องการจริงๆ

แต่แล้วเราลองมาดูในรูปแบบอื่นกัน

protected function fetchLog(\VCS\Driver\Svn $log)

ยกตัวอย่างว่าคุณได้เขียนคำสั่งสำหรับดึง log จาก S์VN เอาไว้ แต่แล้วในปัจจุบันคุณอยากเปลี่ยนมาใช้ GIT แน่นอนว่าคำสั่งของคุณก็ต้องรองรับ GIT ด้วยเช่นกัน แต่แล้วปัญหาก็เกิดขึ้นว่า fetchLog() นั้นรองรับแต่ instance ของ SVN เท่านั้น พอคุณแก้ให้มันรองรับ GIT มันก็จะไม่รองรับ SVN อีก (ดีงาม….)

คำตอบที่ดีที่สุดสำหรับเรื่องนี้คือ interface type hinting เราลองมาดูคำสั่งเหล่านี้กัน

\\file: log.php

protected function fetchLog(\VCS\Driver\DriverInterface $log)


\\file: VCS/Driver/Svn.php

namespace VCS\Driver;
class Svn implements DriverInterface {}


\\file: VCS/Driver/Git.php

namespace VCS\Driver;
class Git implements DriverInterface {}

จากคำสั่งนี้จะทำให้เห็นว่า fetchLog() นั้นมี type hint เป็นชื่อ interface ว่า DriverInterface ซึ่งการเขียน interface type hinting แบบนี้มันจะไม่เป็นการบังคับว่าคุณต้องส่ง class อะไรเข้ามา แต่มันจะบอกว่าส่งอะไรมาก็ได้ตราบใดที่ class instance นั้นๆ ได้ทำการ implement interface ที่ถูกต้องเข้ามา

เกร็ดสำหรับมือโปร: คุณสามารถ mock class  ด้วยการ implement interface นั้นๆ ซึ่งมันจะทำให้คำสั่งนั้นสามารถทดสอบได้ง่าย!

บทสรุป

ด้วยการเขียนคำสั่งในครั้งถัดไปของคุณโดยการคิดไว้เสมอว่าคำสั่งของคุณนั้นต้องสามารถเคลื่อนย้ายได้ และไม่ผูกติดกับ framework ใดๆ (ทำตัวให้โค้ดเชื่อว่า framework ไม่มีอยู่จริง) โค้ดของคุณจะพิเศษ ปรับเปลี่ยนได้ และทดสอบได้ง่าย มันจะมีประโยชน์มากเมื่อทำงานในระยะยาวครับ

Thanks this article as original content. Don’t Hard-Code Yourself. Make Your Code Framework-Agnostic

 

วิธีติดตั้ง XHProf

XHProf คืออะไร?

สั้นๆ คือมันเป็นเครื่องมือกลุ่ม Profiler หรือ Performance analysis ซึ่งไว้ใช้ตรวจสอบว่าโค้ด PHP ของเราทำงานช้าตรงไหน มันสามารถบอกได้ในระดับ Function หรือ Method โดยบอกได้ว่าถูกเรียกกี่ครั้ง ใครเป็นคนเรียก ใช้เวลาในการทำงานส่วนนั้นนานเท่าไร และสามารถออกเป็นกราฟเพื่อดูได้ง่ายขึ้น เป้าหมายคือใช้ดูว่าโค้ดส่วนไหนเขียนดีหรือไม่ดี ยังสามารถปรับ pattern ได้ไหม คุ้มไหมที่จะทำ อะไรที่มันช้า คุณสามารถใช้สิ่งนี้กับพวก Legacy code ได้ โดยไม่ต้องไปงมโข่งว่าอะไรที่ช้า

โดยส่วนประกอบของ XHProf มี 2 ส่วน คือ ส่วน PHP Extension กับส่วน Report ซึ่งในบทความนี้จะบอกวิธีการติดตั้งทั้งหมดให้ใช้งานได้จริงและง่ายที่สุด

คำเตือน เจ้าของบล็อกเป็นพวกชอบ Ubuntu ดังนั้นใครใช้ OS อื่นก็เอาตัวรอดกันเองนะครับ และผมมักจะไม่ sudo su เพราะถือว่าเป็นคำสั่งที่หลวมมากๆ คุณควรใช้สิทธิ์ root เมื่อจำเป็นเท่านั้น

วิธีการติดตั้ง XHProf ส่วน PHP Extension

คำเตือน หวังว่าคนอ่านจะสามารถโหลด PHP Extension เป็นนะ

ก่อนอื่น โปรดมั่นใจก่อนว่าสามารถรันคำสั่ง pecl ได้ ซึ่งถ้ารันไม่ได้ให้พิมพ์คำสั่งดังนี้

sudo apt-get install pear
sudo apt-get install php5-dev

จากนั้นให้ติดตั้ง XHProf ผ่านทาง PECL ด้วยคำสั่งนี้

sudo pecl install -f xhprof

เมื่อติดตั้งเสร็จก่อนเราจะโหลด extension เข้า PHP ให้เราสร้างโฟลเดอร์สำหรับเก็บ log ที่ตัว XHProf มันสร้างขึ้นมา ในที่นี้ผมจะสร้างไว้ในโฟลเดอร์ home ของ account ของเราเอง และกำหนด permission ให้มัน

mkdir ~/xhprof

เสร็จแล้ว ให้ทำการสร้างไฟล์ mod สำหรับ load เข้า PHP ซึ่งวิธีการผมจะไม่บอกนะ เพราะบางคนใช้ Apache โหลด PHP Module บางคน PHP-FPM หรืออะไรก็ตาม สรุปข้างในไฟล์ ini ให้เขียนแบบนี้

extension = xhprof.so
xhprof.output_dir="/home/<your_username>/xhprof"

ให้เราแทนค่า <your_username> ด้วยชื่อ account ที่เราใช้ล็อกอินเข้า Ubuntu ครับ จากนั้นให้บันทึก

สุดท้ายคือรีสตาร์ท Apache หรือ PHP ครับ อันนี้ตามสะดวกเลย

วิธีการติดตั้ง XHProf ส่วนออกหน้ารายงาน

ก่อนอื่นนี้เป็นเรื่องสำคัญมาก สำหรับคนที่มีหลายเว็บอยู่ภายในเครื่องทดสอบ คุณควรมีโดเมนกลางสำหรับโฟลเดอร์ /var/www ด้วย เพื่อที่ XHProf ส่วนที่ออกรายงานจะได้ถูกวางไว้ตรงนั้นเพื่อให้เรียกดูได้ง่ายๆ จากทุกโปรเจ็ค

ดังนั้นในส่วนนี้ผมจะเขียนโดยอ้างอิงว่าผมจะวางตัวออกรายงานไว้ที่ /var/www/xhprof และจะถูกเรียกด้วย http://localhost/xhprof อันนี้เป็นอันเข้าใจตรงกันนะครับว่าต้องเซ็ตให้เข้าถึงได้แล้วนะครับ ส่วนโปรเจ็คอื่นๆ ของผมจะอยู่ภายใต้ /var/www เหมือนกัน เช่น /var/www/ethaizone.com เป็นต้นครับ

ก่อนอื่นสร้างโฟลเดอร์ xhprof แต่เราจะนำโค้ดลงมาด้วยการ git clone แทนเพื่อความง่าย

cd /var/www
git clone https://github.com/facebook/xhprof xhprof

เราจะได้โค้ดส่วนออกรายงานมาอยู่ใน /var/www/xhprof แล้ว

จากนั้นสิ่งที่จะขาดไม่ได้ ให้ติดตั้ง graphviz ด้วย เพราะต้องใช้ในการสร้าง Callgraph เพื่อออกรายงาน

sudo apt-get install graphviz

เสร็จในส่วนนี้ครับ

วิธีการสร้างตัวเริ่มจับ Profiler ของ XHProf

หลังจากที่เราได้ทั้งหมดมานี้ XHProf จะยังไม่เริ่มจับ Profiler เลย เราจึงต้องมีโค้ด PHP ส่วนหนึ่งที่เอามาคลุมหัวท้าย ซึ่งหลายบทความที่ผมอ่านเจอมา มักจะบอกให้เราเอาโค้ดแปะหัวท้าย index.php ของแต่ละโปรเจ็คเอง แต่ผมว่ามัน “ยุ่งยาก” เกินไป ดังนั้นเรามาทำให้มันใช้งานได้ง่ายขึ้นดีกว่า

โดยเทคนิคที่ผมจะใช้คือการทำ php auto prepend และ append เพื่อที่เราจะไม่ต้องไปแทรกโค้ดเองในทุกโปรเจ็ค แล้วจึงคุมการทำงานด้วยการเช็ค cookie ว่ามีหรือไม่มี

เริ่มกันเลย

nano /var/www/xhprof/header.php

แล้ววางโค้ดตามนี้

<?php
    if (extension_loaded('xhprof') && isset($_COOKIE['xhprof']) && $_COOKIE['xhprof'] == 'omg') {
    include_once '/var/www/xhprof/xhprof_lib/utils/xhprof_lib.php';
    include_once '/var/www/xhprof/xhprof_lib/utils/xhprof_runs.php';
    xhprof_enable(XHPROF_FLAGS_CPU + XHPROF_FLAGS_MEMORY);
}

แก้ชื่อคุ๊กกี้กับค่าที่ใช้ตรวจสอบให้เป็นของตัวเอง (เพื่อความปลอดภัย) แล้วทำการบันทึก จากนั้นสร้างอีกไฟล์

nano /var/www/xhprof/footer.php

แล้ววางโค้ดนี้ครับ

<?php
if (extension_loaded('xhprof') && isset($_COOKIE['xhprof']) && $_COOKIE['xhprof'] == 'omg') {
    $profiler_namespace = 'myapp'; // namespace for your application
    $xhprof_data = xhprof_disable();
    $xhprof_runs = new XHProfRuns_Default();
    $run_id = $xhprof_runs->save_run($xhprof_data, $profiler_namespace);
    // url to the XHProf UI libraries (change the host name and path)
    $profiler_url = sprintf('http://localhost/xhprof/xhprof_html/?run=%s&source=%s', $run_id, $profiler_namespace);
    echo '<a href="'. $profiler_url .'" target="_blank">Profiler output</a>';
}

แก้ชื่อคุ๊กกี้กับค่าที่ใช้ตรวจสอบให้เป็นของตัวเอง (เพื่อความปลอดภัย) และค่าต้องเหมือนใน header.php ข้างบนนะครับ แล้วทำการบันทึก

จากนั้นผมจะเพิ่ม auto prepend ลง .htaccess

nano /var/www/.htaccess

แล้วเพิ่มโค้ดส่วนนี้

php_value auto_prepend_file "/var/www/xhprof/header.php"
php_value auto_append_file "/var/www/xhprof/footer.php"

จากนั้นให้บันทึก เท่าที่ก็เสร็จแล้วครับ

ทดลองใช้ XHProf ครั้งแรก

อันนี้ง่ายๆ เลยครับ หา browser สักตัว เปิดเว็บในเครื่องสักเว็บ เสร็จแล้วแก้ cookie เพิ่มอันใหม่ชื่อ xhprof พร้อมค่าที่คุณกำหนดไว้ในไฟล์ header.php แล้วให้เปิดหน้าเว็บใหม่อีกที ถ้าคุณเลื่อนลงมาล่างสุด คุณส่วนเจอลิงก์คำว่า Profiler output ให้กดลิงก์เลย ถ้าคุณทำถูกต้อง มันจะแสดงในหน้าเว็บเป็นรายงานให้คุณดูได้เลย

ส่วนในกรณีคุณเจอ error ที่เกี่ยวข้องกับ permission ของ XHProf ให้ทำการรันสองคำสั่งนี้ครับ

sudo chown www-data ~/xhprof/ -R
sudo chown www-data /var/www/xhprof/ -R

หวังว่าจะช่วยได้นะครับ