PageRenderTime 37ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 0ms

/components/ERestController.php

https://github.com/eligundry/RESTFullYii
PHP | 433 lines | 258 code | 48 blank | 127 comment | 38 complexity | 29ac654f6216f910ac1f0ac6e8da601b MD5 | raw file
  1. <?php
  2. class ERestController extends Controller
  3. {
  4. Const APPLICATION_ID = 'REST';
  5. Const C404NOTFOUND = 'HTTP/1.1 404 Not Found';
  6. Const C401UNAUTHORIZED = 'HTTP/1.1 401 Unauthorized';
  7. Const C406NOTACCEPTABLE = 'HTTP/1.1 406 Not Acceptable';
  8. Const C201CREATED = 'HTTP/1.1 201 Created';
  9. Const C200OK = 'HTTP/1.1 200 OK';
  10. Const C500INTERNALSERVERERROR = 'HTTP/1.1 500 Internal Server Error';
  11. Const USERNAME = 'admin@restuser';
  12. Const PASSWORD = 'admin@Access';
  13. public $HTTPStatus = 'HTTP/1.1 200 OK';
  14. protected $requestReader;
  15. protected $model = null;
  16. public function __construct($id, $module = null) {
  17. parent::__construct($id, $module);
  18. $this->requestReader = new ERequestReader('php://input');
  19. }
  20. public function filters() {
  21. $restFilters = array('restAccessRules+ restList restView restCreate restUpdate restDelete');
  22. if(method_exists($this, '_filters'))
  23. return CMap::mergeArray($restFilters, $this->_filters());
  24. else
  25. return $restFilters;
  26. }
  27. public function accessRules()
  28. {
  29. $restAccessRules = array(
  30. array('allow', // allow all users to perform 'index' and 'view' actions
  31. 'actions'=>array('restList', 'restView', 'restCreate', 'restUpdate', 'restDelete', 'error'),
  32. 'users'=>array('*'),
  33. ));
  34. if(method_exists($this, '_accessRules'))
  35. return CMap::mergeArray($restAccessRules, $this->_accessRules());
  36. else
  37. return $restAccessRules;
  38. }
  39. /**
  40. * Controls access to restfull requests
  41. */
  42. public function filterRestAccessRules( $c )
  43. {
  44. Yii::app()->clientScript->reset(); //Remove any scripts registered by Controller Class
  45. //For requests from JS check that a user is loged in and throw validateUser
  46. //validateUser can/should be overridden in your controller.
  47. if(!Yii::app()->user->isGuest && $this->validateAjaxUser($this->action->id))
  48. $c->run();
  49. else
  50. {
  51. Yii::app()->errorHandler->errorAction = '/' . $this->uniqueid . '/error';
  52. if(!(isset($_SERVER['HTTP_X_'.self::APPLICATION_ID.'_USERNAME']) and isset($_SERVER['HTTP_X_'.self::APPLICATION_ID.'_PASSWORD']))) {
  53. // Error: Unauthorized
  54. throw new CHttpException(401, 'You are not authorized to proform this action.');
  55. }
  56. $username = $_SERVER['HTTP_X_'.self::APPLICATION_ID.'_USERNAME'];
  57. $password = $_SERVER['HTTP_X_'.self::APPLICATION_ID.'_PASSWORD'];
  58. // Find the user
  59. if($username != self::USERNAME)
  60. {
  61. // Error: Unauthorized
  62. throw new CHttpException(401, 'Error: User Name is invalid');
  63. }
  64. else if($password != self::PASSWORD)
  65. {
  66. // Error: Unauthorized
  67. throw new CHttpException(401, 'Error: User Password is invalid');
  68. }
  69. // This tells the filter chain $c to keep processing.
  70. $c->run();
  71. }
  72. }
  73. /**
  74. * Custom error handler for restfull Errors
  75. */
  76. public function actionError()
  77. {
  78. if($error=Yii::app()->errorHandler->error)
  79. {
  80. if(!Yii::app()->request->isAjaxRequest)
  81. $this->HTTPStatus = $this->getHttpStatus($error['code'], 'C500INTERNALSERVERERROR');
  82. $this->renderJson(array('success' => false, 'message' => $error['message'], 'data' => array('errorCode'=>$error['code'])));
  83. }
  84. }
  85. /**
  86. * Get HTTP Status Headers From code
  87. */
  88. public function getHttpStatus($statusCode, $default='C200OK')
  89. {
  90. switch ($statusCode) {
  91. case '200':
  92. return self::C200OK;
  93. break;
  94. case '201':
  95. return self::C201CREATED;
  96. break;
  97. case '401':
  98. return self::C401UNAUTHORIZED;
  99. break;
  100. case '404':
  101. return self::C404NOTFOUND;
  102. break;
  103. case '406':
  104. return self::C406NOTACCEPTABLE;
  105. break;
  106. case '500':
  107. return self::C500INTERNALSERVERERROR;
  108. break;
  109. default:
  110. return self::$default;
  111. }
  112. }
  113. /**
  114. ******************************************************************************************
  115. ******************************************************************************************
  116. * Actions that are tiggered by RESTFull requests
  117. * To change their default behavoir
  118. * you should overide "doRest..." Methods in the controller
  119. * and leave these actions as is
  120. ******************************************************************************************
  121. ******************************************************************************************
  122. */
  123. /**
  124. * Renders list of data assosiated with controller as json
  125. */
  126. public function actionRestList() {
  127. $this->doRestList();
  128. }
  129. /**
  130. * Renders View of record as json
  131. * Or Custom method
  132. */
  133. public function actionRestView($id, $var=null, $var2=null)
  134. {
  135. //If the value is numeric we can assume we have a record ID
  136. if($this->isPk($id))
  137. $this->doRestView($id);
  138. else
  139. {
  140. //if the $id is not numeric
  141. //we are assume that the client is attempting to call a custom method
  142. //There may optionaly be a second param `$var` passed in the url
  143. if(isset($var) && isset($var2))
  144. $var = array($var, $var2);
  145. $id = 'doCustomRestGet' . ucfirst($id);
  146. if(method_exists($this, $id))
  147. $this->$id($var);
  148. else
  149. throw new CHttpException(500, 'Method does not exist.');
  150. }
  151. }
  152. /**
  153. * Updated record
  154. */
  155. public function actionRestUpdate($id, $var=false)
  156. {
  157. $this->HTTPStatus = $this->getHttpStatus('201');
  158. if(!$var)
  159. $this->doRestUpdate($id, $this->data());
  160. else
  161. {
  162. $var = 'doCustomRestPut' . ucfirst($var);
  163. if(method_exists($this, $var))
  164. $this->$var($id, $this->data());
  165. else if($this->isPk($var))
  166. $this->doRestUpdate($id, $this->data());
  167. else
  168. throw new CHttpException(500, 'Method does not exist.');
  169. }
  170. }
  171. /**
  172. * Creates new record
  173. */
  174. public function actionRestCreate($id=null) {
  175. $this->HTTPStatus = $this->getHttpStatus('201');
  176. if(!$id) {
  177. $this->doRestCreate($this->data());
  178. }
  179. else
  180. {
  181. //we can assume if $id is set the user is trying to call a custom method
  182. $id = 'doCustomRestPost' . ucfirst($id);
  183. if(method_exists($this, $id))
  184. $this->$id($this->data());
  185. else if($this->isPk($var))
  186. $this->doRestCreate($this->data());
  187. else
  188. throw new CHttpException(500, 'Method does not exist.');
  189. }
  190. }
  191. /**
  192. * Deletes record
  193. */
  194. public function actionRestDelete($id)
  195. {
  196. $this->doRestDelete($id);
  197. }
  198. /**
  199. ******************************************************************************************
  200. ******************************************************************************************
  201. * Helper functions for processing Rest data
  202. ******************************************************************************************
  203. ******************************************************************************************
  204. */
  205. /**
  206. * Takes array and renders Json String
  207. */
  208. protected function renderJson($data) {
  209. $this->layout = 'ext.restfullyii.views.layouts.json';
  210. $this->render('ext.restfullyii.views.api.output', array('data'=>$data));
  211. }
  212. /**
  213. * Get data submited by the client
  214. */
  215. public function data() {
  216. $request = $this->requestReader->getContents();
  217. if ($request) {
  218. if ($json_post = CJSON::decode($request)){
  219. return $json_post;
  220. }else{
  221. parse_str($request,$variables);
  222. return $variables;
  223. }
  224. }
  225. return false;
  226. }
  227. /**
  228. * Returns the model assosiated with this controller.
  229. * The assumption is that the model name matches your controller name
  230. * If this is not the case you should override this method in your controller
  231. */
  232. public function getModel() {
  233. if ($this->model === null) {
  234. $modelName = str_replace('Controller', '', get_class($this));
  235. $this->model = new $modelName;
  236. }
  237. return $this->model;
  238. }
  239. /**
  240. * Helper for loading a single model
  241. */
  242. protected function loadModel($id) {
  243. return $this->getModel()->findByPk($id);
  244. }
  245. /**
  246. * Helper for saving single/mutliple models
  247. * Multiple models now working
  248. */
  249. private function saveModel($model, $data) {
  250. if(!isset($data[0])) {
  251. $model->attributes = $data;
  252. $models[] = $model;
  253. }
  254. else {
  255. for($i=0; $i<count($data); $i++) {
  256. $models[$i] = $this->getModel();
  257. $models[$i]->attributes = $data[$i];
  258. if(!$models[$i]->validate())
  259. throw new CHttpException(406, 'Model could not be saved as vildation failed.');
  260. $this->model = null; //Unsetting model to null to force create new model object.
  261. }
  262. }
  263. for($cnt=0;$cnt<count($models);$cnt++) {
  264. $temp = $models[$cnt];
  265. if(!$temp->save())
  266. throw new CHttpException(406, 'Model could not be saved');
  267. else
  268. $ids[] = $temp->id;
  269. unset($temp);
  270. }
  271. return $ids;
  272. }
  273. /**
  274. ******************************************************************************************
  275. ******************************************************************************************
  276. * OVERIDE THE METHODS BELOW IN YOUR CONTROLLER TO REMOVE/ALTER DEFAULT FUNCTIONALITY
  277. ******************************************************************************************
  278. ******************************************************************************************
  279. */
  280. /**
  281. * Override this function if your model uses a non Numeric PK.
  282. */
  283. public function isPk($pk) {
  284. return filter_var($pk, FILTER_VALIDATE_INT) !== false;
  285. }
  286. /**
  287. * You should override this method to provide stronger access control
  288. * to specifc restfull actions via AJAX
  289. */
  290. public function validateAjaxUser($action)
  291. {
  292. return false;
  293. }
  294. /**
  295. * This is broken out as a sperate method from actionRestList
  296. * To allow for easy overriding in the controller
  297. * and to allow for easy unit testing
  298. */
  299. public function doRestList()
  300. {
  301. $this->renderJson(array('success'=>true, 'message'=>'Records Retrieved Successfully', 'data'=>$this->getModel()->findAll()));
  302. }
  303. /**
  304. * This is broken out as a sperate method from actionRestView
  305. * To allow for easy overriding in the controller
  306. * adn to allow for easy unit testing
  307. */
  308. public function doRestView($id)
  309. {
  310. $this->renderJson(array('success'=>true, 'message'=>'Record Retrieved Successfully', 'data'=>$this->loadModel($id)));
  311. }
  312. /**
  313. * This is broken out as a sperate method from actionResUpdate
  314. * To allow for easy overriding in the controller
  315. * and to allow for easy unit testing
  316. */
  317. public function doRestUpdate($id, $data) {
  318. $model = $this->saveModel($this->loadModel($id), $data);
  319. $this->renderJson(array('success'=>true, 'message'=>'Record Updated', 'data'=>array('id'=>$id)));
  320. }
  321. /**
  322. * This is broken out as a sperate method from actionRestCreate
  323. * To allow for easy overriding in the controller
  324. * and to alow for easy unit testing
  325. */
  326. public function doRestCreate($data) {
  327. $model = $this->getModel();
  328. $ids = $this->saveModel($model, $data);
  329. $this->renderJson(array('success'=>true, 'message'=>'Record(s) Created', 'data'=>array('id'=>$ids)));
  330. }
  331. /**
  332. * This is broken out as a sperate method from actionRestDelete
  333. * To allow for easy overridding in the controller
  334. * and to alow for easy unit testing
  335. */
  336. public function doRestDelete($id)
  337. {
  338. $model = $this->loadModel($id);
  339. if($model->delete())
  340. $data = array('success'=>true, 'message'=>'Record Deleted', 'data'=>array('id'=>$id));
  341. else
  342. throw new CHttpException(406, 'Could not delete model with ID: ' . $id);
  343. $this->renderJson($data);
  344. }
  345. /**
  346. * Provides the ability to Limit and offset results
  347. * http://example.local/api/sample/limit/1/2
  348. * The above example would limit results to 1
  349. * and offest them by 2
  350. */
  351. public function doCustomRestGetLimit($var) {
  352. $criteria = new CDbCriteria();
  353. if(is_array($var)){
  354. $criteria->limit = $var[0];
  355. $criteria->offset = $var[1];
  356. }
  357. else {
  358. $criteria->limit = $var;
  359. }
  360. $this->renderJson(array('success'=>true, 'message'=>'Records Retrieved Successfully', 'data'=>$this->getModel()->findAll($criteria)));
  361. }
  362. /**
  363. * Returns the current record count
  364. * http://example.local/api/sample/count
  365. */
  366. public function doCustomRestGetCount($var=null, $remote=true) {
  367. $this->renderJson(array('success'=>true, 'message'=>'Record Count Retrieved Successfully', 'data'=>array('count'=> $this->getModel()->count() )));
  368. }
  369. /**
  370. * Search by attribute
  371. * Simply post a list of attributes and values you wish to search by
  372. * http://example.local/api/sample/search
  373. * POST = {'id':'6', 'name':'evan'}
  374. */
  375. public function doCustomRestPostSearch($data)
  376. {
  377. $this->renderJson(array('success'=>true, 'message'=>'Records Retrieved Successfully', 'data'=>$this->getModel()->findAllByAttributes($data)));
  378. }
  379. public function setRequestReader($requestReader) {
  380. $this->requestReader = $requestReader;
  381. }
  382. public function setModel($model) {
  383. $this->model = $model;
  384. }
  385. }