<转>php 创建简单的Restful WebAPI

Restful API现在非常的流行啊,目前工作的项目也使用了ASP.NET Web API技术。用下来的感觉是前台数据的展现层可以和后台数据的处理层解耦性很好。所以在开发阶段,前台数据展现页面布局和后台数据处理调整起来都很方便。Restful API利用了http协议,配合一些类似backbone这样的js mvc框架非常好用。

  最近有一个需求是用php实现一个简单的 Restful WebAPI。因为之前用的ASP.NET Web API,所以就模拟实现一下它的结构。

  由路由Router来选择Controller,由method选择Action,数据传输使用json。举个例子

    Get      /api/resouces           返回资源信息  Controller:Resouces Action: get()

    Get      /api/resouces/{id}   返回资源信息  Controller:Resouces Action: get($id)

    Post     /api/resouces          创建资源信息  Controller:Resouces Action: post()

    Put      /api/resouces/{id}    更新资源信息  Controller:Resouces Action: get($id)

    Delete  /api/resouces/{id}    删除资源信息  Controller:Resouces Action: delete($id)

  总结看来,对于url我们只需要处理2种:/api/resouces 和 /api/resouces/2。

  下面的文章从路由开始的记录一个restful api 的简单实现。

  (做个说明,由于本人php编程经验有限,希望大家对文章中出现的任何问题都不吝赐教,可以共同进步)

 

 

 

 

  先说一下这个项目的代码结构吧。

  首先是index.php,我是把它作为中央控制器,一个调度器。程序通过rewrite或其他方式,把所有url导向本文件,由index.php调度其他代码。

  然后route.php,用设置好的路由规则匹配当前的url,来选择响应的controller(下面解释)来处理request,返回response。

  request.php,本文件用来解析requset信息(头部信息:method,accept,querystring,请求body的数据(主要为json格式)等),保存到内存里等待其他程序使用。

  response.php,本文件用来生成response信息(body数据(json格式),设置状态码,content-type等),返回给客户端。

  controller文件夹内的文件,这些文件用来处理具体业务逻辑,根据request.php,操作数据库,使用response.php生成结果返回给客户端。

  db.php由名字可知,本文件为数据库操作类(mysql)。

  还有其他类如日志功能,身份验证等等以后再实现吧。

  Rewite

  应用程序首先将所有url导向到index.php。可以使用Apache服务器mod_rewrite重写转向(Rewrite)模块来实现。由于我的web服务搭建在新浪SAE,它本身的config.yaml具有url重写功能,只需要加入一句 - rewrite: if(!is_file()) goto "index.php?%{QUERY_STRING}" 就可以了(config.yaml配置可参阅SAE相关文档)。

  Route

  首先在router.php配置相关规则:

复制代码
<?php
/**
 *路由配置文件编写说明: 
 *  路由配置在一个array数组中,一条记录代表一个规则
 *  优先匹配索引低的规则
 *  key:   只接受2中规则 '/{controller}'和'/{controller}/{id}'。{controller}可用字符包括:字母,数字,_
 *  value: 第一项controller类名(文件名除去扩展名必须与类名相同);
 *         第二项id只能为正整数(包含0)
 *  controller文件必须位于'/controller'文件夹下;类名必须与文件名相同(除去扩展名.php),区分大小写。
**/
$routes= array(
    '/resources' => array('resources',''),
    '/resources/id' => array('resources','id'),
);
?>
复制代码

  routes数组保存应用程序的相关规则,由于时间紧迫,设置规则十分不灵活,后期需要加以修改,规则及其解析我们都可以随时进行调整。 routes数组key为需要匹配的url,value为响应的controller名字。前文我们有提到,我们只需要处理2种url :'/resources' => array('resources','')表示匹配到/resources时,选择controller文件下的resources.php文件内的 resources类来处理。'/resources/id' => array('resources','id'),大同小异,只不过我们需要,匹配并保存url中第二层的id。

  然后route.php解析上面的规则:

复制代码
<?php

class Route
{
    private $filepath;  
    private $classname;    
    private $id; 
    private $routepatterns; 
 
    public function __construct()
    {
        $this->filepath      = '';    
        $this->classname     = '';  
        $this->id            = null;
        $this->routepatterns = array(); 
        $this->initRoutes();
    }

    private function initRoutes()
    {
        $reg_m1 = '#^/(\w+)$#';
        $reg_m2 = '#^/(\w+)/id$#';
        $matches = array();
        $routes = array();
        include 'router.php';
        foreach($routes as $key=>$value){  
            
            if(preg_match($reg_m1,$key,$matches)){
                $this->routepatterns[] = array('#^/('.$matches[1].')\??$#i', array('controller/'.$value[0].'.php',$value[0]));
            }
            else if(preg_match($reg_m2,$key,$matches)){
                $this->routepatterns[] = array('#^/('.$matches[1].')/(\d+)\??$#i', array('controller/'.$value[0].'.php',$value[0]));
            }
        }
    }

    public function processURL($urlpath)
    {
        $matches = array();
        foreach($this->routepatterns as $router){
             if(preg_match($router[0],$urlpath,$matches)){
                 $filepath_ = '/'.$router[1][0];
                 $classname_ = $router[1][1];
                 $id_ = count($matches)>2?$matches[2]:null;
                 $this->setFilePath($filepath_);
                 $this->setClassName($classname_);
                 $this->setID($id_);
                 return true;
             }
         }
         return false;
    }

    public function setFilePath($filepath)  
    {  
        $this->filepath = $filepath;  
    }  
  
    public function setClassName($classname)  
    {  
        $this->classname = $classname;  
    }  
  
    public function setID($id)  
    {  
        $this->id = $id;  
    }  
  
    public function getFilePath()  
    {  
        return $this->filepath;  
    }  
  
    public function getClassName()  
    {  
        return $this->classname;  
    }  
  
    public function getID()  
    {  
        return $this->id;  
    }  
  
}

?>
复制代码

   私有方法initRoutes(),是一个预处理方法。它解析了routes所有规则,保存routepatterns中,等待使用。 processURL($urlpath)方法使用解析出的routepatterns匹配传入url,获取相应的结果,并记录下来。

  processURL($urlpath)方法是Rote类的核心,它将传入的url用之前定义的路由规则匹配。成功则获取controller,id(如果有),返回true;失败返回false。

 

 

 

 

上篇记录了怎样实现route,本篇记录怎么实现request,response。

  Request 处理请求

复制代码
<?php

class Request
{
    private $request_vars;  
    private $data;  
    private $http_accept;  
    private $method; 
    private $ID; 
  
    public function __construct($id = null)  
    {  
        $this->request_vars      = array();  
        $this->data              = array(); 
        $this->http_accept       = 'application/json';  
        $this->method            = 'get';  
        $this->ID                = $id;  
        $this->processRequest();
    }  

    public function processRequest()
    {
        $request_method = strtolower($_SERVER['REQUEST_METHOD']);
        $this->setMethod($request_method);  

        $request_vars = $_GET;
        if($request_vars != ''){
            $this->setRequestVars($request_vars); 
        }  

        $data = file_get_contents('php://input');
        if($data != ''){
            $this->setData(json_decode($data,TRUE));  
        } 
    } 

    public function setID($id)  
    {  
        $this->ID = $id;  
    }  
  
    public function setData($data)  
    {  
        $this->data = $data;  
    }  
  
    public function setMethod($method)  
    {  
        $this->method = $method;  
    }  
  
    public function setRequestVars($request_vars)  
    {  
        $this->request_vars = $request_vars;  
    }

    public function getID()  
    {  
        return $this->ID;  
    }  
  
    public function getData()  
    {  
        return $this->data;  
    }  
  
    public function getMethod()  
    {  
        return $this->method;  
    }  
  
    public function getHttpAccept()  
    {  
        return $this->http_accept;  
    }  
  
    public function getRequestVars()  
    {  
        $this->request_vars['id'] = $this->ID;
        return $this->request_vars;  
    }  
}  

?>
复制代码

  代码十分简单,就是从request获取请求method,querystring,请求body数据(json格式)。

  Response 发送响应

复制代码
<?php

class Response
{
    public function __construct()  
    {
    }

    public function sendResponse($status = 200, $body = '', $content_type = 'application/json') 
    {
         $status_header = 'HTTP/1.1 ' . $status . ' ' . $this->getStatusCodeMessage($status);  
         header($status_header);  
         header('Content-type: ' . $content_type . '; charset=utf-8'); 
         echo $body;
         exit;  
    }

    public function getStatusCodeMessage($status)  
    {  
        $codes = 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 (isset($codes[$status])) ? $codes[$status] : '';  
    }
}  

?>
复制代码

  生成response。

  这两个类为工具类,实现也不复杂,功能单一,需要继续丰富修改。

posted on 2016-02-18 23:39  hahahahahai12  阅读(281)  评论(0)    收藏  举报

导航