PageRenderTime 52ms CodeModel.GetById 21ms RepoModel.GetById 1ms app.codeStats 0ms

/components/ERestController.php

https://github.com/drLev/RESTFullYii
PHP | 789 lines | 567 code | 84 blank | 138 comment | 99 complexity | 30020efa2945f2d432f9247757d6dd5a 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. public $restrictedProperties = array();
  15. public $restFilter = array();
  16. public $restSort = array();
  17. public $restLimit = 100; // Default limit
  18. public $restOffset = 0; //Default Offset
  19. public $developmentFlag = true; //When set to `false' 500 erros will not return detailed messages.
  20. //Auto will include all relations
  21. //FALSE will include no relations in response
  22. //You may also pass an array of relations IE array('posts', 'comments', etc..)
  23. //Override $nestedModels in your controller as needed
  24. public $nestedModels = 'auto';
  25. protected $requestReader;
  26. protected $model = null;
  27. public function __construct($id, $module = null)
  28. {
  29. parent::__construct($id, $module);
  30. $this->requestReader = new ERequestReader('php://input');
  31. }
  32. public function beforeAction($event)
  33. {
  34. if(isset($_GET['filter']))
  35. $this->restFilter = $_GET['filter'];
  36. if(isset($_GET['sort']))
  37. $this->restSort = $_GET['sort'];
  38. if(isset($_GET['limit']))
  39. $this->restLimit = $_GET['limit'];
  40. if(isset($_GET['offset']))
  41. $this->restOffset = $_GET['offset'];
  42. return parent::beforeAction($event);
  43. }
  44. public function onException($event)
  45. {
  46. if(!$this->developmentFlag && ($event->exception->statusCode == 500 || is_null($event->exception->statusCode)))
  47. $message = "Internal Server Error";
  48. else
  49. {
  50. $message = $event->exception->getMessage();
  51. if($tempMessage = CJSON::decode($message))
  52. $message = $tempMessage;
  53. }
  54. $errorCode = (!isset($event->exception->statusCode) || is_null($event->exception->statusCode))? 500: $event->exception->statusCode;
  55. $this->renderJson(array('success' => false, 'message' => $message, 'data' => array('errorCode'=>$errorCode)));
  56. $event->handled = true;
  57. }
  58. public function filters()
  59. {
  60. $restFilters = array('restAccessRules+ restList restView restCreate restUpdate restDelete');
  61. if(method_exists($this, '_filters'))
  62. return CMap::mergeArray($restFilters, $this->_filters());
  63. else
  64. return $restFilters;
  65. }
  66. public function accessRules()
  67. {
  68. $restAccessRules = array(
  69. array(
  70. 'allow', // allow all users to perform 'index' and 'view' actions
  71. 'actions'=>array('restList', 'restView', 'restCreate', 'restUpdate', 'restDelete', 'error'),
  72. 'users'=>array('*'),
  73. )
  74. );
  75. if(method_exists($this, '_accessRules'))
  76. return CMap::mergeArray($restAccessRules, $this->_accessRules());
  77. else
  78. return $restAccessRules;
  79. }
  80. /**
  81. * Controls access to restfull requests
  82. */
  83. public function filterRestAccessRules( $c )
  84. {
  85. Yii::app()->clientScript->reset(); //Remove any scripts registered by Controller Class
  86. Yii::app()->onException = array($this, 'onException'); //Register Custom Exception
  87. //For requests from JS check that a user is loged in and call validateUser
  88. //validateUser can/should be overridden in your controller.
  89. if(!Yii::app()->user->isGuest && $this->validateAjaxUser($this->action->id))
  90. $c->run();
  91. else
  92. {
  93. Yii::app()->errorHandler->errorAction = '/' . $this->uniqueid . '/error';
  94. if(!(isset($_SERVER['HTTP_X_'.self::APPLICATION_ID.'_USERNAME']) and isset($_SERVER['HTTP_X_'.self::APPLICATION_ID.'_PASSWORD']))) {
  95. // Error: Unauthorized
  96. throw new CHttpException(401, 'You are not authorized to preform this action.');
  97. }
  98. $username = $_SERVER['HTTP_X_'.self::APPLICATION_ID.'_USERNAME'];
  99. $password = $_SERVER['HTTP_X_'.self::APPLICATION_ID.'_PASSWORD'];
  100. // Find the user
  101. if($username != self::USERNAME)
  102. {
  103. // Error: Unauthorized
  104. throw new CHttpException(401, 'Error: User Name is invalid');
  105. }
  106. else if($password != self::PASSWORD)
  107. {
  108. // Error: Unauthorized
  109. throw new CHttpException(401, 'Error: User Password is invalid');
  110. }
  111. // This tells the filter chain $c to keep processing.
  112. $c->run();
  113. }
  114. }
  115. /**
  116. * Custom error handler for restfull Errors
  117. */
  118. public function actionError()
  119. {
  120. if($error=Yii::app()->errorHandler->error)
  121. {
  122. //print_r($error); exit();
  123. if(!Yii::app()->request->isAjaxRequest)
  124. $this->HTTPStatus = $this->getHttpStatus($error['code'], 'C500INTERNALSERVERERROR');
  125. else if(!$this->developmentFlag)
  126. {
  127. if($error['code'] == 500)
  128. $error['message'] = 'Internal Server Error';
  129. }
  130. $this->renderJson(array('success' => false, 'message' => $error['message'], 'data' => array('errorCode'=>$error['code'])));
  131. }
  132. }
  133. /**
  134. * Get HTTP Status Headers From code
  135. */
  136. public function getHttpStatus($statusCode, $default='C200OK')
  137. {
  138. switch ($statusCode)
  139. {
  140. case '200':
  141. return self::C200OK;
  142. break;
  143. case '201':
  144. return self::C201CREATED;
  145. break;
  146. case '401':
  147. return self::C401UNAUTHORIZED;
  148. break;
  149. case '404':
  150. return self::C404NOTFOUND;
  151. break;
  152. case '406':
  153. return self::C406NOTACCEPTABLE;
  154. break;
  155. case '500':
  156. return self::C500INTERNALSERVERERROR;
  157. break;
  158. default:
  159. return self::$default;
  160. }
  161. }
  162. protected function getNestedRelations()
  163. {
  164. $nestedRelations = array();
  165. if(!is_array($this->nestedModels) && $this->nestedModels == 'auto')
  166. {
  167. foreach($this->model->metadata->relations as $rel=>$val)
  168. {
  169. $className = $val->className;
  170. if(!is_array($className::model()->tableSchema->primaryKey))
  171. $nestedRelations[] = $rel;
  172. }
  173. return $nestedRelations;
  174. }
  175. else if(!is_array($this->nestedModels) && $this->nestedModels === false)
  176. return $nestedRelations;
  177. else if(is_array($this->nestedModels))
  178. return $this->nestedModels;
  179. return $nestedRelations;
  180. }
  181. /**
  182. ******************************************************************************************
  183. ******************************************************************************************
  184. * Actions that are tiggered by RESTFull requests
  185. * To change their default behavoir
  186. * you should overide "doRest..." Methods in the controller
  187. * and leave these actions as is
  188. ******************************************************************************************
  189. ******************************************************************************************
  190. */
  191. /**
  192. * Renders list of data assosiated with controller as json
  193. */
  194. public function actionRestList()
  195. {
  196. $this->doRestList();
  197. }
  198. /**
  199. * Renders View of record as json
  200. * Or Custom method
  201. */
  202. public function actionRestView($id, $var=null, $var2=null)
  203. {
  204. if($this->isPk($id) && is_null($var))
  205. $this->doRestView($id);
  206. else
  207. {
  208. if($this->isPk($id) && !is_null($var) && is_null($var2))
  209. {
  210. if($this->validateSubResource($var))
  211. $this->doRestViewSubResource($id, $var);
  212. else
  213. $this->triggerCustomRestGet(ucFirst($var), array($id));
  214. }
  215. else if($this->isPk($id) && !is_null($var) && !is_null($var2))
  216. {
  217. if($this->validateSubResource($var))
  218. $this->doRestViewSubResource($id, $var, $var2);
  219. else
  220. $this->triggerCustomRestGet(ucFirst($var), array($id, $var2));
  221. }
  222. else
  223. {
  224. //if the $id is not numeric and var + var2 are not set
  225. //we are assume that the client is attempting to call a custom method
  226. //There may optionaly be a second param `$var` passed in the url
  227. $this->triggerCustomRestGet(ucFirst($id), array($var, $var2));
  228. }
  229. }
  230. }
  231. /**
  232. * Updated record
  233. */
  234. public function actionRestUpdate($id, $var=null, $var2=null)
  235. {
  236. $this->HTTPStatus = $this->getHttpStatus('200');
  237. if($this->isPk($id))
  238. {
  239. if(is_null($var))
  240. $this->doRestUpdate($id, $this->data());
  241. else if (is_null($var2))
  242. $this->triggerCustomRestPut($var, array($id));
  243. else if(!is_null($var2))
  244. {
  245. if($this->validateSubResource($var))
  246. $this->doRestUpdateSubResource($id, $var, $var2);
  247. else
  248. $this->triggerCustomRestPut($var, array($id, $var2));
  249. }
  250. }
  251. else
  252. $this->triggerCustomRestPut($id, array($var, $var2));
  253. }
  254. /**
  255. * Creates new record
  256. */
  257. public function actionRestCreate($id=null, $var=null)
  258. {
  259. $this->HTTPStatus = $this->getHttpStatus('201');
  260. if(!$id)
  261. {
  262. $this->doRestCreate($this->data());
  263. }
  264. else
  265. {
  266. //we can assume if $id is set and var is not a subresource
  267. //then the user is trying to call a custom method
  268. $var = 'doCustomRestPost' . ucfirst($id);
  269. if(method_exists($this, $var))
  270. $this->$var($this->data());
  271. else if($this->isPk($var))
  272. $this->doRestCreate($this->data());
  273. else
  274. throw new CHttpException(500, 'Method or Sub-Resource does not exist.');
  275. }
  276. }
  277. /**
  278. * Deletes record
  279. */
  280. public function actionRestDelete($id, $var=null, $var2=null)
  281. {
  282. if($this->isPk($id))
  283. {
  284. if(is_null($var))
  285. $this->doRestDelete($id);
  286. else if(!is_null($var2))
  287. {
  288. if($this->validateSubResource($var, $var2))
  289. $this->doRestDeleteSubResource($id, $var, $var2); //Looks like we are trying to delete a subResource
  290. else
  291. $this->triggerCustomDelete($var, array($id, $var2));
  292. }
  293. else
  294. $this->triggerCustomDelete($var, array($id));
  295. }
  296. else
  297. {
  298. $this->triggerCustomDelete($id, array($var, $var2));
  299. }
  300. }
  301. /**
  302. ******************************************************************************************
  303. ******************************************************************************************
  304. * Helper functions for processing Rest data
  305. ******************************************************************************************
  306. ******************************************************************************************
  307. */
  308. /**
  309. * Takes array and renders Json String
  310. */
  311. protected function renderJson($data) {
  312. $this->layout = 'ext.restfullyii.views.layouts.json';
  313. $this->render('ext.restfullyii.views.api.output', array('data'=>$data));
  314. }
  315. /**
  316. * Get data submited by the client
  317. */
  318. public function data()
  319. {
  320. $request = $this->requestReader->getContents();
  321. if ($request) {
  322. if ($json_post = CJSON::decode($request)){
  323. return $json_post;
  324. }else{
  325. parse_str($request,$variables);
  326. return $variables;
  327. }
  328. }
  329. return false;
  330. }
  331. /**
  332. * Returns the model assosiated with this controller.
  333. * The assumption is that the model name matches your controller name
  334. * If this is not the case you should override this method in your controller
  335. */
  336. public function getModel()
  337. {
  338. if ($this->model === null)
  339. {
  340. $modelName = str_replace('Controller', '', get_class($this));
  341. $this->model = new $modelName;
  342. }
  343. $this->_attachBehaviors($this->model);
  344. return $this->model;
  345. }
  346. /**
  347. * Helper for loading a single model
  348. */
  349. protected function loadOneModel($id)
  350. {
  351. return $this->getModel()->with($this->nestedRelations)->findByPk($id);
  352. }
  353. //Updated setModelAttributes to allow for related data to be set.
  354. private function setModelAttributes($model, $data)
  355. {
  356. foreach($data as $var=>$value)
  357. {
  358. if(($model->hasAttribute($var) || isset($model->metadata->relations[$var])) && !in_array($var, $this->restrictedProperties))
  359. $model->$var = $value;
  360. else
  361. throw new CHttpException(406, 'Parameter \'' . $var . '\' is not allowed for model');
  362. }
  363. return $model;
  364. }
  365. /**
  366. * Helper for saving single/mutliple models
  367. */
  368. private function saveModel($model, $data)
  369. {
  370. if(!isset($data[0]))
  371. $models[] = $this->setModelAttributes($model, $data);
  372. else
  373. {
  374. for($i=0; $i<count($data); $i++)
  375. {
  376. $models[$i] = $this->setModelAttributes($this->getModel(), $data[$i]);
  377. if(!$models[$i]->validate())
  378. throw new CHttpException(406, 'Model could not be saved as vildation failed.');
  379. $this->model = null;
  380. }
  381. }
  382. for($cnt=0;$cnt<count($models);$cnt++)
  383. {
  384. $this->_attachBehaviors($models[$cnt]);
  385. if(!$models[$cnt]->save())
  386. throw new CHttpException(406, 'Model could not be saved');
  387. else
  388. $ids[] = $models[$cnt]->{$models[$cnt]->tableSchema->primaryKey};
  389. }
  390. return $models;
  391. }
  392. //Attach helper behaviors
  393. public function _attachBehaviors($model)
  394. {
  395. //Attach this behavior to help saving nested models
  396. if(!array_key_exists('EActiveRecordRelationBehavior', $model->behaviors()))
  397. $model->attachBehavior('EActiveRecordRelationBehavior', new EActiveRecordRelationBehavior());
  398. //Attach this behavior to help outputting models and their relations as arrays
  399. if(!array_key_exists('MorrayBehavior', $model->behaviors()))
  400. $model->attachBehavior('MorrayBehavior', new MorrayBehavior());
  401. if(!array_key_exists('ERestHelperScopes', $model->behaviors()))
  402. $model->attachBehavior('ERestHelperScopes', new ERestHelperScopes());
  403. return true;
  404. }
  405. /**
  406. * Convert list of models or single model to array
  407. */
  408. public function allToArray($models, $options=array('relname'=>''))
  409. {
  410. if(is_array($models))
  411. {
  412. $results = array();
  413. foreach($models as $model)
  414. {
  415. $this->_attachBehaviors($model);
  416. $results[] = $model->toArray($options);
  417. }
  418. return $results;
  419. }
  420. else if($models != null)
  421. {
  422. $this->_attachBehaviors($models);
  423. return $models->toArray($options);
  424. }
  425. else
  426. return array();
  427. }
  428. public function triggerCustomRestGet($id, $vars=array())
  429. {
  430. $method = 'doCustomRestGet' . ucfirst($id);
  431. if(method_exists($this, $method))
  432. $this->$method($vars);
  433. else
  434. throw new CHttpException(500, 'Method or Sub-Resource does not exist.');
  435. }
  436. public function triggerCustomRestPut($method, $vars=array())
  437. {
  438. $method = 'doCustomRestPut' . ucfirst($method);
  439. if(method_exists($this, $method))
  440. {
  441. if(count($vars) > 0)
  442. $this->$method($this->data(), $vars);
  443. else
  444. $this->$method($this->data());
  445. }
  446. throw new CHttpException(500, 'Method or Sub-Resource does not exist.');
  447. }
  448. public function triggerCustomDelete($methodName, $vars=array())
  449. {
  450. $method = 'doCustomRestDelete' . ucfirst($methodName);
  451. if(method_exists($this, $method))
  452. $this->$method($vars);
  453. else
  454. throw new CHttpException(500, 'Method or Sub-Resource does not exist.');
  455. }
  456. public function validateSubResource($subResourceName, $subResourceID=null)
  457. {
  458. if(is_null($relations = $this->getModel()->relations()))
  459. return false;
  460. if(!isset($relations[$subResourceName]))
  461. return false;
  462. if($relations[$subResourceName][0] != CActiveRecord::MANY_MANY)
  463. return false;
  464. if(!is_null($subResourceID))
  465. return filter_var($subResourceID, FILTER_VALIDATE_INT) !== false;
  466. return true;
  467. }
  468. public function getSubResource($subResourceName)
  469. {
  470. $relations = $this->getModel()->relations();
  471. return $this->getModel()->parseManyManyFk($subResourceName, $relations[$subResourceName]);
  472. }
  473. /**
  474. ******************************************************************************************
  475. ******************************************************************************************
  476. * OVERIDE THE METHODS BELOW IN YOUR CONTROLLER TO REMOVE/ALTER DEFAULT FUNCTIONALITY
  477. ******************************************************************************************
  478. ******************************************************************************************
  479. */
  480. /**
  481. * Override this function if your model uses a non Numeric PK.
  482. */
  483. public function isPk($pk)
  484. {
  485. return filter_var($pk, FILTER_VALIDATE_INT) !== false;
  486. }
  487. /**
  488. * You should override this method to provide stronger access control
  489. * to specifc restfull actions via AJAX
  490. */
  491. public function validateAjaxUser($action)
  492. {
  493. return false;
  494. }
  495. public function outputHelper($message, $results, $totalCount=0, $model=null)
  496. {
  497. if(is_null($model))
  498. $model = lcfirst(get_class($this->model));
  499. else
  500. $model = lcfirst($model);
  501. $this->renderJson(array(
  502. 'success'=>true,
  503. 'message'=>$message,
  504. 'data'=>array(
  505. 'totalCount'=>$totalCount,
  506. $model=>$this->allToArray($results)
  507. )
  508. ));
  509. }
  510. /**
  511. * This is broken out as a sperate method from actionRestList
  512. * To allow for easy overriding in the controller
  513. * and to allow for easy unit testing
  514. */
  515. public function doRestList()
  516. {
  517. $this->outputHelper(
  518. 'Records Retrieved Successfully',
  519. $this->getModel()->with($this->nestedRelations)
  520. ->filter($this->restFilter)->orderBy($this->restSort)
  521. ->limit($this->restLimit)->offset($this->restOffset)
  522. ->findAll(),
  523. $this->getModel()
  524. ->with($this->nestedRelations)
  525. ->filter($this->restFilter)
  526. ->count()
  527. );
  528. }
  529. /**
  530. * This is broken out as a sperate method from actionRestView
  531. * To allow for easy overriding in the controller
  532. * adn to allow for easy unit testing
  533. */
  534. public function doRestViewSubResource($id, $subResource, $subResourceID=null)
  535. {
  536. $subResourceRelation = $this->getModel()->getActiveRelation($subResource);
  537. $subResourceModel = new $subResourceRelation->className;
  538. $this->_attachBehaviors($subResourceModel);
  539. if(is_null($subResourceID))
  540. {
  541. $modelName = get_class($this->model);
  542. $newRelationName = "_" . $subResourceRelation->className . "Count";
  543. $this->getModel()->metaData->addRelation($newRelationName, array(
  544. $modelName::STAT, $subResourceRelation->className, $subResourceRelation->foreignKey
  545. ));
  546. $model = $this->getModel()->with($newRelationName)->findByPk($id);
  547. $count = $model->$newRelationName;
  548. $results = $this->getModel()
  549. ->with($subResource)
  550. ->limit($this->restLimit)
  551. ->offset($this->restOffset)
  552. ->findByPk($id, array('together'=>true));
  553. $results = $results->$subResource;
  554. $this->outputHelper(
  555. 'Records Retrieved Successfully',
  556. $results,
  557. $count,
  558. $subResourceRelation->className
  559. );
  560. }
  561. else
  562. {
  563. $results = $this->getModel()
  564. ->with($subResource)
  565. ->findByPk($id, array('condition'=>"$subResource.id=$subResourceID"));
  566. if(is_null($results))
  567. {
  568. $this->HTTPStatus = 404;
  569. throw new CHttpException('404', 'Record Not Found');
  570. }
  571. $this->outputHelper(
  572. 'Record Retrieved Successfully',
  573. $results->$subResource,
  574. 1,
  575. $subResourceRelation->className
  576. );
  577. }
  578. }
  579. /**
  580. * This is broken out as a sperate method from actionRestView
  581. * To allow for easy overriding in the controller
  582. * adn to allow for easy unit testing
  583. */
  584. public function doRestView($id)
  585. {
  586. $model = $this->loadOneModel($id);
  587. if(is_null($model))
  588. {
  589. $this->HTTPStatus = 404;
  590. throw new CHttpException('404', 'Record Not Found');
  591. }
  592. $this->outputHelper(
  593. 'Record Retrieved Successfully',
  594. $model,
  595. 1
  596. );
  597. }
  598. /**
  599. * This is broken out as a sperate method from actionResUpdate
  600. * To allow for easy overriding in the controller
  601. * and to allow for easy unit testing
  602. */
  603. public function doRestUpdate($id, $data)
  604. {
  605. $model = $this->saveModel($this->loadOneModel($id), $data);
  606. $this->outputHelper(
  607. 'Record Updated',
  608. $this->loadOneModel($id),
  609. 1
  610. );
  611. }
  612. /**
  613. * This is broken out as a sperate method from actionRestCreate
  614. * To allow for easy overriding in the controller
  615. * and to alow for easy unit testing
  616. */
  617. public function doRestCreate($data)
  618. {
  619. $models = $this->saveModel($this->getModel(), $data);
  620. //$this->renderJson(array('success'=>true, 'message'=>'Record(s) Created', 'data'=>array($models)));
  621. $this->outputHelper(
  622. 'Record(s) Created',
  623. $models,
  624. count($models)
  625. );
  626. }
  627. /**
  628. * This is broken out as a sperate method from actionRestCreate
  629. * To allow for easy overriding in the controller
  630. * and to alow for easy unit testing
  631. */
  632. public function doRestUpdateSubResource($id, $subResource, $subResourceID)
  633. {
  634. list($relationTable, $fks) = $this->getSubResource($subResource);
  635. if($this->saveSubResource($id, $subResourceID, $relationTable, $fks) > 0)
  636. {
  637. $this->renderJson(
  638. array('success'=>true, 'message'=>'Sub-Resource Added', 'data'=>array(
  639. $fks[0] => $id,
  640. $fks[1] => $subResourceID,
  641. ))
  642. );
  643. }
  644. else
  645. throw new CHttpException('500', 'Could not save Sub-Resource');
  646. }
  647. public function saveSubResource($pk, $fk, $relationTable, $fks)
  648. {
  649. return $this->getModel()->dbConnection->commandBuilder->createInsertCommand($relationTable, array(
  650. $fks[0] => $pk,
  651. $fks[1] => $fk,
  652. ))->execute();
  653. }
  654. /**
  655. * This is broken out as a sperate method from actionRestDelete
  656. * To allow for easy overridding in the controller
  657. * and to alow for easy unit testing
  658. */
  659. public function doRestDelete($id)
  660. {
  661. $model = $this->loadOneModel($id);
  662. if($model->delete())
  663. $data = array('success'=>true, 'message'=>'Record Deleted', 'data'=>array('id'=>$id));
  664. else
  665. throw new CHttpException(406, 'Could not delete model with ID: ' . $id);
  666. $this->renderJson($data);
  667. }
  668. /**
  669. * This is broken out as a sperate method from actionRestDelete
  670. * To allow for easy overridding in the controller
  671. * and to alow for easy unit testing
  672. */
  673. public function doRestDeleteSubResource($id, $subResource, $subResourceID)
  674. {
  675. list($relationTable, $fks) = $this->getSubResource($subResource);
  676. $criteria=new CDbCriteria();
  677. $criteria->addColumnCondition(array(
  678. $fks[0]=>$id,
  679. $fks[1]=>$subResourceID
  680. ));
  681. if($this->getModel()->dbConnection->commandBuilder->createDeleteCommand($relationTable, $criteria)->execute())
  682. {
  683. $data = array('success'=>true, 'message'=>'Record Deleted', 'data'=>array(
  684. $fks[0]=>$id,
  685. $fks[1]=>$subResourceID
  686. ));
  687. }
  688. else
  689. {
  690. throw new CHttpException(406, 'Could not delete model with ID: ' . array(
  691. $fks[0]=>$id,
  692. $fks[1]=>$subResourceID
  693. ));
  694. }
  695. $this->renderJson($data);
  696. }
  697. public function setRequestReader($requestReader)
  698. {
  699. $this->requestReader = $requestReader;
  700. }
  701. public function setModel($model)
  702. {
  703. $this->model = $model;
  704. }
  705. }