Kotchasan PHP Framework

โปรเจ็คตัวอย่างเว็บไซต์ร้านค้าออนไลน์ที่ใช้ Javascript API ในการแสดงสินค้า ตอนที่ 2

ตอนที่สอง ผมจะอธิบายในส่วนของการสร้าง REST API ด้วยคชสาร ซึ่งสามารถสร้างได้ง่ายมาก โดยที่โค้ดในส่วนนี้จะอยู่ในโปรเจ็คระบบบัญชีออนไลน์ นะครับ

ไฟล์แรก คือ api.php เป็นไฟล์หลักสำหรับการเรียก API ครับ สามารถใช้ชื่ออื่นก็ได้ (ตัวอย่างนี้มีการใช้ index.php เป็นหน้าเว็บหลักไปแล้ว) จุดที่สำคัญในไฟล์นี้คือมีการกำหนดค่า Router ไปที่ \Gcms\Router และ Controller ไปที่ \Index\Api\Controller
<?php
/**
 * api.php
 * หน้าเพจสำหรับให้ API เรียกมา
 *
 * @author Goragod Wiriya <admin@goragod.com>
 * @link http://www.kotchasan.com/
 * @copyright 2016 Goragod.com
 * @license http://www.kotchasan.com/license/
 */

// load Kotchasan
include 'load.php';
// Initial Kotchasan Framework
$app = Kotchasan::createWebApplication('Gcms\Config');
$app->defaultRouter = 'Gcms\Router';
$app->defaultController = 'Index\Api\Controller';
$app->run();

ไฟล์ที่สอง Gcms/Router.php หรือคลาส \Gcms\Router
<?php
/**
 * @filesource Gcms/Login.php
 * @link http://www.kotchasan.com/
 * @copyright 2016 Goragod.com
 * @license http://www.kotchasan.com/license/
 */


namespace Gcms;

/**
 * Router Class สำหรับ GCMS
 *
 * @author Goragod Wiriya <admin@goragod.com>
 *
 * @since 1.0
 */

class Router extends \Kotchasan\Router
{
  /**
   * กฏของ Router สำหรับการแยกหน้าเว็บไซต์
   *
   * @var array
   */

  protected $rules = array(
    // api.php/products/<category_id>/<page>
    '/api\.php\/(products)\/([0-9]+)\/([0-9]+)/i' => array('action', 'category_id', 'page'),
    // api.php/products/<page>
    '/api\.php\/(products)\/([0-9]+)/i' => array('action', 'page'),
    // api.php/search/<q>/<page>
    '/api\.php\/(search)\/([^\/]+|$)(\/([0-9]+))?/i' => array('action', 'q', '', 'page'),
    // api.php/<action>/<id>
    '/api\.php\/([a-z]+)(\/([0-9]+))?/i' => array('action', '', 'id'),
  );
}

ใจความสำคัญของคลาสนี้คือมีการกำหนด $rules สำหรับกฏของ Router ในการแยกหน้า ตัวอย่างเช่น
'/api\.php\/(products)\/([0-9]+)\/([0-9]+)/i' => array('action', 'category_id', 'page'),
  • (products) จะถูก map ไปที่ action ได้ action=products
  • ([0-9]+) จะถูก map ไปที่ category_id ได้ category_id=xxx
  • ([0-9]+) จะถูก map ไปที่ page ได้ page=xxx
ข้อมูลหลังจาก parse ด้วย Router แล้วจะถูกส่งไปยังคลาส Request ที่เมธอด GET ของคชสาร

ไฟล์ที่สาม modules/index/controllers/api.php หรือ \Index\Api\Controller ซึ่งเป็น Controller หลักของ API
<?php
/**
 * @filesource modules/index/controllers/api.php
 * @link http://www.kotchasan.com/
 * @copyright 2016 Goragod.com
 * @license http://www.kotchasan.com/license/
 */


namespace Index\Api;

use \Kotchasan\Http\Request;

/**
 * Controller สำหรับโหลดข้อมูลด้วย Ajax
 *
 * @author Goragod Wiriya <admin@goragod.com>
 *
 * @since 1.0
 */

class Controller extends \Kotchasan\Controller
{

  /**
   * มาจากการเรียกด้วย Ajax
   *
   * @param Request $request
   */

  public function index(Request $request)
  {
    $action = $request->get('action')->filter('a-z');
    if (method_exists('Index\Api\Model', $action)) {
      $result = \Index\Api\Model::$action($request);
    }
    if (!isset($result)) {
      $result = array(
        'error' => array(
          'code' => 400,
          'message' => 'bad request'
        )
      );
    }
    // Response
    $response = new \Kotchasan\Http\Response;
    $response->withHeaders(array(
        'Content-type' => 'application/json; charset=UTF-8'
      ))
      ->withContent(json_encode($result))
      ->send();
  }
}

คลาสนี้มีหน้าที่ในการเลือก Model ที่ต้องการจาก Request ที่ส่งมาจาก Router (ตัวแปร $request) และทำการส่งกลับข้อมูลรูปแบบ JSON ผ่าน Response
$action = $request->get('action')->filter('a-z');

เมธอดที่ต้องการส่งมาจาก Router ที่พารามิเตอร์ get('action') หรือเทียบเท่า $_GET['action']
if (method_exists('Index\Api\Model', $action)) {

นำเมธอดไปตรวจสอบว่ามีหรือไม่ในคลาส Index\Api\Model
$result = \Index\Api\Model::$action($request);

ถ้ามีก็จะเรียกใช้เมธอด คืนค่าผลลัพท์ที่ตัวแปร $result
ในกรณีที่ไม่พบเมธอด ตัวแปร $result จะถูกกำนดค่า error ไปแทน
และสุดท้ายคืนค่าผลลัพท์เป็น JSON โดยใช้คลาส Response
// Response
$response = new \Kotchasan\Http\Response;
$response->withHeaders(array(
    'Content-type' => 'application/json; charset=UTF-8'
  ))
  ->withContent(json_encode($result))
  ->send();

ไฟล์สุดท้าย modules/index/models/api.php หรือคลาส \Index\Api\Model ซึ่งเป็น Model ที่ Controller เรียกใช้ (ผมจะอธิบายทีละเมธอดเลยนะครับ)
  /**
   * คืนค่ารายการหมวดหมู่ทั้งหมด
   *
   * @param Request $request
   * @return array
   */

  public static function categories(Request $request)
  {
    $query = static::create()->db()->createQuery()
      ->select('category_id', 'topic')
      ->from('category')
      ->where(array('type', 0))
      ->order('category_id')
      ->toArray()
      ->cacheOn();
    $result = array();
    foreach ($query->execute() as $item) {
      $result[] = array(
        'category_id' => $item['category_id'],
        'topic' => $item['topic'],
      );
    }
    return $result;
  }

เมธอดแรก คืนค่าหมวดหมู่ทั้งหมด หรือเทียบเท่า SQL Query เป็น
SELECT category_id, topic FROM category WHERE type=0 ORDER BY category_id

เมธอด products คืนค่ารายการสินค้าที่เกี่ยวข้องตามพารามิเตอร์ที่ส่งมา เมธอดนี้จะทำหน้าที่หลายอย่าง เช่น query ข้อมูลสินค้าที่ต้องการตามหมวดหมู่ รวมถึง query ข้อมูลที่มาจากการค้นหา และส่งกลับข้อมูลสำหรับการแบ่งหน้าการแสดงผลด้วย
  /**
   * คืนค่ารายการสินค้า ถ้ามีการระบุ id มา หมายถึงสินค้าในหมวดที่เลือก
   *
   * @param Request $request
   * @return array
   */

  public static function products(Request $request)
  {
    // ค่าที่ส่งมา
    $q = $request->get('q')->topic();
    $category_id = $request->get('category_id')->toInt();
    $page = $request->get('page')->toInt();
    $list_per_page = $request->get('limit', 30)->toInt();
    // ตัวแปรสำหรับส่งค่ากลับ
    $result = array();
    // Model
    $model = new static;
    $query = $model->db()->createQuery()->from('product');
    if ($category_id > 0) {
      $query->where(array('category_id', $category_id));
      $result['category_id'] = $category_id;
    }
    $where = array();
    if ($q != '') {
      foreach (explode(' ', $q) as $item) {
        $where[] = array('product_no', 'LIKE', "%$item%");
        $where[] = array('topic', 'LIKE', "%$item%");
        $where[] = array('description', 'LIKE', "%$item%");
      }
    }
    if (!empty($where)) {
      $query->andWhere(\Kotchasan\Database\Sql::WHERE($where, 'OR'));
      $result['q'] = $q;
    }
    // จำนวน
    $result['total'] = $query->cacheOn()->count();
    $result['totalpage'] = ceil($result['total'] / $list_per_page);
    $result['page'] = max(1, ($page > $result['totalpage'] ? $result['totalpage'] : $page));
    $result['start'] = $list_per_page * ($result['page'] - 1);
    // query
    $result['items'] = $query->order('id')
      ->select()
      ->order('topic', 'product_no')
      ->limit($list_per_page, $result['start'])
      ->cacheOn()
      ->toArray()
      ->execute();
    // คืนค่า
    return $result;
  }

เมธอดถัดมา เมธอด serach จะไปเรียกใช้เมธอด products อีกที
  /**
   * ค้นหารายการสินค้า จาก product_no topic description
   *
   * @param Request $request
   * @return array
   */

  public static function search(Request $request)
  {
    return self::products($request);
  }

เมธอดสุดท้าย อ่านค่าสินค้าที่เลือกจาก ID ของสินค้า
  /**
   * คืนค่ารายละเอียดของสินค้าที่ id
   *
   * @param Request $request
   * @return array
   */

  public static function product(Request $request)
  {
    return static::create()->db()->createQuery()
        ->from('product')
        ->where(array('id', $request->get('id')->toInt()))
        ->cacheOn()
        ->toArray()
        ->first();
  }

จะได้ SQL ประมาณนี้
SELECT * FROM product WHERE id=? LIMIT 1

ผลลัพท์ของ Query ทั้งหมด จะส่งกลับเป็นแอเรย์ กลับไปที่ Controller เพื่อใช้ในการส่งค่ากลับต่อไป
{
  "id": 1,
  "product_no": "0111900300104",
  "topic": "Welness Smart Chek เครื่องตรวจวัดน้ำตาลกลูโคสในเลือด",
  "description": "ตรวจวัดระดับน้ำตาลได้รวดเร็วแม่นยำ ด้วยวิธีการง่ายๆแค่ 4 ขั้นตอน",
  "price": "1990.00",
  "url": "http://www.tvdirect.tv/welness-smart-check",
  "image": "http://cdns.cdntvdirect.com/media/catalog/product/s/m/smart-chek_1_2.jpg",
  "last_update": 0,
  "vat": "0.00",
  "unit": "",
  "category_id": 29,
  "count_stock": 0
}

ตัวอย่างด้านบนเป็นผลลัพท์ของ API ในรูปแบบ JSON ที่ส่งกลับ

เพิ่มเติม สำหรับการใช้งาน API ที่อาจมีปัญหาการร้องขอข้อมูลจากเว็บไซต์ต่าง URL กัน (API อยู่โดเมนหนึ่ง และ เว็บไซต์ที่เรียกใช้อยู่อีกโดเมนหนึ่ง) ให้กำหนดค่า Apache ให้ยอมรับการร้องขอข้อมูลโดยการกำหนดที่ไฟล์ .htaccess
RewriteEngine On

# Cross domain access
Header add Access-Control-Allow-Origin "*"
Header add Access-Control-Allow-Headers "origin, x-requested-with, content-type"
Header add Access-Control-Allow-Methods "PUT, GET, POST, DELETE, OPTIONS"

สำหรับโปรแกรมที่ผมใช้สำหรับทดสอบการทำงานของ API ผมใช้ตัวนี้เลยครับ https://install.advancedrestclient.com (ตัวที่เป็น extension ของ Chrome ก็มี)
อ่านต่อตอนสุดท้ายคลิกลิงค์ด้านล่างเลยครับ