关于REST的介绍可以参考我之前的文章,总体说来,REST是web发展的趋势,而PHP是web开发的利器,但我找了一遍,只找到了两个PHP REST框架(不包括那些以MVC为核心,同时又支持REST的框架),一个是Tonic,架构理念我比较认同,但代码质量实在不敢恭维。还有一个是Recess,在我看来,它有点复杂化了,把不该rest做的事也做了。在这种情况下,我只能自己动手,丰衣足食了。
RESTY的流程很简单,获取Request单例,然后执行exec方法,该方法里会调用Route来解析URI获取相应的Resource,然后实例化Resource,触发相应的HTTP方法,最后返回一个Response对象,Response执行output方法就输出了结果。听起来好像一点都不简单,哈哈,还是来大概看一下代码吧
index.php
try {
Request::instance()->exec()->output();
} catch (Route_Exception $e) {
Response::instance()
->set_status(404)
->set_body(array(
'error' => 'Resource Not Found',
'Request' => $_SERVER['REQUEST_URI'],
))
->output()
;
}
request.php
public function exec()
{
$class_name = 'Resource_'.str_replace('/', '_', $this->get_resource());
$class = new ReflectionClass($class_name);
$resource = $class->newInstance($this);
$class->getMethod('before')->invoke($resource);
$class->getMethod($this->request_method)->invoke($resource);
$class->getMethod('after')->invoke($resource);
$response = Response::instance();
$response->set_body($resource->get_data());
return $response;
}
response.php
public function output()
{
$this->_content_encoding();
header('Content-type:application/json;charset=utf-8');
header('Status:'.$this->_status.' '.$this->_messages[$this->_status]);
header('Content-Length: '.strlen($this->_body));
foreach($this->_header as $key => $val)
{
header($key.':'.$val);
}
echo $this->_body;
}
RESTY包含了核心的Request/Resource/Response/Route/Config/Validation功能,没有其他多余的部件,如Controller/View等等,很纯粹。一个工具应该把一件事做好,同时提供接口,这也是RESTY的哲学。
使用时,只需定义好uri对应的Resource,然后编写Resource就行了,其他的事RESTY会帮你搞定。
config/resource.php demo
return array(
'/example/(?<id>[0-9]+)' => 'example',
'/example/foo/(?<name>[a-zA-Z_0-9]+)' => 'example/foo',
);
可以看到uri支持正则,没错,原生的php正则。resource部分对应resource文件的路径(不包括后缀)
resource/example.php
class Resource_Example extends Resource
{
public function get()
{
/* set etag
Response::instance()
->if_none_match(md5('hello'))
->add_etag(md5('hello'))
;
//*/
if ($this->validate())
{
$this->_data = $this->get_data();
}
else
{
$this->_data = array('error' => implode(',', $this->getErrors()), 'request' => $_SERVER['REQUEST_URI']);
}
}
public function post()
{
$this->_data = array_merge($this->get_data(), array('type' => 'post'));
}
}
每一个资源对应4个http方法。RESTY还很贴心地提供了Validation部件(基本上是直接从Kohana中K过来的),方便对数据进行校验。
system/classes文件夹下的类文件,都可以在app/classes文件夹下扩展,而且使用时不用做任何修改。假设你之前已经写了不少Resource,忽然想到要扩展系统的Resource类,正常的做法是定义一个MY_Resource之类的类文件来扩展系统的Resource类,然后使用时使用MY_Resource而不是Resource。但这样就会有个问题,之前使用的Resource类都要做修改了,可谓牵一发而动全身。RESTY就方便了,同样要扩展Resource类,只要在app/classes下新建一个resource.php文件,然后扩展Resty_Resource类即可。
class Resource extends Resty_Resource
{
public function foo()
{
//...
}
}
这样使用时还是一样的Resource类,但却多了foo方法。这也是从Kohana学到的无缝扩展大法(题外话:Kohana真是个不错的框架,各位不妨一试)。原理就是在类自动加载时会先去app/classes文件夹下去找,如果没找到的话再去system/classes下找。
作为一个比较完整的REST框架,Validation还是不能少的,为了不重复制造轮子,直接把Kohana的验证类搬了过来,稍作修改。
配置:config/validation.php
return array(
'example' => array(
'get' => array(
'filters' => array(
'id' => array(
'trim' => null,
),
),
'rules' => array(
'id' => array(
'not_empty' => null,
'min_length' => array(2),
'digit' => null,
),
),
),
),
);
错误提示:config/message.php
return array( 'example' => array( 'id' => array( 'digit' => 'id必须是数字', 'not_empty' => 'id不能为空', 'min_length' => 'id长度至少为:value', ), ), );
config文件如上面所示,就是返回一个数组。使用也很简单:
// 获取config/message.php文件的example key对应的内容
Config::get('message.example');
// 设置config(不会写入到文件,只在一个http request有效)
Config::set('message.example.id.digit', 'id can be anything');