อย่าทำตัวเป็นทาสเฟรมเวิร์ค 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

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

 

 

 

Laravel 4 Security

จากเมื่อก่อนที่เคยเขียน Laravel Security ซึ่งในตอนนั้นผมยังใช้ Laravel 3 อยู่ แต่ทุกวันนี้ต้องบอกเลยว่า Laravel 4 นั้นเยี่ยมมากๆ และยังไม่รวมถึง Laravel 4.1 ที่ดีขึ้นไปอีก แต่ขั้นตอนการ update ออกจะสะท้านหน่อยในโปรเจ็คขนาดใหญ่

วันนี้ผมจะเขียนอิงหัวข้อเดิม จะได้เป็นข้อเปรียบเทียบกับ Laravel 3 ได้เพื่อใครที่กำลังตัดสินใจว่าจะใช้ดีหรือไม่ โดยผมจะเขียนอิง Laravel 4 ซึ่งในด้านความปลอดภัยนั้นไม่ได้แตกต่างกับ 4.1 สักเท่าไร

1. CSRF protection หรือการปลอมแปลง http request ที่เรียกข้ามเว็บไซต์ เรื่องนี้ Laravel มีป้องกันอยู่แล้ว โดยเป็นการสร้าง Token ไว้ตรวจสอบ หาอ่านได้ที่ CSRF protection แต่สำหรับคนที่เขียน Route แบบ Controller สามารถเขียน filter ไว้ที่ Controller แบบนี้ได้ (ไม่มีใน doc)

$this->beforeFilter('csrf');

2. Cookie Security เรื่องนี้ไม่ต่างจาก Laravel 3 มากครับ หาอ่านได้ที่ Cookies ซึ่งจริงๆ ต้องบอกรวมๆ ว่าข้อมูลต่างๆ ของ Laravel 4 ทั้ง Cookie Session จะถูกเข้ารหัสทั้งหมด ทำให้ข้อมูลนั้นปลอดภัยครับ แต่ถ้าเรื่องทางเทคนิคเชิงลึก แนะนำให้แกะโค้ดดูครับ

3. SQL-Injection เรื่องนี้ไม่ต้องท้าวความเลย มีป้องกันไว้เหมือนกัน โดย Laravel จะทำงานผ่าน PDO หาอ่านได้ที่ PDO/Prepared statements and stored procedures สรุปให้สั้นๆ คือ ถ้าเรียกทำงานผ่าน Fluent หรือ Eloquent มันก็จะจัดการให้เสร็จ ซึ่งใน Laravel 4 ยังสามารถเขียน query ดิบได้อยู่ แต่มันเป็นสิ่งที่ไม่แนะนำให้ทำครับ เราจึงไม่เห็นหัวข้อของเรื่อง raw query ถูกพูดใน doc ซักเท่าไร (ถูกให้ความสำคัญลดลง)

4. XSS หรือการแทรกโค้ดไม่พึ่งประสงค์ลงหน้าเว็บ เรื่องนี้ Laravel ไม่มีการป้องกันครับ เหมือนเดิมครับ เพราะ PHP ก็มีฟังค์ชั่นพื้นฐานมาเพื่อป้องกันเรื่องพวกนี้แล้วอย่าง strip_tags() and htmlentities() ดังนั้นเรื่องนี้ต้องจัดการกันเองครับ ด้วยการเขียน Validation ให้ครบครับ

เขียนคราวนี้เหมือนเอาบทความเก่ามาแก้ครับ เพราะช่วงนี้งานชุกครับ  /XD