:::

8. 設計成 RESTful API

  1. 如果要將網站資料提供給 App或者一些前端工具,如 Vue 或 React...等使用的話,必須做成 API,也可以做成 Open Data
  2. 要寫 API,基本上會建議 用 json 格式,另外還要處理一些跨域、檔頭回覆的東西,為了簡化,這裡提供一個簡易的 REST API class,請將之存為 SimpleRest.php
    <?php
    /*
     * 一個簡單的 RESTful web services 基類
     * 我們可以基於這個類來擴展需求
     */
    class SimpleRest
    {
    
        private $httpVersion = "HTTP/1.1";
    
        public function setHttpHeaders($statusCode)
        {
    
            $statusMessage = $this->getHttpStatusMessage($statusCode);
            // Allow from any origin
            if (isset($_SERVER['HTTP_ORIGIN'])) {
                header("Access-Control-Allow-Origin: {$_SERVER['HTTP_ORIGIN']}");
                header('Access-Control-Allow-Credentials: true');
                header('Access-Control-Max-Age: 86400'); // cache for 1 day
            }
    
            if ($_SERVER['REQUEST_METHOD'] == 'OPTIONS') {
    
                if (isset($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_METHOD'])) {
                    header("Access-Control-Allow-Methods: GET, POST, OPTIONS");
                }
    
                if (isset($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS'])) {
                    header("Access-Control-Allow-Headers: {$_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS']}");
                }
    
                exit(0);
            }
            header($this->httpVersion . " " . $statusCode . " " . $statusMessage);
            header("Content-Type: application/json; charset=utf-8");
        }
    
        public function getHttpStatusMessage($statusCode)
        {
            $httpStatus = array(
                100 => 'Continue',
                101 => 'Switching Protocols',
                200 => 'OK',
                201 => 'Created',
                202 => 'Accepted',
                203 => 'Non-Authoritative Information',
                204 => 'No Content',
                205 => 'Reset Content',
                206 => 'Partial Content',
                300 => 'Multiple Choices',
                301 => 'Moved Permanently',
                302 => 'Found',
                303 => 'See Other',
                304 => 'Not Modified',
                305 => 'Use Proxy',
                306 => '(Unused)',
                307 => 'Temporary Redirect',
                400 => 'Bad Request',
                401 => 'Unauthorized',
                402 => 'Payment Required',
                403 => 'Forbidden',
                404 => 'Not Found',
                405 => 'Method Not Allowed',
                406 => 'Not Acceptable',
                407 => 'Proxy Authentication Required',
                408 => 'Request Timeout',
                409 => 'Conflict',
                410 => 'Gone',
                411 => 'Length Required',
                412 => 'Precondition Failed',
                413 => 'Request Entity Too Large',
                414 => 'Request-URI Too Long',
                415 => 'Unsupported Media Type',
                416 => 'Requested Range Not Satisfiable',
                417 => 'Expectation Failed',
                500 => 'Internal Server Error',
                501 => 'Not Implemented',
                502 => 'Bad Gateway',
                503 => 'Service Unavailable',
                504 => 'Gateway Timeout',
                505 => 'HTTP Version Not Supported');
            return ($httpStatus[$statusCode]) ? $httpStatus[$statusCode] : $status[500];
        }
    }
    

     

  3. 我們要借助該類別來將我們網站上的一些函式,也打包成一個物件,我們先把 index.php 中除了 add_count() 以外的函式都搬移到 function.php 中,以便讓其他程式呼叫使用。
    <?php
    use JasonGrimes\Paginator;
    
    // 列出所有文章
    function index($p, $cate_id = 0, $keyword = '', $year = '')
    {
        ...略...
    }
    
    // 觀看某篇文章
    function show($id)
    {
        ...略...
    }
    
    // 取得縮圖
    function get_thumbs($id)
    {
        ...略...
    }
    
    // 製作摘要
    function get_summary($content)
    {
        ...略...
    }
    
    // 年度文章數
    function article_count()
    {
        ...略...
    }
    

    由於 index() 中有用到 JasonGrimes\Paginator 分頁物件,故在此檔前面也必須加上  use JasonGrimes\Paginator;

  4. 此時 index.php 應該會變得很乾淨(前面可以拿掉 use JasonGrimes\Paginator;
    <?php
    require_once 'header.php';
    
    // 過濾外來變數
    $p = isset($_REQUEST['p']) ? (int) $_REQUEST['p'] : 1;
    $id = isset($_REQUEST['id']) ? (int) $_REQUEST['id'] : 0;
    $op = isset($_REQUEST['op']) ? filter_var($_REQUEST['op'], FILTER_SANITIZE_STRING) : 'index';
    $cate_id = isset($_REQUEST['cate_id']) ? (int) $_REQUEST['cate_id'] : 0;
    $keyword = isset($_REQUEST['keyword']) ? filter_var($_REQUEST['keyword'], FILTER_SANITIZE_STRING) : '';
    
    switch ($op) {
        // 觀看單一文章
        case 'show':
            $news = show($id);
            add_count($id);
            $smarty->assign('news', $news);
            break;
    
        // 預設為文章列表
        default:
            $all_news = index($p, $cate_id, $keyword, $year);
            $smarty->assign('all_news', $all_news);
            break;
    }
    
    $smarty->assign('op', $op);
    $smarty->assign('cate_id', $cate_id);
    $smarty->display('index.tpl');
    
    // 點閱數+1
    function add_count($id)
    {
        global $db;
        $sql = "UPDATE `articles` SET `counter` = `counter` + 1
        WHERE `id` = ?";
        $sth = $db->prepare($sql);
        $values = [$id];
        $sth->execute($values);
    }
    

     

  5. 接著,我們就可以來寫屬於一個自己的類別,如:NewsRest.php,基本上,就是取得資料後,將之做成 json 格式而已,也就是轉一手的概念,當然,其中還會添加許多 REST API 需要的檔頭及回應,不過這都由 SimpleRest.php 處理掉了,所以我們只要繼承物件,然後添加我們要的成員方法即可,剩下的我們不需要操心。
    <?php
    require_once "SimpleRest.php";
    require_once "header.php";
    
    class NewsRest extends SimpleRest
    {
        public function __construct()
        {
        }
    
        // 取得所有文章的 json
        public function index($p = 1, $cate_id = 0, $keyword = '', $year = '')
        {
            $data = index($p, $cate_id, $keyword, $year);
            return $this->encodeJson($data);
        }
    
        // 取得單一文章的 json
        public function show($id)
        {
            $data = show($id);
            return $this->encodeJson($data);
        }
    
        // 取得年度文章數
        public function count()
        {
            $data = article_count();
            return $this->encodeJson($data);
        }
    
        public function encodeJson($responseData)
        {
            if (empty($responseData)) {
                $statusCode = 404;
                $responseData = array('error' => '無資料');
            } else {
                $statusCode = 200;
            }
            $this->setHttpHeaders($statusCode);
    
            $jsonResponse = json_encode($responseData, 256);
            return $jsonResponse;
        }
    
    }
    

     

  6. 最後,利用我們自己寫的 class 去寫 API 檔案,建立 api.php
    <?php
    require_once "NewsRest.php";
    $NewsRest = new NewsRest();
    
    // 過濾外來變數
    $p = isset($_REQUEST['p']) ? (int) $_REQUEST['p'] : 1;
    $id = isset($_REQUEST['id']) ? (int) $_REQUEST['id'] : 0;
    $op = isset($_REQUEST['op']) ? filter_var($_REQUEST['op'], FILTER_SANITIZE_STRING) : 'index';
    $cate_id = isset($_REQUEST['cate_id']) ? (int) $_REQUEST['cate_id'] : 0;
    $keyword = isset($_REQUEST['keyword']) ? filter_var($_REQUEST['keyword'], FILTER_SANITIZE_STRING) : '';
    
    switch ($op) {
        case 'index':
            echo $NewsRest->index($p, $cate_id, $keyword, $year);
            break;
    
        case 'show':
            echo $NewsRest->show($id);
            break;
    
        case 'count':
            echo $NewsRest->count();
            break;
    }
    

     

  7. 如此,只要前端程式去呼叫 http://網址/api.php?op=index 會接收到這樣的 json 資料,接著就可以繼續往下運用處理了:
  8. 用 Nuxt 前端網站範例:http://vue.lces.tn.edu.tw/

:::

書籍目錄

展開 | 闔起

https%3A%2F%2Fcampus-xoops.tn.edu.tw%2Fmodules%2Ftad_book3%2Fpage.php%3Ftbsn%3D52%26tbdsn%3D1836

計數器

今天: 4303430343034303
昨天: 5069506950695069
總計: 5134751513475151347515134751513475151347515134751