starship/restfullyii

为您的 Yii 应用程序提供的 RESTFull API

dev-master 2017-12-04 16:45 UTC

This package is not auto-updated.

Last update: 2024-09-21 17:37:48 UTC


README

RestfullYii 让您轻松地为 Yii 项目添加 RESTFul API。RestfullYii 为您的资源提供完整的 HTTP 动词支持(GET、PUT、POST、DELETE),以及偏移量、限制、排序、过滤等功能。您还可以轻松地读取和操作相关数据。

RestfullYii 经过精心重构,现在 100% 测试覆盖!新的基于事件架构允许进行清洁且无限制的自定义。

工作原理

RestfullYii 为您的标准路由添加了一组新的 RESTFul 路由,但前面添加了 '/api'。

因此,如果您将 RestfullYii 应用于 'WorkController',您将默认获得以下新路由。

[GET] http://yoursite.com/api/work (returns all works)
[GET] http://yoursite.com/api/work/1 (returns work with PK=1)
[POST] http://yoursite.com/api/work (create new work)
[PUT] http://yoursite.com/api/work/1 (update work with PK=1)
[DELETE] http://yoursite.com/api/work/1 (delete work with PK=1)

要求

  • PHP 5.4.0(或更高版本)*
  • YiiFramework 1.1.14(或更高版本)
  • PHPUnit 3.7(或更高版本)以运行测试。

对于旧版本的 PHP(< 5.4),请检查 v1.15 或 cccsw 的惊人 5.3 端口:https://github.com/cccssw/RESTFullYii

安装

手动安装

  1. 下载并将 'starship' 目录放置在您的 Yii 扩展目录中。

  2. 在 config/main.php 中,您需要添加 RestfullYii 别名。这允许您灵活地放置扩展。

	'aliases' => array(
		.. .
        'RestfullYii' =>realpath(__DIR__ . '/../extensions/starship/RestfullYii'),
        .. .
	),
  1. 在您的主配置中包含 ext.starship.RestfullYii.config.routes(如下所示)或复制路由并将其粘贴到同一配置的 components->urlManager->rules 中。
	'components' => array(
		'urlManager' => array(
			'urlFormat' => 'path',
			'rules' => require(
				dirname(__FILE__).'/../extensions/starship/RestfullYii/config/routes.php'
			),
		),
	)

使用 Composer 安装

{
    "require": {
        "starship/restfullyii": "dev-master"
    }
}
  1. 在 config/main.php 中,您需要添加 RestfullYii 别名。这允许您灵活地放置扩展。
	'aliases' => array(
		.. .
		//Path to your Composer vendor dir plus starship/restfullyii path
		'RestfullYii' =>realpath(__DIR__ . '/../../../vendor/starship/restfullyii/starship/RestfullYii'),
        .. .
	),
  1. 在您的主配置中包含 ext.starship.RestfullYii.config.routes(如下所示)或复制路由并将其粘贴到同一配置的 components->urlManager->rules 中。
	'components' => array(
		'urlManager' => array(
			'urlFormat' => 'path',
			'rules' => require(
				dirname(__FILE__).'/../../../vendor/starship/restfullyii/starship/RestfullYii/config/routes.php
			),
		),
	)

##控制器设置 向控制器添加一组 RESTFul 动作。

  1. 将 ERestFilter 添加到您的控制器 filter 方法中。
public function filters()
{
		return array(
			'accessControl', // perform access control for CRUD operations
			array(
				'RestfullYii.filters.ERestFilter + 
			 	REST.GET, REST.PUT, REST.POST, REST.DELETE, REST.OPTIONS'
			),
		);
}
  1. 将 ERestActionProvider 添加到您的控制器 actions 方法中。
public function actions()
{
		return array(
			'REST.'=>'RestfullYii.actions.ERestActionProvider',
		);
}	
  1. 如果您正在使用 accessControl 过滤器,请确保允许所有 RESTFul 路由的访问。
public function accessRules()
{
		return array(
			array('allow', 'actions'=>array('REST.GET', 'REST.PUT', 'REST.POST', 'REST.DELETE', 'REST.OPTIONS'),
			'users'=>array('*'),
			),
			array('deny',  // deny all users
				'users'=>array('*'),
			),
		);
}

制作请求

要了解如何进行 RestfullYii API 请求,最好查看一些示例。首先将显示代码示例,首先是 JavaScript* 作为 AJAX 用户*,然后使用 CURL。

* JS 示例使用 jQuery

* 默认的 AJAX 用户验证为 !Yii::app()->user->isGuest,因此用户必须登录才能进行此类请求。

###GET 请求

获取资源列表(WorkController)

JavaScript

 $.ajax({
    url:'/api/work',
    type:"GET",
    success:function(data) {
      console.log(data);
    },
    error:function (xhr, ajaxOptions, thrownError){
      console.log(xhr.responseText);
    } 
  }); 

CURL

curl -i -H "Accept: application/json" -H "X-REST-USERNAME: admin@restuser" -H "X-REST-PASSWORD: admin@Access"\
http://my-site.com/api/work

响应

{
	"success":true,
	"message":"Record(s) Found",
	"data":{
		"totalCount":"30",
		"work":[
			{
				"id": "1",
                "title": "title1",
				"author_id": "1",
                "content": "content1",
                "create_time": "2013-08-07 10:09:41"
			},
			{
				"id": "2",
                "title": "title2",
				"author_id": "2",
                "content": "content2",
                "create_time": "2013-08-08 11:01:11"
			},
			. . .,
		]
	}
}	

获取单个资源(WorkController)

JavaScript

 $.ajax({
    url:'/api/work/1',
    type:"GET",
    success:function(data) {
      console.log(data);
    },
    error:function (xhr, ajaxOptions, thrownError){
      console.log(xhr.responseText);
    } 
  }); 

CURL

curl -i -H "Accept: application/json" -H "X-REST-USERNAME: admin@restuser" -H "X-REST-PASSWORD: admin@Access"\
http://my-site.com/api/work/1

响应

{
	"success":true,
	"message":"Record Found",
	"data":{
		"totalCount":"1",
		"work":[
			{
				"id": "1",
                "title": "title1",
				"author_id": "1",
                "content": "content1",
                "create_time": "2013-08-07 10:09:41"
			}
		]
	}
}	

GET 请求:限制 & 偏移量(WorkController)

您可以通过将限制和偏移量变量添加到请求查询字符串中来限制和分页结果。

JavaScript

 $.ajax({
    url:'/api/work?limit=10&offset=30',
    type:"GET",
    success:function(data) {
      console.log(data);
    },
    error:function (xhr, ajaxOptions, thrownError){
      console.log(xhr.responseText);
    } 
  }); 

CURL

curl -i -H "Accept: application/json" -H "X-REST-USERNAME: admin@restuser" -H "X-REST-PASSWORD: admin@Access"\
http://my-site.com/api/work?limit=10&offset=30

响应

{
	"success":true,
	"message":"Record(s) Found",
	"data":{
		"totalCount":"30",
		"work":[
			{
				"id": "11",
                "title": "title11",
				"author_id": "11",
                "content": "content11",
                "create_time": "2013-08-11 11:10:09"
			},
			{
				"id": "12",
                "title": "title12",
				"author_id": "12",
                "content": "content12",
                "create_time": "2013-08-08 12:11:10"
			},
			. . .,
		]
	}
}	

GET 请求:排序结果(WorkController)

您可以通过任何有效参数或多个参数以及提供排序方向(ASC 或 DESC)来对结果进行排序。sort=[{"property":"title", "direction":"DESC"}, {"property":"create_time", "direction":"ASC"}]

JavaScript

 $.ajax({
    url:'/api/work?sort=[{"property":"title", "direction":"DESC"}, {"property":"create_time", "direction":"ASC"}]',
    type:"GET",
    success:function(data) {
      console.log(data);
    },
    error:function (xhr, ajaxOptions, thrownError){
      console.log(xhr.responseText);
    } 
  }); 

CURL

curl -i -H "Accept: application/json" -H "X-REST-USERNAME: admin@restuser" -H "X-REST-PASSWORD: admin@Access"\
http://my-site.com/api/work?sort=%5B%7B%22property%22%3A%22title%22%2C+%22direction%22%3A%22DESC%22%7D%2C+%7B%22property%22%3A%22create_time%22%2C+%22direction%22%3A%22ASC%22%7D%5D

响应

{
	"success":true,
	"message":"Record(s) Found",
	"data":{
		"totalCount":"30",
		"work":[
			{
				"id": "29",
                "title": "title30b",
				"author_id": "29",
                "content": "content30b",
                "create_time": "2013-08-07 14:05:01"
			},
			{
				"id": "30",
                "title": "title30",
				"author_id": "30",
                "content": "content30",
                "create_time": "2013-08-08 09:10:09"
			},
			{
				"id": "28",
                "title": "title28",
				"author_id": "28",
                "content": "content28",
                "create_time": "2013-08-09 14:05:01"
			},
			. . .,
		]
	}
}	

GET 请求:过滤结果(WorkController)

您可以通过任何有效参数或多个参数以及一个操作符来过滤结果。

可用的过滤操作符

  • in
  • not in
  • =
  • !=
  • =

  • <
  • <=
  • 没有操作符是 "LIKE"
/api/post/?filter = [
  {"property": "id", "value" : 50, "operator": ">="}
, {"property": "user_id", "value" : [1, 5, 10, 14], "operator": "in"}
, {"property": "state", "value" : ["save", "deleted"], "operator": "not in"}
, {"property": "date", "value" : "2013-01-01", "operator": ">="}
, {"property": "date", "value" : "2013-01-31", "operator": "<="}
, {"property": "type", "value" : 2, "operator": "!="}
]

###POST 请求(创建新资源)使用 POST 请求时,必须在请求正文中包含资源数据作为 JSON 对象。

JavaScript

var postData = {
	"title": "title31",
	"author_id": "31",
	"content": "content31",
	"create_time": "2013-08-20 09:23:14"
};

 $.ajax({
	url:'/api/work',
	data:JSON.stringify(postData)
	type:"POST",
	success:function(data) {
		console.log(data);
	},
	error:function (xhr, ajaxOptions, thrownError){
		console.log(xhr.responseText);
	} 
}); 

CURL

curl -l -H "Accept: application/json" -H "X-REST-USERNAME: admin@restuser" -H "X-REST-PASSWORD: admin@Access"\
-X POST -d '{"title": "title31", "author_id": "31", "content": "content31", "create_time": "2013-08-20 09:23:14"}' http://my-site.com/api/work

响应

{
	"success":true,
	"message":"Record Created",
	"data":{
		"totalCount":"1",
		"work":[
			{
				"id": "31",
                "title": "title31",
				"author_id": "31",
                "content": "content31",
                "create_time": "2013-08-20 09:23:14"
			}
		]
	}
}	

### PUT 请求(更新现有资源)与 POST 请求类似,我们必须在请求体中包含资源数据,格式为 JSON 对象。

JavaScript

var postData = {
	"id": "31",
	"title": "title31",
	"author_id": "31",
	"content": "content31",
	"create_time": "2013-08-20 09:23:14"
};

 $.ajax({
	url:'/api/work/31',
	data:JSON.stringify(postData)
	type:"PUT",
	success:function(data) {
		console.log(data);
	},
	error:function (xhr, ajaxOptions, thrownError){
		console.log(xhr.responseText);
	} 
}); 

CURL

curl -l -H "Accept: application/json" -H "X-REST-USERNAME: admin@restuser" -H "X-REST-PASSWORD: admin@Access"\
-X PUT -d '{"id": "31", "title": "title31", "author_id": "31", "content": "content31", "create_time": "2013-08-20 09:23:14"}' http://my-site.com/api/work/31

响应

{
	"success":true,
	"message":"Record Updated",
	"data":{
		"totalCount":"1",
		"work":[
			{
				"id": "31",
                "title": "title31",
				"author_id": "31",
                "content": "content31",
                "create_time": "2013-08-20 09:23:14"
			}
		]
	}
}	

DELETE 请求(删除资源)

JavaScript

 $.ajax({
    url:'/api/work/1',
    type:"DELETE",
    success:function(data) {
      console.log(data);
    },
    error:function (xhr, ajaxOptions, thrownError){
      console.log(xhr.responseText);
    } 
  }); 

CURL

curl -l -H "Accept: application/json" -H "X-REST-USERNAME: admin@restuser" -H "X-REST-PASSWORD: admin@Access"\
"X-HTTP-Method-Override: DELETE" -X DELETE http://my-site.com/api/work/1

响应

{
	"success":true,
	"message":"Record Deleted",
	"data":{
		"totalCount":"1",
		"work":[
			{
				"id": "1",
                "title": "title1",
				"author_id": "1",
                "content": "content1",
                "create_time": "2013-08-07 10:09:41"
			}
		]
	}
}	

子资源

当处理“多对多”关系时,您现在可以将它们视为子资源。

考虑

URL Format: http://mysite.com/api/<controller>/<id>/<many_many_relation>/<many_many_relation_id>

Getting player 3 who is on team 1  
or simply checking whether player 3 is on that team (200 vs. 404)  
GET /api/team/1/players/3  

getting player 3 who is also on team 3  
GET /api/team/3/players/3  

Adding player 3 also to team 2  
PUT /api/team/2/players/3  

Getting all teams of player 3  
GET /api/player/3/teams  

Remove player 3 from team 1 (Injury)
DELETE /api/team/1/players/3  

Team 1 found a replacement, who is not registered in league yet  
POST /api/player  

From payload you get back the id, now place it officially to team 1  
PUT /api/team/1/players/44  

定制与配置

RestfullYii 的默认行为可以通过内置的事件系统轻松定制。RestFullYii 的请求/响应处理几乎触发了所有方面的行为。更改 RestfullYii 的行为就像注册适当的事件处理器一样简单。事件处理器可以全局(在主配置中)和本地(在控制器级别)注册。

为了理解如何实现这一点,让我们创建一个需要一些定制的场景,并看看我们如何实现它。

假设我们在 API 中有两个控制器,WorkController 和 CategoryController。我们希望我们的 API 以以下方式运行

  1. API 应通过 AJAX 对 JavaScript 可用。
  2. API 应对任何外部客户端不可用。
  3. 只有注册用户才能查看工作和分类资源。
  4. 只有具有权限 REST-UPDATE 的用户才能更新作品。
  5. 只有具有权限 REST-CREATE 的用户才能创建作品。
  6. 只有具有权限 REST-DELETE 的用户才能删除作品。
  7. 对所有 API 用户禁止对分类进行创建、更新和删除。

现在我们知道了我们希望我们的 API 如何运行,让我们看一下上面的列表,并确定哪些功能可以在全局实现。由于 1、2 和 3 影响我们的两个控制器,我们可以通过在 config/main.php 中注册一些回调来全局实现这些功能。

为了实现 1 和 3,我们不需要做任何事情,因为这是 RestfullYii 的默认行为,所以我们留下 2。默认情况下,RestfullYii 允许外部客户端访问,这不是我们想要的。让我们改变它!

在 /protected/config/main.php 的 params 部分,我们将添加以下内容

$config = array(
	..,
	'params'=>[
		'RestfullYii' => [
			'req.auth.user'=>function($application_id, $username, $password) {
				return false;
			},
		]
	]
);

这告诉 RestfullYii,当处理 'req.auth.user' 事件时,应始终返回 false。返回 false 将拒绝访问(true 允许)。同样,验证 AJAX 用户有自己的事件 'req.auth.ajax.user',如前所述,默认情况下允许注册用户访问。

这处理了我们的全局配置,现在我们可以专注于功能 4-7,同时注意不要破坏功能 3。由于功能 4-6 涉及工作控制器和用户权限,我们可以同时完成所有这些。记住,RestfullYii 的默认行为是允许所有注册用户完全访问 API,这并不是我们想要的。让我们改变它!

现在,我们将局部注册事件处理器在 WorkController 中;为此,我们需要在 WorkController 中添加一个特殊公共方法,称为 'restEvents'。然后一旦我们有了 'restEvents' 方法,我们可以使用另一个特殊方法 'onRest' 来注册我们的事件处理器。我们可以使用 '$this->onRest()' 调用 'onRest'。'onRest' 接受两个参数,第一个是事件名称,第二个是实际处理事件的 Callable。这个 Callable 绑定到控制器,因此 Callable 中的 $this 上下文始终引用当前控制器。这对于全局和本地注册的事件处理器都适用。

现在我们准备修改事件处理器 'req.auth.ajax.user' 的输出,但这次不是覆盖默认行为,而是使用后过滤功能添加我们的附加用户验证。后过滤事件的名称总是以 'post.filter.' 前缀的主事件名称,因此在我们的情况下,事件名称是 'post.filter.req.auth.ajax.user'。传递到后过滤处理器的参数始终是主事件返回的值。看一下

class WorkController extends Controller
{
	.. .
	
	public function restEvents()
	{
		$this->onRest('post.filter.req.auth.ajax.user', function($validation) {
			if(!$validation) {
				return false;
			}
			switch ($this->getAction()->getId()) {
				case 'REST.POST':
					return Yii::app()->user->checkAccess('REST-CREATE');
					break;
				case 'REST.PUT':
					return Yii::app()->user->checkAccess('REST-UPDATE');
					break;
				case 'REST.DELETE':
					return Yii::app()->user->checkAccess('REST-DELETE');
					break;
				default:
					return false;
					break;
			}
		});
	}
	
	.. .
}

太棒了!这只剩下功能7,即不允许在类别上创建、更新、删除。我们再次将此更改本地添加,但这次是添加到CategoryController中。看看

class CategoryController extends Controller
{
	.. .
	
	public function restEvents()
	{
		$this->onRest('post.filter.req.auth.ajax.user', function($validation) {
			if(!$validation) {
				return false;
			}
			return ($this->getAction()->getId() == 'REST.GET');
		});
	}
	
	.. .
}

现在我们已经实现了所有功能!

自定义路由定义

自定义路由定义非常简单,您只需为您的路由和HTTP动词组合创建一个事件处理器(事件名称 = 'req.<verb>.<route_name>.render')。让我们看看一些示例。

以下是我们要添加到我们的API中的路由列表

  1. [GET] /api/category/active

  2. [GET] /api/work/special/<param1>

  3. [PUT] /api/work/special/<param1>/<param2>

  4. [POST] /api/work/special/<param1>

  5. [DELETE] /api/work/special/<param1>/<param2>

自定义路由1

从路由中可以看出,请求将由Category控制器处理。因此,我们将在这里添加我们的事件处理器。

class CategoryController extends Controller
{
	.. .
	public function restEvents()
    {
    	$this->onRest('req.get.active.render', function() {
    		//Custom logic for this route.
    		//Should output results.
    		$this->emitRest('req.render.json', [
    			[
    				'type'=>'raw',
    				'data'=>['active'=>true]
    			]
    		]);
		});
	}
}

自定义路由2-5

这些路由都涉及Work控制器。因此,我们将在这里添加我们的事件处理器。

class WorkController extends Controller
{
	.. .
	
	public function restEvents()
	{
		$this->onRest('req.get.special.render', function($param1) {
			echo CJSON::encode(['param1'=>$param1]);
		});
		
		$this->onRest('req.put.special.render', function($data, $param1, $param2) {
			//$data is the data sent in the PUT
			echo CJSON::encode(['data'=>$data, $param1, $param2]);
		});
		
		$this->onRest('req.post.special.render', function($data, $param1) {
			//$data is the data sent in the POST
			echo CJSON::encode(['data'=>$data, 'param1'=>$param1, 'param2'=>$param2]);
		});
		
		$this->onRest('req.delete.special.render', function($param1, $param2) {
			echo CJSON::encode(['param1'=>$param1, 'param2'=>$param2]);
		});
	}

##覆盖和添加默认模型属性我们可以更改任何给定请求中模型(s)输出的属性。我们可以使用“model.YOUR_MODEL_NAME_HERE.override.attributes”事件来完成此操作。假设我们有一个名为“Work”的模型,并且我们希望每次引用该模型时都将其非AR属性“url”添加到我们的JSON输出中。

$this->onRest('model.work.override.attributes', function($model) {
   return array_merge($model->attributes, ['url'=>"http://www.mysite.com/media/{$model->id}.jpg"]);
});
  • 您也可以使用此相同的事件来删除或覆盖默认AR属性

##后过滤渲染事件可以通过“req.[get,put,post,delete].[resource, resources].render”事件后过滤模型输出的输出。这将允许您完全更改输出,以满足您的需求。

$this->onRest('post.filter.req.get.resources.render', function($json) {
   $j = CJSON::decode($json);
   $j['data'] = array_map(function($work_data){
      $work = Work::model();
      $work->setAttributes($work_data);
      $work_data['url'] = $work->url;
      return $work_data;
   }, $j['data'])
   return $j;
});

##CORS请求(跨源资源共享)

现在,使用RESTFullYii可以从JavaScript进行跨源请求!RESTFullYii有几个特定的CORS事件,可以帮助轻松进行CORS请求。

假设您有以下设置;一个具有域名http://rest-api-server.back的API服务器和一个位于域名http://my-js-client-app.front的客户端应用程序。显然,您希望从您的前端客户端应用程序(my-js-client-app.front)向您的后端API服务器(rest-api-server.back)发送请求。

#####1) 您首先需要让RESTFullYii知道my-js-client-app.front可以请求给定的资源或资源。假设我们只想允许我们的应用程序请求“Artist”资源。因此,让我们在ArtistController中添加一个事件钩子

class ArtistController extends Controller
{
	[...]
	
	public function restEvents()
	{
		$this->onRest('req.cors.access.control.allow.origin', function() {
			return ['http://my-js-client-app.front']; //List of sites allowed to make CORS requests 
		});
	}
}

如果您想允许my-js-client-app.front访问其他资源,您只需在适当的控制器(s)中重复此过程或在主配置参数中全局应用它(见上述示例)。

#####2) 我们需要确定我们想要允许的请求类型。默认情况下,RESTFullYii将允许GET & POST,但对我们来说,我们还想允许PUT & DELETE。

class ArtistController extends Controller
{
	[...]
	
	public function restEvents()
	{
		$this->onRest('req.cors.access.control.allow.origin', function() {
			return ['http://my-js-client-app.front']; //List of sites allowed to make CORS requests 
		});
		
		$this->onRest('req.cors.access.control.allow.methods', function() {
			return ['GET', 'POST', 'PUT', 'DELETE']; //List of allowed http methods (verbs) 
		});
	}
}

这是最小配置,但还有更多可用;请参阅(《req.cors.access.control.allow.headers》、《req.cors.access.control.max.age》、《req.auth.cors》)

#####3) 现在服务器已经设置好以允许CORS请求,我们可以从客户端http://my-js-client-app.front发送请求了

$.ajax({
	type: "GET",
	url: "http://rest-api-server.back/api/artist",
	headers: {
		'X_REST_CORS': 'Yes',
	},
	success: function( response ){
		console.log(response);
	},
	error: function( error ){
		console.log( "ERROR:", error );
	}
});

######*注意上面的jQuery请求中的headers对象。您必须发送X_REST_CORS: 'Yes'

####就这样!您正在制作甜美的CORS!

事件

所有事件及其默认事件处理器的列表。

###config.application.id

/**
 * config.application.id
 *
 * returns the app id that is applied to header vars (username, password)
 *
 * @return (String) default is 'REST'
 */
$this->onRest('config.application.id', function() {
	return 'REST';
});

####pre.filter.config.application.id

$this->onRest('pre.filter.config.application.id', function() {
	//no return
});

####post.filter.config.application.id

$this->onRest('post.filter.config.application.id', function($app_id) {
	return $app_id; //String
});

###config.dev.flag

/**
 * config.dev.flag
 *
 * return true to set develop mode; false to turn of develop mode
 *
 * @return (bool) true by default
 */
$this->onRest('config.dev.flag', function() {
	return true;
});

####pre.filter.config.dev.flag

$this->onRest('pre.filter.config.dev.flag', function() {
	//no return
});

####post.filter.config.dev.flag

$this->onRest('post.filter.config.dev.flag', function($flag) {
	return $flag; //Bool
});

###req.event.logger

/**
* req.event.logger
*
* @param (String) (event) the event to log
* @param (String) (category) the log category
* @param (Array) (ignore) Events to ignore logging
*
* @return (Array) the params sent into the event
*/
$this->onRest('req.event.logger', function($event, $category='application',$ignore=[]) {
	if(!isset($ignore[$event])) {
		Yii::trace($event, $category);
	}
	return [$event, $category, $ignore];
});

###req.disable.cweblogroute

/**
 * req.disable.cweblogroute
 *
 * this is only relevant if you have enabled CWebLogRoute in your main config
 *
 * @return (Bool) true (default) to disable CWebLogRoute, false to allow
 */ 
$this->onRest('req.disable.cweblogroute', function(){
	return true;
});

####pre.filter.req.disable.cweblogroute

$this->onRest('pre.filter.req.disable.cweblogroute', function() {
	//no return
});

####post.filter.req.disable.cweblogroute

$this->onRest('post.filter.req.disable.cweblogroute', function($disable) {
	return $disable; //Bool
});

###req.auth.https.only

/**
 * req.auth.https.only
 *
 * return true to restrict to https;
 * false to allow http or https
 *
 * @return (bool) default is false
 */ 
$this->onRest('req.auth.https.only', function() {
	return false;
});

####pre.filter.req.auth.https.only

$this->onRest('pre.filter.req.auth.https.only', function() {
	//no return
});

####post.filter.req.auth.https.only

$this->onRest('post.filter.req.auth.https.only', function($https_only) {
	return $https_only; //Bool
});

###req.auth.username

/**
 * req.auth.username
 *
 * This is the username used to grant access to non-ajax users
 * At a minimum you should change this value
 *
 * @return (String) the username
 */ 
$this->onRest('req.auth.username', function(){
	return 'admin@restuser';
});

####pre.filter.req.auth.username

$this->onRest('pre.filter.req.auth.username', function() {
	//no return
});

####post.filter.req.auth.username

$this->onRest('post.filter.req.auth.username', function($username) {
	return $username; //String
});

###req.auth.password

/**
 * req.auth.password
 *
 * This is the password use to grant access to non-ajax users
 * At a minimum you should change this value
 *
 * @return (String) the password
 */ 
$this->onRest('req.auth.password', function(){
	return 'admin@Access';
});

####pre.filter.req.auth.password

$this->onRest('pre.filter.req.auth.password', function() {
	//no return
});

####post.filter.req.auth.password

$this->onRest('post.filter.req.auth.password', function($password) {
	return $password; //String
});

###req.auth.user

/**
 * req.auth.user
 *
 * Used to validate a non-ajax user
 *
 * @param (String) (application_id) the application_id defined in config.application.id
 * @param (String) (username) the username defined in req.auth.username
 * @param (String) (password) the password defined in req.auth.password
 *
 * @return (Bool) true to grant access; false to deny access
 */ 
$this->onRest('req.auth.user', function($application_id, $username, $password) {
	if(!isset($_SERVER['HTTP_X_'.$application_id.'_USERNAME']) || !isset($_SERVER['HTTP_X_'.$application_id.'_PASSWORD'])) {
				return false;
			}
	if( $username != $_SERVER['HTTP_X_'.$application_id.'_USERNAME'] ) {
		return false;
	}
	if( $password != $_SERVER['HTTP_X_'.$application_id.'_PASSWORD'] ) {
		return false;
	}
	return true;
});

####pre.filter.req.auth.user

$this->onRest('pre.filter.req.auth.user', function($application_id, $username, $password) {
	return [$application_id, $username, $password]; //Array [String, String, String]
});

####post.filter.req.auth.user

$this->onRest('post.filter.req.auth.user', function($validation) {
	return $validation; //Bool
});

###req.auth.ajax.user

/**
 * req.auth.ajax.user
 *
 * Used to validate an ajax user
 * That is requests originating from JavaScript
 * By default all logged-in users will be granted access
 * everyone else will be denied
 * You should most likely over ride this
 *
 * @return (Bool) return true to grant access; false to deny access
 */ 
$this->onRest('req.auth.ajax.user', function() {
	if(Yii::app()->user->isGuest) {
		return false;
	}
	return true;
});

####pre.filter.req.auth.ajax.user

$this->onRest('pre.filter.req.auth.ajax.user', function() {
	//no return
});

####post.filter.req.auth.ajax.user

$this->onRest('post.filter.req.auth.ajax.user', function($validation) {
	return $validation; //Bool
});

###req.auth.type

/**
 * req.auth.type
 *
 * @return (Int) The request authentication type which may be 'USERPASS' (2), 'AJAX' (3) or 'CORS' (1)
 */
$this->onRest('req.auth.type', function($application_id) {
	if(isset($_SERVER['HTTP_X_'.$application_id.'_CORS']) || (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] == 'OPTIONS')) {
		return ERestEventListenerRegistry::REQ_TYPE_CORS;
	} else if(isset($_SERVER['HTTP_X_'.$application_id.'_USERNAME']) && isset($_SERVER['HTTP_X_'.$application_id.'_PASSWORD'])) {
		return ERestEventListenerRegistry::REQ_TYPE_USERPASS;
	} else {
		return ERestEventListenerRegistry::REQ_TYPE_AJAX;
	}
});

####pre.filter.req.auth.type

$this->onRest('pre.filter.req.auth.type', function($application_id) {
	return $application_id; //String
});

####post.filter.req.auth.type

$this->onRest('post.filter.config.application.id', function($auth_type) {
	return $auth_type; //Int
});

###req.cors.access.control.allow.origin

/**
 * req.cors.access.control.allow.origin
 *
 * Used to validate a CORS request
 *
 * @return (Array) return a list of domains (origin) allowed access
 */
$this->onRest('req.cors.access.control.allow.origin', function() {
	return []; //Array
});

####pre.filter.req.cors.access.control.allow.origin

$this->onRest('pre.filter.req.cors.access.control.allow.origin', function() {
	//No Return
});

####post.filter.req.cors.access.control.allow.origin

$this->onRest('post.filter.req.cors.access.control.allow.origin', function($allowed_origins) {
	return $allowed_origins; //Array
});

###req.cors.access.control.allow.methods

/**
 * req.cors.access.control.allow.methods
 *
 * Used by CORS request to indicate the http methods (verbs) 
 * that can be used in the actual request
 *
 * @return (Array) List of http methods allowed via CORS
 */
$this->onRest('req.cors.access.control.allow.methods', function() {
	return ['GET', 'POST'];
});

####pre.filter.req.cors.access.control.allow.methods

$this->onRest('pre.filter.req.cors.access.control.allow.methods', function() {
	//No Return
});

####post.filter.req.cors.access.control.allow.methods

$this->onRest('post.filter.req.cors.access.control.allow.methods', function($allowed_methods) {
	return $allowed_methods; //Array
});

###req.cors.access.control.allow.headers

/**
 * req.cors.access.control.allow.headers
 *
 * Used by CORS request to indicate which custom headers are allowed in a request
 *
 * @return (Array) List of allowed headers
 */ 
$this->onRest('req.cors.access.control.allow.headers', function($application_id) {
	return ["X_{$application_id}_CORS"];
});

####pre.filter.req.cors.access.control.allow.headers

$this->onRest('pre.filter.req.cors.access.control.allow.headers', function() {
	//No Return
});

####post.filter.req.cors.access.control.allow.headers

$this->onRest('post.filter.req.cors.access.control.allow.headers', function($allowed_headers) {
	return $allowed_headers; //Array
});

###req.cors.access.control.max.age

/**
 * req.cors.access.control.max.age
 *
 * Used in a CORS request to indicate how long the response can be cached, 
 * so that for subsequent requests, within the specified time, no preflight request has to be made
 *
 * @return (Int) time in seconds
 */
$this->onRest('req.cors.access.control.max.age', function() {
	return 3628800; //Int
});

####pre.filter.req.cors.access.control.max.age

$this->onRest('pre.filter.req.cors.access.control.max.age', function() {
	//No Return
});

####post.filter.req.cors.access.control.max.age

$this->onRest('post.filter.req.cors.access.control.max.age', function($max_age_in_seconds) {
	return $max_age_in_seconds; //int
});

###req.auth.cors

/**
 * req.auth.cors
 *
 * Used to authorize a given CORS request
 *
 * @param (Array) (allowed_origins) list of allowed remote origins
 *
 * @return (Bool) true to allow access and false to deny access
 */
$this->onRest('req.auth.cors', function ($allowed_origins) {
	if((isset($_SERVER['HTTP_ORIGIN'])) && (( array_search($_SERVER['HTTP_ORIGIN'], $allowed_origins)) !== false )) {
		return true;
	}
	return false;	
});

####pre.filter.req.auth.cors

$this->onRest('pre.filter.req.auth.cors', function($allowed_origins) {
	return $allowed_origins; //Array
});

####post.filter.req.auth.cors

$this->onRest('post.filter.config.application.id', function($auth_cors) {
	return $auth_cors; //Bool
});

###req.auth.uri

/**
 * req.auth.uri
 *
 * return true to allow access to a given uri / http verb;
 * false to deny access to a given uri / http verb;
 *
 * @return (bool) default is true
 */ 
$this->onRest(req.auth.uri, function($uri, $verb) {
	return true;
});

####pre.filter.req.auth.uri

$this->onRest('pre.filter.req.auth.uri', function($uri, $verb) {
	return [$uri, $verb]; //array[string, string]
});

####post.filter.req.auth.uri

$this->onRest('post.filter.req.auth.uri, function($validation) {
	return $validation; //bool
});

####req.after.action

/**
 * req.after.action
 *
 * Called after the request has been fulfilled
 * By default it has no behavior
 */ 
$this->onRest('req.after.action', function($filterChain) {
	//Logic being applied after the action is executed
});

####pre.filter.req.after.action

$this->onRest('pre.filter.req.after.action', function($filterChain) {
	return $filterChain; //Object
});

####post.filter.req.after.action

$this->onRest('post.filter.after.action', function($result=null) {
	return $result; //Mixed
});

###req.param.is.pk

/**
 * req.param.is.pk
 *
 * Called when attempting to validate a resources primary key
 * The default is an integer
 *
 * @return (bool) true to confirm primary key; false to deny
 */
$this->onRest('req.param.is.pk', function($pk) {
	return $pk === '0' || preg_match('/^-?[1-9][0-9]*$/', $pk) === 1;
});

####pre.filter.req.param.is.pk

$this->onRest('pre.filter.req.param.is.pk', function($pk) {
	return $pk; //Mixed
});

####post.filter.req.param.is.pk

$this->onRest('post.filter.req.param.is.pk', function($isPk) {
	return $isPk; //Bool
});

###req.is.subresource

/**
 * req.is.subresource
 * 
 * Called when trying to determain if the request is for a subresource
 * WARNING!!!: ONLY CHANGE THIS EVENTS BEHAVIOR IF YOU REALLY KNOW WHAT YOUR DOING!!!
 * WARNING!!!: CHANGING THIS MAY LEAD TO INCONSISTENT AND OR INCORRECT BEHAVIOR
 *
 * @param (Object) (model) model instance to evaluate
 * @param (String) (subresource_name) potentially the name of the subresource
 * @param (String) (http_verb) the http verb used to make the request
 *
 * @return (Bool) True if this is a subresouce request and false if not
 */ 
$onRest('req.is.subresource', function($model, $subresource_name, $http_verb) {
	if(!array_key_exists($subresource_name, $model->relations())) {
		return false;
	}
	if($model->relations()[$subresource_name][0] != CActiveRecord::MANY_MANY) {
		return false;
	}
	return true;
});

####pre.filter.req.is.subresource

$this->onRest('pre.filter.req.param.is.pk', function($model, $subresource_name, $http_verb) {
	return [$model, $subresource_name, $http_verb]; //Array
});

####post.filter.req.is.subresource

$this->onRest('post.filter.req.is.subresource', function($is_subresource) {
	return $is_subresource; //Bool
});

###req.data.read

/**
 * req.data.read
 *
 * Called when reading data on POST & PUT requests
 *
 * @param (String) this can be either a stream wrapper of a file path
 *
 * @return (Array) the JSON decoded array of data
 */ 
$this->onRest('req.data.read', function($stream='php://input') {
	$reader = new ERestRequestReader($stream);
	return CJSON::decode($reader->getContents());
});

####pre.filter.req.data.read

$this->onRest('pre.filter.req.data.read, function($stream='php://input') {
	return $stream; //Mixed
});

####post.filter.req.data.read

$this->onRest('post.filter.req.data.read', function($data) {
	return [$data]; //Array [Array]
});

###req.get.resource.render

/**
 * req.get.resource.render
 *
 * Called when a GET request for a single resource is to be rendered
 * @param (Object) (data) this is the resources model
 * @param (String) (model_name) the name of the resources model
 * @param (Array) (relations) the list of relations to include with the data
 * @param (Int) (count) the count of records to return (will be either 1 or 0)
 */
$this->onRest('req.get.resource.render', function($data, $model_name, $relations, $count, $visibleProperties=[], $hiddenProperties=[]) {
	//Handler for GET (single resource) request
	$this->setHttpStatus((($count > 0)? 200: 204));
	$this->renderJSON([
		'type'				=> 'rest',
		'success'			=> (($count > 0)? true: false),
		'message'			=> (($count > 0)? "Record Found": "No Record Found"),
		'totalCount'		=> $count,
		'modelName'			=> $model_name,
		'relations'			=> $relations,
		'visibleProperties'	=> $visibleProperties,
		'hiddenProperties'	=> $hiddenProperties,
		'data'				=> $data,
	]);
});

####pre.filter.req.get.resource.render

$this->onRest('pre.filter.req.get.resource.render', function($data, $model_name, $relations, $count, $visibleProperties=[], $hiddenProperties=[]) {
	return [$data, $model_name, $relations, $count, $visibleProperties, $hiddenProperties]; //Array [Object, String, Array, Int, Array[String], Array[String]]
});

####post.filter.req.get.resource.render

/*
 * @param (JSON String) $json
 */
$this->onRest('post.filter.req.get.resource.render', function($json) {
	return $json //Mixed[JSON Sting, ARRAY]
});

####req.get.resources.render

/**
 * req.get.resources.render
 *
 * Called when a GET request for when a list resources is to be rendered
 *
 * @param (Array) (data) this is an array of models representing the resources
 * @param (String) (model_name) the name of the resources model
 * @param (Array) (relations) the list of relations to include with the data
 * @param (Int) (count) the count of records to return
 */
$this->onRest('req.get.resources.render', function($data, $model_name, $relations, $count, $visibleProperties=[], $hiddenProperties=[]) {
	//Handler for GET (list resources) request
	$this->setHttpStatus((($count > 0)? 200: 204));
	$this->renderJSON([
		'type'				=> 'rest',
		'success'			=> (($count > 0)? true: false),
		'message'			=> (($count > 0)? "Record(s) Found": "No Record(s) Found"),
		'totalCount'		=> $count,
		'modelName'			=> $model_name,
		'relations'			=> $relations,
		'visibleProperties'	=> $visibleProperties,
		'hiddenProperties'	=> $hiddenProperties,
		'data'				=> $data,
	]);
});

####pre.filter.req.get.resources.render

$this->onRest('pre.filter.req.get.resources.render', function($data, $model_name, $relations, $count, $visibleProperties, $hiddenProperties) {
	return [$data, $model_name, $relations, $count, $visibleProperties, $hiddenProperties]; //Array [Array [Object], String, Array, Int, Array[String], Array[String]]
});

####post.filter.req.get.resources.render

/*
 * @param (JSON String) $json
 */
$this->onRest('post.filter.req.get.resources.render', function($json) {
	return $json //Mixed[JSON Sting, ARRAY]
});

###req.put.resource.render

/**
 * req.put.resource.render
 * 
 * Called when a PUT request (update) is to be rendered
 *
 * @param (Object) (model) the updated model
 * @param (Array) (relations) list of relations to render with model
 */
$this->onRest('req.put.resource.render' function($model, $relations, $visibleProperties=[], $hiddenProperties=[]) {
	$this->renderJSON([
		'type'				=> 'rest',
		'success'			=> 'true',
		'message'			=> "Record Updated",
		'totalCount'	=> "1",
		'modelName'		=> get_class($model),
		'relations'		=> $relations,
		'visibleProperties'	=> $visibleProperties,
		'hiddenProperties'	=> $hiddenProperties,
		'data'				=> $model,
	]);
});

####pre.filter.req.put.resource.render

$this->onRest('pre.filter.req.req.put.resource.render', function($model, $relations, $visibleProperties=[], $hiddenProperties=[]) {
	return [$model, $relations, $visibleProperties, $hiddenProperties]; //Array [Object, Array, Array[String], Array[String]]
});

####post.filter.req.put.resource.render

/*
 * @param (JSON String) $json
 */
$this->onRest('post.filter.req.put.resource.render', function($json) {
	return $json //Mixed[JSON Sting, ARRAY]
});

###req.post.resource.render

/**
 * req.post.resource.render
 * 
 * Called when a POST request (create) is to be rendered
 *
 * @param (Object) (model) the newly created model
 * @param (Array) (relations) list of relations to render with model
 */
$this->onRest('req.post.resource.render', function($model, $relations=[], $visibleProperties=[], $hiddenProperties=[]) {
	$this->renderJSON([
		'type'				=> 'rest',
		'success'			=> 'true',
		'message'			=> "Record Created",
		'totalCount'	=> "1",
		'modelName'		=> get_class($model),
		'relations'		=> $relations,
		'visibleProperties'	=> $visibleProperties,
		'hiddenProperties'	=> $hiddenProperties,
		'data'				=> $model,
	]);
});

####pre.filter.req.post.resource.render

$this->onRest('pre.filter.req.post.resource.render', function($model, $relations, $visibleProperties=[], $hiddenProperties=[]) {
	return [$model, $relations, $visibleProperties, $hiddenProperties]; //Array [Object, Array, Array[String], Array[String]]
});

####post.filter.req.post.resource.render

/*
 * @param (JSON String) $json
 */
$this->onRest('post.filter.req.post.resource.render', function($json) {
	return $json //Mixed[JSON Sting, ARRAY]
});

###req.delete.resource.render

/**
 * req.delete.resource.render
 *
 * Called when DELETE request is to be rendered
 *
 * @param (Object) (model) this is the deleted model object for the resource
 */
$this->onRest('req.delete.resource.render', function($model, $visibleProperties=[], $hiddenProperties=[]) {
	$this->renderJSON([
		'type'				=> 'rest',
		'success'			=> 'true',
		'message'			=> "Record Deleted",
		'totalCount'		=> "1",
		'modelName'			=> get_class($model),
		'relations'			=> [],
		'visibleProperties'	=> $visibleProperties,
		'hiddenProperties'	=> $hiddenProperties,
		'data'				=> $model,
	]);
});

####req.delete.resource.render

$this->onRest('pre.filter.req.delete.resource.render', function($model, $visibleProperties=[], $hiddenProperties=[]) {
	return[$model, $visibleProperties, $hiddenProperties]; //Array[Object, Array[String], Array[String]]
});

####post.filter.req.get.resource.render

/*
 * @param (JSON String) $json
 */
$this->onRest('post.filter.req.get.resource.render', function($json) {
	return $json //Mixed[JSON Sting, ARRAY]
});

###req.get.subresource.render

/**
 * req.get.subresource.render
 *
 * Called when a GET request for a sub-resource is to be rendered
 *
 * @param (Object) (model) the model representing the sub-resource
 * @param (String) (subresource_name) the name of the sub-resource to render
 * @param (Int) (count) the count of sub-resources to render (will be either 1 or 0)
 */
$this->onRest('req.get.subresource.render', function($model, $subresource_name, $count, $visibleProperties, $hiddenProperties) {
	$this->setHttpStatus((($count > 0)? 200: 204));

	$this->renderJSON([
		'type'				=> 'rest',
		'success'			=> true,
		'message'			=> (($count > 0)? "Record(s) Found": "No Record(s) Found"),
		'totalCount'		=> $count,
		'modelName'			=> $subresource_name,
		'visibleProperties'	=> $visibleProperties,
		'hiddenProperties'	=> $hiddenProperties,
		'data'				=> $model,
	]);
});

####pre.filter.req.delete.subresource.render

$this->onRest('pre.filter.req.delete.subresource.render', function($model, $subresource_name, $count, $visibleProperties=[], $hiddenProperties=[]) {
	return [$model, $subresource_name, $count, $visibleProperties, $hiddenProperties]; //Array [Object, String, Int, Array[String], Array[String]]
});

###req.get.subresources.render

/**
 * req.get.subresources.render
 *
 * Called when a GET request for a list of sub-resources is to be rendered
 *
 * @param (Array) (models) list of sub-resource models
 * @param (String) (subresource_name) the name of the sub-resources to render
 * @param (Int) (count) the count of sub-resources to render
 */
$this->onRest('req.get.subresources.render', function($models, $subresource_name, $count, $visibleProperties=[], $hiddenProperties=[]) {
	$this->setHttpStatus((($count > 0)? 200: 204));
	
	$this->renderJSON([
		'type'				=> 'rest',
		'success'			=> (($count > 0)? true: false),
		'message'			=> (($count > 0)? "Record(s) Found": "No Record(s) Found"),
		'totalCount'		=> $count,
		'modelName'			=> $subresource_name,
		'visibleProperties'	=> $visibleProperties,
		'hiddenProperties'	=> $hiddenProperties,
		'data'				=> $models,
	]);
});

####pre.filter.req.get.subresources.render

$this->onRest('pre.filter.req.get.subresources.render', function($models, $subresource_name, $count, $visibleProperties=[], $hiddenProperties=[]) {
	return [$models, $subresource_name, $count, $visibleProperties, $hiddenProperties]; //Array [Array[Object], String, Int, Array[String], Array[String]]
});

####post.filter.req.get.subresources.render

/*
 * @param (JSON String) $json
 */
$this->onRest('post.filter.req.get.subresources.render', function($json) {
	return $json //Mixed[JSON Sting, ARRAY]
});

###req.put.subresource.render

/**
 * req.put.subresource.render
 *
 * Called when a PUT request to a sub-resource (add a sub-resource) is rendered
 *
 * @param (Object) (model) the model of the resource that owns the subresource
 * @param (String) (subresource_name) the name of the sub-resource
 * @param (Mixed/Int) (subresource_id) the primary key of the sub-resource
 */
$this->onRest('req.put.subresource.render', function($model, $subresource_name, $subresource_id, $visibleProperties=[], $hiddenProperties=[]) {
	$this->renderJSON([
		'type'				=> 'rest',
		'success'			=> 'true',
		'message'			=> "Subresource Added",
		'totalCount'		=> "1",
		'modelName'			=> get_class($model),
		'relations'			=> [$subresource_name],
		'visibleProperties'	=> $visibleProperties,
		'hiddenProperties'	=> $hiddenProperties,
		'data'				=> $model,
	]);
});

####pre.filter.req.put.subresource.render

$this->onRest('pre.filter.req.put.subresource.render', function($model, $subresource_name, $subresource_id, $visibleProperties=[], $hiddenProperties=[]) {
	return [$model, $subresource_name, $subresource_id, $visibleProperties, $hiddenProperties]; //Array [Object, String, Int, Array[String], Array[String]]
});

####post.filter.req.put.subresources.render

/*
 * @param (JSON String) $json
 */
$this->onRest('post.filter.req.put.subresources.render', function($json) {
	return $json //Mixed[JSON Sting, ARRAY]
});

###req.delete.subresource.render

/**
 * req.delete.subresource.render
 *
 * Called when DELETE request on a sub-resource is to be made
 *
 * @param (Object) (model) this is the model object that owns the deleted sub-resource
 * @param (String) (subresource_name) the name of the deleted sub-resource
 * @param (Mixed/Int) (subresource_id) the primary key of the deleted sub-resource
 */
$this->onRest('req.delete.subresource.render', function($model, $subresource_name, $subresource_id, $visibleProperties=[], $hiddenProperties=[]) {
	$this->renderJSON([
		'type'				=> 'rest',
		'success'			=> 'true',
		'message'			=> "Sub-Resource Deleted",
		'totalCount'		=> "1",
		'modelName'			=> get_class($model),
		'relations'			=> [$subresource_name],
		'visibleProperties'	=> $visibleProperties,
		'hiddenProperties'	=> $hiddenProperties,
		'data'				=> $model,
	]);
});

####pre.filter.req.delete.subresource.render

$this->onRest('pre.filter.req.delete.subresource.render', function($model, $subresource_name, $subresource_id, $visibleProperties=[], $hiddenProperties=[]) {
	return [$model, $subresource_name, $subresource_id, $visibleProperties, $hiddenProperties]; //Array [Object, String, Int, Array[String], Array[String]]
});

####post.filter.req.delete.subresources.render

/*
 * @param (JSON String) $json
 */
$this->onRest('post.filter.req.delete.subresources.render', function($json) {
	return $json //Mixed[JSON Sting, ARRAY]
});

###req.render.json

/**
 * req.render.json
 * NOT CALLED internally
 * The handler exists to allow users the ability to easily render arbitrary JSON
 * To do so you must 'emitRest' this event
 *
 * @param (Array) (data) data to be rendered
 */
$this->onRest('req.render.json', function($data) {
	$this->renderJSON([
		'type' => 'raw',
		'data' => $data,
	]);
});

####pre.filter.req.render.json

$this->onRest('pre.filter.req.render.json', function($data) {
	return [$data]; //Array[Array]
});

###req.exception

/**
 * req.exception
 *
 * Error handler called when an Exception is thrown
 * Used to render response to the client
 *
 * @param (Int) (errorCode) the http status code
 * @param (String) the error message
 */
$this->onRest('req.exception', function($errorCode, $message=null) {
	$this->renderJSON([
		'type'			=> 'error',
		'success'		=> false,
		'message'		=> (is_null($message)? $this->getHttpStatus()->message: $message),
		'errorCode' => $errorCode,
	]);
});

####pre.filter.req.exception

$this->onRest('pre.filter.req.exception', function($errorCode, $message=null) {
	return [$errorCode, $message=null]; //Array [Int, String]
});

###model.instance

/**
 * model.instance
 *
 * Called when an instance of the model representing the resource(s) is requested
 * By default this is your controller class name minus the 'Controller'
 *
 * @return (Object) an empty instance of a resources Active Record model
 */
$this->onRest('model.instance', function() {
	$modelName = str_replace('Controller', '', get_class($this));
	return new $modelName();
});

####pre.filter.model.instance

$this->onRest('pre.filter.model.instance', function() {
	//No return
});

####post.filter.model.instance

$this->onRest('post.filter.model.instance', function($result)) {
	return $result; //
});

###model.attach.behaviors

/**
 * model.attach.behaviors
 *
 * Called on all requests (Other then custom) to add some magic to your models
 * Attach helper behaviors to model
 *
 * @param (Object) (model) an instance of an AR Model
 *
 * @return (Object) an instance of an Active Record model
 */
$this->onRest('model.attach.behaviors', function($model) {
	//Attach this behavior to help saving nested models
	if(!array_key_exists('ERestActiveRecordRelationBehavior', $model->behaviors())) {
		$model->attachBehavior('ERestActiveRecordRelationBehavior', new ERestActiveRecordRelationBehavior());
	}
	
	if(!array_key_exists('ERestHelperScopes', $model->behaviors())) {
		$model->attachBehavior('ERestHelperScopes', new ERestHelperScopes());
	}
	return $model;
});

####pre.filter.model.attach.behaviors

$this->onRest('pre.filter.model.attach.behaviors', function($model) {
	return $model //Object
});

####post.filter.model.attach.behaviors

$this->onRest('post.filter.model.attach.behaviors', function($model)) {
	return $model; //Object
});

###model.with.relations

/**
 * model.with.relations
 *
 * Called when trying to determine which relations to include in a requests render
 * The default is all relations not starting with an underscore
 * You should most likely customize this per resource
 * as some resources may have relations that return large number of records
 *
 * @return (Array) list of relations (Strings) to attach to resources output
 */
$this->onRest('model.with.relations', function($model) {
	$nestedRelations = [];
	foreach($model->metadata->relations as $rel=>$val)
	{
		$className = $val->className;
		$rel_model = call_user_func([$className, 'model']);
		if(!is_array($rel_model->tableSchema->primaryKey) && substr($rel, 0, 1) != '_') {
			$nestedRelations[] = $rel;
		}
	}
	return $nestedRelations;
});

####pre.filter.model.with.relations

$this->onRest('pre.filter.model.with.relations', function($model) {
	return $model; //Object
});

####post.filter.model.with.relations

$this->onRest('post.filter.model.with.relations', function($result)) {
	return $result; //Array[String]
});

###model.lazy.load.relations

/**
 * model.lazy.load.relations
 *
 * Called when determining if relations should be lazy or eager loaded
 * The default is to lazy load. In most cases this is sufficient
 * Under certain conditions eager loading my be more appropriate
 *
 * @return (Bool) true to lazy load relations; false to eager load
 */
$this->onRest('model.lazy.load.relations', function() {
	return true;
});

####pre.filter.model.lazy.load.relations

$this->onRest('pre.filter.model.lazy.load.relations', function() {
	//No return
});

####post.filter.model.lazy.load.relations

$this->onRest('post.filter.model.lazy.load.relations', function($result)) {
	return $result; //Bool
});

###model.limit

/**
 * model.limit
 *
 * Called when applying a limit to the resources returned in a GET request
 * The default is 100 or the value of the _GET param 'limit'
 *
 * @return (Int) the number of results to return
 */
$this->onRest('model.limit', function() {
	return isset($_GET['limit'])? $_GET['limit']: 100;
});

####pre.filter.model.limit

$this->onRest('pre.filter.model.limit', function() {
	//No return
});

####post.filter.model.limit

$this->onRest('post.filter.model.limit', function($result)) {
	return $result; //Int
});

###model.offset

/**
 * model.offset
 *
 * Called when applying an offset to the records returned in a GET request
 * The default is 0 or the value of the _GET param 'offset'
 *
 * @return (Int) the offset of results to return
 */
$this->onRest('model.offset', function() {
	return isset($_GET['offset'])? $_GET['offset']: 0;
});

####pre.filter.model.offset

$this->onRest('pre.filter.model.offset', function() {
	//No return
});

####post.filter.model.offset

$this->onRest('post.filter.model.offset', function($result)) {
	return $result; //Int
});

###model.scenario

/**
 * model.scenario
 *
 * Called before a resource is found
 * This is the scenario to apply to a resource pre-find
 * The default is 'restfullyii' or the value of the _GET param 'scenario'
 *
 * @return (String) the scenario name
 */
$this->onRest('model.scenario', function() {
	return isset($_GET['scenario'])? $_GET['scenario']: 'restfullyii';
});

####pre.filter.model.scenario

$this->onRest('pre.filter.model.scenario', function() {
	//No return
});

####post.filter.model.scenario

$this->onRest('post.filter.model.scenario', function($result)) {
	return $result; //String
});

###model.filter

/**
 * model.filter
 *
 * Called when attempting to apply a filter to apply to the records in a GET request
 * The default is 'NULL' or the value of the _GET param 'filter'
 * The format is JSON: 
 * '[{"property":"SOME_PROPERTY", "value":"SOME_VALUE"}]'
 * See documentation for additional options
 *
 * @return (JSON) the filter to apply
 */
$this->onRest('model.filter', function() {
	return isset($_GET['filter'])? $_GET['filter']: null;
});

####pre.filter.model.filter

$this->onRest('pre.filter.model.filter', function() {
	//No return
});

####post.filter.model.filter

$this->onRest('post.filter.model.filter', function($result)) {
	return $result; //Array[Object]
});

###model.sort

/**
 * model.sort
 *
 * Called when attempting to sort records returned in a GET request
 * The default is 'NULL' or the value of the _GET param 'sort'
 * The format is JSON:
 * [{"property":"SOME_PROPERTY", "direction":"DESC"}]
 *
 * @return (JSON) the sort to apply
 */ 
$this->onRest('model.sort', function() {
	return isset($_GET['sort'])? $_GET['sort']: null;
});

####pre.filter.model.sort

$this->onRest('pre.filter.model.sort', function() {
	//No return
});

####post.filter.model.sort

$this->onRest('post.filter.model.sort', function($result)) {
	return $result; //Array[Object]
});

###model.find

/**
 * model.find
 *
 * Called when attempting to find a single model
 *
 * @param (Object) (model) an instance of the resources model
 * @param (Mixed/Int) (id) the resources primary key
 *
 * @return (Object) the found model
 */
$this->onRest('model.find', function($model, $id) {
	return $model->findByPk($id);
});

####pre.filter.model.find

$this->onRest('pre.filter.model.find', function($model, $id) {
	return [$model, $id]; //Array [Object, Int]
});

####post.filter.model.find

$this->onRest('post.filter.model.find', function($result)) {
	return $result; //Object
});

###model.find.all

/**
 * model.find.all
 *
 * Called when attempting to find a list of models
 *
 * @param (Object) (model) an instance of the resources model
 *
 * @return (Array) list of found models
 */
$this->onRest('model.find.all', function($model) {
	return $model->findAll();
});

####pre.filter.model.find.all

$this->onRest('pre.filter.model.find.all', function($model) {
	return $model; //Object
});

####post.filter.model.find.all

$this->onRest('post.filter.model.find.all', function($result)) {
	return $result; //Array[Object]
});

###model.count

/**
 * model.count
 *
 * Called when the count of model(s) is needed
 *
 * @param (Object) (model) the model to apply the count to
 *
 * @return (Int) the count of models
 */
$this->onRest('model.count', function($model) {
	return $model->count();
});

####pre.filter.model.count

$this->onRest('pre.filter.model.count', function($model) {
	return $model; //Object
});

####post.filter.model.count

$this->onRest('post.filter.model.count', function($result)) {
	return $result; //Int
});

###model.subresource.find

/**
 * model.subresource.find
 *
 * Called when attempting to find a subresource
 *
 * @param (Object) (model) the model that represents the owner of the sub-resource
 * @param (String) (subresource_name) the name of the sub-resource
 * @param (Mixed/Int) (subresource_id) the primary key of the sub-resource
 *
 * @return (Object) the sub-resource model
 */
$this->onRest('model.subresource.find', function($model, $subresource_name, $subresource_id) {
	$subresource = @$this->getSubresourceHelper()->getSubresource($model, $subresource_name, $subresource_id);
	if(count($subresource) > 0) {
		return $subresource[0];
	}
	return $subresource; //Object
});

####pre.filter.model.subresource.find

$this->onRest('pre.filter.model.subresource.find', function($model, $subresource_name, $subresource_id) {
	return [$model, $subresource_name, $subresource_id]; //Array [Object, String, Int]
});

####post.filter.model.subresource.find

$this->onRest('post.filter.model.subresource.find', function($result)) {
	return $result; //Object
});

###model.subresource.find.all

/**
 * model.subresource.find.all
 *
 * Called when attempting to find all subresources of a resource
 *
 * @param (Object) (model) the model that represents the owner of the sub-resources
 * @param (String) (subresource_name) the name of the sub-resource
 *
 * @return (Array) list of sub-resource models
 */
$this->onRest(ERestEvent::MODEL_SUBRESOURCES_FIND_ALL, function($model, $subresource_name) {
	return $this->getSubresourceHelper()->getSubresource($model, $subresource_name);
});

####pre.filter.model.subresource.find.all

$this->onRest('pre.filter.model.subresource.find.all', function($model, $subresource_name) {
	return [$model, $subresource_name]; //Array [Object, String]
});

####post.filter.model.subresource.find.all

$this->onRest('post.filter.model.subresource.find.all', function($result)) {
	return $result; //Array[Object]
});

###model.subresource.count

/**
 * model.subresource.count
 *
 * Called when the count of sub-resources is needed
 *
 * @param (Object) (model) the model that represents the owner of the sub-resource
 * @param (String) (subresource_name) the name of the sub-resource
 * @param (Mixed/Int) (subresource_id) the primary key of the sub-resource
 *
 * @return (Int) count of the subresources
 */
$this->onRest('model.subresource.count', function($model, $subresource_name, $subresource_id=null) {
	return $this->getSubresourceHelper()->getSubresourceCount($model, $subresource_name, $subresource_id);
});

####pre.filter.model.subresource.count

$this->onRest('pre.filter.model.subresource.count', function($model, $subresource_name, $subresource_id=null) {
	return [$model, $subresource_name, $subresource_id=null]; //Array [Object, String, Int]
});

####post.filter.model.subresource.count

$this->onRest('post.filter.model.subresource.count', function($result)) {
	return $result; //Int
});

###model.apply.post.data

/**
 * model.apply.post.data
 *
 * Called on POST requests when attempting to apply posted data
 *
 * @param (Object) (model) the resource model to save data to
 * @param (Array) (data) the data to save to the model
 * @param (Array) (restricted_properties) list of restricted properties
 *
 * @return (Object) the model with posted data applied
 */
$this->onRest('model.apply.post.data', function($model, $data, $restricted_properties) {
	return $this->getResourceHelper()->setModelAttributes($model, $data, $restricted_properties);
});

####pre.filter.model.apply.post.data

$this->onRest('pre.filter.model.apply.post.data', function($model, $data, $restricted_properties) {
	return [$model, $data, $restricted_properties]; //Array []
});

####post.filter.model.apply.post.data

$this->onRest('post.filter.model.apply.post.data', function($result)) {
	return $result; //
});

###model.apply.put.data

/**
 * model.apply.put.data
 *
 * Called on PUT requests when attempting to apply PUT data
 *
 * @param (Object) (model) the resource model to save data to
 * @param (Array) (data) the data to save to the model
 * @param (Array) (restricted_properties) list of restricted properties
 *
 * @return (Object) the model with PUT data applied
 */
$this->onRest('model.apply.put.data', function($model, $data, $restricted_properties) {
	return $this->getResourceHelper()->setModelAttributes($model, $data, $restricted_properties);
});

####pre.filter.model.apply.put.data

$this->onRest('pre.filter.model.apply.put.data', function($model, $data, $restricted_properties) {
	return [$model, $data, $restricted_properties]; //Array [Object, Array, Array]
});

####post.filter.model.apply.put.data

$this->onRest('post.filter.model.apply.put.data', function($result)) {
	return $result; //Object
});

###model.save

/**
 * model.save
 *
 * Called whenever a model resource is saved
 *
 * @param (Object) the resource model to save
 *
 * @return (Object) the saved resource
 */
$this->onRest('model.save', function($model) {
	if(!$model->save()) {
		throw new CHttpException('400', CJSON::encode($model->errors));
	}
	$model->refresh();
	return $model;
});

####pre.filter.model.save

$this->onRest('pre.filter.model.save', function($model) {
	return $model; //Object
});

####post.filter.model.save

$this->onRest('post.filter.model.save', function($result)) {
	return $result; //Object
});

###model.subresources.save

/**
 * model.subresources.save
 *
 * Called whenever a sub-resource is saved
 *
 * @param (Object) (model) the owner of the sub-resource
 * @param (String) (subresource_name) the name of the subresource
 * @param (Mixed/Int) (subresource_id) the primary key of the subresource
 *
 * @return (Object) the updated model representing the owner of the sub-resource
 */
$this->onRest(ERestEvent::MODEL_SUBRESOURCE_SAVE, function($model, $subresource_name, $subresource_id) {
	if(!$this->getSubresourceHelper()->putSubresourceHelper($model, $subresource_name, $subresource_id)) {
		throw new CHttpException('500', 'Could not save Sub-Resource');
	}
	$model->refresh();
	return true;
});

####pre.filter.model.subresources.save

$this->onRest('pre.filter.model.subresources.save', function($model, $subresource_name, $subresource_id) {
	return [$model, $subresource_name, $subresource_id]; //Array [Object, String, Int]
});

####post.filter.model.subresources.save

$this->onRest('post.filter.model.subresources.save', function($result)) {
	return $result; //Object
});

###model.delete

/**
 * model.delete
 *
 * Called whenever a model resource needs deleting
 *
 * @param (Object) (model) the model resource to be deleted
 */
$this->onRest('model.delete', function($model) {
	if(!$model->delete()) {
		throw new CHttpException(500, 'Could not delete model');
	}
	return $model;
});

####pre.filter.model.delete

$this->onRest('pre.filter.model.delete', function($model) {
	return $model; //Object
});

####post.filter.model.delete

$this->onRest('post.filter.model.delete', function($result)) {
	return $result; //Object
});

###model.subresource.delete

/**
 * model.subresource.delete
 *
 * Called whenever a subresource needs deleting
 *
 * @param (Object) (model) the owner of the sub-resource
 * @param (String) (subresource_name) the name of the subresource
 * @param (Mixed/Int) (subresource_id) the primary key of the subresource
 *
 * @return (Object) the updated model representing the owner of the sub-resource
 */
$this->onRest('model.subresource.delete', function($model, $subresource_name, $subresource_id) {
	if(!$this->getSubresourceHelper()->deleteSubResource($model, $subresource_name, $subresource_id)) {
		throw new CHttpException(500, 'Could not delete Sub-Resource');
	}
	$model->refresh();
	return $model;
});

####pre.filter.model.subresource.delete

$this->onRest('pre.filter.model.subresource.delete', function($model, $subresource_name, $subresource_id) {
	return [$model, $subresource_name, $subresource_id]; //Array [Object, String, Int]
});

####post.filter.model.subresource.delete

$this->onRest('post.filter.model.subresource.delete', function($result)) {
	return $result; //Object
});

###model.restricted.properties

/**
 * model.restricted.properties
 *
 * Called when determining which properties if any should be considered restricted
 * The default is [] (no restricted properties)
 *
 * @return (Array) list of restricted properties
 */
$this->onRest('model.restricted.properties', function() {
	return [];
});

####pre.filter.model.restricted.properties

$this->onRest('pre.filter.model.restricted.properties', function() {
	//No return
});

####post.filter.model.restricted.properties

$this->onRest('post.filter.model.restricted.properties', function($result)) {
	return $result; //Array
});

###model.visible.properties

/**
 * model.visible.properties
 *
 * Called when determining which properties if any should be considered visible
 * The default is [] (no hidden properties)
 *
 * @return (Array) list of visible properties
 */
$this->onRest('model.visible.properties', function() {
	return [];
});

####pre.filter.model.visible.properties

$this->onRest('pre.filter.model.visible.properties', function() {
	//No return
});

####post.filter.model.visible.properties

$this->onRest('post.filter.model.visible.properties', function($result) {
	return $result; //Array
});

###model.hidden.properties

/**
 * model.hidden.properties
 *
 * Called when determining which properties if any should be considered hidden
 * The default is [] (no hidden properties)
 *
 * @return (Array) list of hidden properties
 */
$this->onRest('model.hidden.properties', function() {
	return [];
});

####pre.filter.model.hidden.properties

$this->onRest('pre.filter.model.hidden.properties', function() {
	//No return
});

####post.filter.model.hidden.properties

$this->onRest('post.filter.model.hidden.properties', function($result)) {
	return $result; //Array
});

Testing

运行项目的自动化测试。

单元测试

  1. 确保你在测试配置中拥有正确的数据库和数据库用户(/WEB_ROOT/protected/extensions/starship/RestfullYii/tests/testConfig.php)。
  2. % cd /WEB_ROOT/protected/extensions/starship/RestfullYii/tests
  3. % phpunit unit/

贡献者

许可证

Starship / RestfullYii 在 WTFPL 许可证下发布 - http://sam.zoy.org/wtfpl/。这意味着你可以用这个扩展做任何事情。