RESTFullYii /components/ERestController.php

Language PHP Lines 434
MD5 Hash 29ac654f6216f910ac1f0ac6e8da601b
Repository https://github.com/eligundry/RESTFullYii.git View Raw File
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
<?php
class ERestController extends Controller
{
	Const APPLICATION_ID = 'REST';
	Const C404NOTFOUND = 'HTTP/1.1 404 Not Found';
	Const C401UNAUTHORIZED = 'HTTP/1.1 401 Unauthorized';
	Const C406NOTACCEPTABLE = 'HTTP/1.1 406 Not Acceptable';
	Const C201CREATED = 'HTTP/1.1 201 Created';
	Const C200OK = 'HTTP/1.1 200 OK';
	Const C500INTERNALSERVERERROR = 'HTTP/1.1 500 Internal Server Error';
	Const USERNAME = 'admin@restuser';
	Const PASSWORD = 'admin@Access';

	public $HTTPStatus = 'HTTP/1.1 200 OK';

	protected $requestReader;
	protected $model = null;

	public function __construct($id, $module = null) {
		parent::__construct($id, $module);
		$this->requestReader = new ERequestReader('php://input');
	}

	public function filters() {
		$restFilters = array('restAccessRules+ restList restView restCreate restUpdate restDelete');
		if(method_exists($this, '_filters'))
			return CMap::mergeArray($restFilters, $this->_filters());
		else
			return $restFilters;
	} 

	public function accessRules()
	{
	$restAccessRules = array(
		array('allow',	// allow all users to perform 'index' and 'view' actions
				'actions'=>array('restList', 'restView', 'restCreate', 'restUpdate', 'restDelete', 'error'),
				'users'=>array('*'),
		));

	if(method_exists($this, '_accessRules'))
		return CMap::mergeArray($restAccessRules, $this->_accessRules());
	else
		return $restAccessRules;
	}	

	/**
	 * Controls access to restfull requests
	 */ 
	public function filterRestAccessRules( $c )
  {
    Yii::app()->clientScript->reset(); //Remove any scripts registered by Controller Class

    //For requests from JS check that a user is loged in and throw validateUser
    //validateUser can/should be overridden in your controller.
    if(!Yii::app()->user->isGuest && $this->validateAjaxUser($this->action->id)) 
      $c->run(); 
    else 
    {
      Yii::app()->errorHandler->errorAction = '/' . $this->uniqueid . '/error';

      if(!(isset($_SERVER['HTTP_X_'.self::APPLICATION_ID.'_USERNAME']) and isset($_SERVER['HTTP_X_'.self::APPLICATION_ID.'_PASSWORD']))) {
        // Error: Unauthorized
        throw new CHttpException(401, 'You are not authorized to proform this action.');
      }
      $username = $_SERVER['HTTP_X_'.self::APPLICATION_ID.'_USERNAME'];
      $password = $_SERVER['HTTP_X_'.self::APPLICATION_ID.'_PASSWORD'];
      // Find the user
      if($username != self::USERNAME)
      {
        // Error: Unauthorized
        throw new CHttpException(401, 'Error: User Name is invalid');
      } 
      else if($password != self::PASSWORD) 
      {
        // Error: Unauthorized
        throw new CHttpException(401, 'Error: User Password is invalid');
      } 
      // This tells the filter chain $c to keep processing.
      $c->run(); 
    }
  }	


	/**
	 * Custom error handler for restfull Errors
	 */ 
	public function actionError()
	{
    if($error=Yii::app()->errorHandler->error)
    {
      if(!Yii::app()->request->isAjaxRequest)
        $this->HTTPStatus = $this->getHttpStatus($error['code'], 'C500INTERNALSERVERERROR');

      $this->renderJson(array('success' => false, 'message' => $error['message'], 'data' => array('errorCode'=>$error['code'])));
    }
	}

	/**
	 * Get HTTP Status Headers From code
	 */ 
	public function getHttpStatus($statusCode, $default='C200OK')
	{
	switch ($statusCode) {
		case '200':
		return self::C200OK;
		break;
		case '201':
		return self::C201CREATED;
		break;
		case '401':
		return self::C401UNAUTHORIZED;
		break;
		case '404':
		return self::C404NOTFOUND;
		break;
		case '406':
		return self::C406NOTACCEPTABLE;
		break;
		case '500':
		return self::C500INTERNALSERVERERROR;
		break;
		default:
		return self::$default;
	}
	}

 /**
	****************************************************************************************** 
	******************************************************************************************
	* Actions that are tiggered by RESTFull requests
	* To change their default behavoir 
	* you should overide "doRest..." Methods in the controller 
	* and leave these actions as is
	******************************************************************************************
	******************************************************************************************
	 */

	/**
	 * Renders list of data assosiated with controller as json
	 */
  public function actionRestList() {
	  $this->doRestList();
	}
	
	/**
	 * Renders View of record as json
	 * Or Custom method
	 */ 
	public function actionRestView($id, $var=null, $var2=null)
	{
	//If the value is numeric we can assume we have a record ID
	if($this->isPk($id))
		$this->doRestView($id);
	else
	{
		//if the $id is not numeric 
		//we are assume that the client is attempting to call a custom method
		//There may optionaly be a second param `$var` passed in the url
		if(isset($var) && isset($var2))
		$var = array($var, $var2);
		$id = 'doCustomRestGet' . ucfirst($id);
		if(method_exists($this, $id))
		$this->$id($var);
		else
		throw new CHttpException(500, 'Method does not exist.');
	}
	}

	/**
	 * Updated record
	 */ 
	public function actionRestUpdate($id, $var=false)
	{
		$this->HTTPStatus = $this->getHttpStatus('201');

		if(!$var)
			$this->doRestUpdate($id, $this->data());
		else
		{
			$var = 'doCustomRestPut' . ucfirst($var);
			if(method_exists($this, $var))
				$this->$var($id, $this->data());
			else if($this->isPk($var))
				$this->doRestUpdate($id, $this->data());
			else
				throw new CHttpException(500, 'Method does not exist.');
		}
	}

	/**
	 * Creates new record
	 */ 
	public function actionRestCreate($id=null) {
		$this->HTTPStatus = $this->getHttpStatus('201');

		if(!$id) {
			$this->doRestCreate($this->data());
		}
		else
		{
			//we can assume if $id is set the user is trying to call a custom method
			$id = 'doCustomRestPost' . ucfirst($id);
			if(method_exists($this, $id))
				$this->$id($this->data());
			else if($this->isPk($var))
				$this->doRestCreate($this->data());
			else
				throw new CHttpException(500, 'Method does not exist.');
		}
	}

	/**
	 * Deletes record
	 */ 
	public function actionRestDelete($id)
	{
	$this->doRestDelete($id);
	}

	 /**
	****************************************************************************************** 
	******************************************************************************************
	* Helper functions for processing Rest data 
	******************************************************************************************
	******************************************************************************************
	 */
	
	/**
	 * Takes array and renders Json String
	 */ 
	protected function renderJson($data) {
		$this->layout = 'ext.restfullyii.views.layouts.json';
		$this->render('ext.restfullyii.views.api.output', array('data'=>$data));
	}


	/**
	 * Get data submited by the client
	 */ 
	public function data() {
    $request = $this->requestReader->getContents();
		if ($request) {
      if ($json_post = CJSON::decode($request)){
				return $json_post;
			}else{
				parse_str($request,$variables);
				return $variables;
			}
		}
		return false;
	}

	/**
	 * Returns the model assosiated with this controller.
	 * The assumption is that the model name matches your controller name
	 * If this is not the case you should override this method in your controller
	 */ 
  public function getModel() {
		if ($this->model === null) {
      $modelName = str_replace('Controller', '', get_class($this)); 
      $this->model = new $modelName;
		}
		return $this->model;
	}

	/**
	* Helper for loading a single model
	*/
	protected function loadModel($id) {
		return $this->getModel()->findByPk($id);
	}
	
	/**
	 * Helper for saving single/mutliple models 
	 * Multiple models now working
	 */ 
  private function saveModel($model, $data) {
		if(!isset($data[0])) {
			$model->attributes = $data;
			$models[] = $model;
		}
    else {
			for($i=0; $i<count($data); $i++) {
				$models[$i] = $this->getModel();
				$models[$i]->attributes = $data[$i];
				if(!$models[$i]->validate())
          throw new CHttpException(406, 'Model could not be saved as vildation failed.');
        $this->model = null; //Unsetting model to null to force create new model object.
			}
		}

    for($cnt=0;$cnt<count($models);$cnt++) {
      $temp = $models[$cnt];
			if(!$temp->save())
				throw new CHttpException(406, 'Model could not be saved');
			else
        $ids[] = $temp->id;
      unset($temp);
		}
		return $ids;
	} 


	/**
	****************************************************************************************** 
	******************************************************************************************
	* OVERIDE THE METHODS BELOW IN YOUR CONTROLLER TO REMOVE/ALTER DEFAULT FUNCTIONALITY
	******************************************************************************************
	******************************************************************************************
	 */
	
	/**
	 * Override this function if your model uses a non Numeric PK.
	 */
	public function isPk($pk) {
		return filter_var($pk, FILTER_VALIDATE_INT) !== false;
  } 

  /**
   * You should override this method to provide stronger access control 
   * to specifc restfull actions via AJAX
   */ 
  public function validateAjaxUser($action)
  {
    return false;
  }

	/**
	 * This is broken out as a sperate method from actionRestList 
	 * To allow for easy overriding in the controller
	 * and to allow for easy unit testing
	 */ 
	public function doRestList()
	{
	$this->renderJson(array('success'=>true, 'message'=>'Records Retrieved Successfully', 'data'=>$this->getModel()->findAll()));
	}

	 /**
	 * This is broken out as a sperate method from actionRestView
	 * To allow for easy overriding in the controller
	 * adn to allow for easy unit testing
	 */ 
	public function doRestView($id)
	{
	$this->renderJson(array('success'=>true, 'message'=>'Record Retrieved Successfully', 'data'=>$this->loadModel($id)));
	}

	/**
	 * This is broken out as a sperate method from actionResUpdate 
	 * To allow for easy overriding in the controller
	 * and to allow for easy unit testing
	 */ 
	public function doRestUpdate($id, $data) {		
		$model = $this->saveModel($this->loadModel($id), $data);
		$this->renderJson(array('success'=>true, 'message'=>'Record Updated', 'data'=>array('id'=>$id)));
	}
	
	/**
	 * This is broken out as a sperate method from actionRestCreate 
	 * To allow for easy overriding in the controller
	 * and to alow for easy unit testing
	 */ 
	public function doRestCreate($data) {
		$model = $this->getModel();
		$ids = $this->saveModel($model, $data);
		$this->renderJson(array('success'=>true, 'message'=>'Record(s) Created', 'data'=>array('id'=>$ids)));
	}
	
	/**
	 * This is broken out as a sperate method from actionRestDelete 
	 * To allow for easy overridding in the controller
	 * and to alow for easy unit testing
	 */ 
	public function doRestDelete($id)
	{
	$model = $this->loadModel($id);
	if($model->delete())
		$data = array('success'=>true, 'message'=>'Record Deleted', 'data'=>array('id'=>$id));
	else
		throw new CHttpException(406, 'Could not delete model with ID: ' . $id);

	$this->renderJson($data);
	}


  /**
   * Provides the ability to Limit and offset results
   * http://example.local/api/sample/limit/1/2
   * The above example would limit results to 1
   * and offest them by 2
   */ 
	public function doCustomRestGetLimit($var) {
		$criteria = new CDbCriteria();
		
		if(is_array($var)){
	  	  $criteria->limit = $var[0];
	  	  $criteria->offset = $var[1];
		}
		else {
	  	  $criteria->limit = $var;
		}

		$this->renderJson(array('success'=>true, 'message'=>'Records Retrieved Successfully', 'data'=>$this->getModel()->findAll($criteria)));
	}

	/**
	 * Returns the current record count
	 * http://example.local/api/sample/count
	 */ 
	public function doCustomRestGetCount($var=null, $remote=true) {
    	$this->renderJson(array('success'=>true, 'message'=>'Record Count Retrieved Successfully', 'data'=>array('count'=> $this->getModel()->count() )));
	}

	/**
	 * Search by attribute
	 * Simply post a list of attributes and values you wish to search by
	 * http://example.local/api/sample/search
	 * POST = {'id':'6', 'name':'evan'}
	 */ 
	public function doCustomRestPostSearch($data)
	{
	$this->renderJson(array('success'=>true, 'message'=>'Records Retrieved Successfully', 'data'=>$this->getModel()->findAllByAttributes($data)));		
	}

	public function setRequestReader($requestReader) {
		$this->requestReader = $requestReader;
	}

	public function setModel($model) {
		$this->model = $model;
	}
}
Back to Top