Rest Api


class UserController extends ApiController

{

	public function actionCreate()

	{

		$model = new User();

		$model->scenario = 'create';

		$model->attributes = $this->params;

		$model->createdAt = date('Y-m-d H:i:s');

		$model->createdById = 1;

		if ($model->validate()) {

			try {

				$model->save();

				$statusCode = RestServer::SUCCESS;

			} catch (Exception $e) {

				$statusCode = RestServer::USER_CREATE_FAILED;

			}

		} else {

			$errors = $model->errors;

			$error = current($errors);

			$statusCode = RestServer::VALIDATION_FAILED;

			$statusMessage = sprintf($this->statusMessages[RestServer::VALIDATION_FAILED], $error[0]);

		}

		

		$this->sendResponse($statusCode, $statusMessage);

	}

}

Above is a sample REST API code written to creating a user. If any validation error I`ll send first error in the $model->errors object. Response is like "{"statusCode":100,"statusMessage":"Validation failed. Reason:First Name cannot be blank."}".

Here statusCode is same for any validation error but statusMessage is changed according to the Yii error message.

Now I want to send separate error codes to each validation error. Ex: first name required - 100, last name required - 101 etc…

Is there any proper way to achieve this rather than checking them individually? Can we bind error code to the rules without interrupting to the web flow.

Thanks,

Aruna

You can create a integer error code as the sum of two parts:

  1. The type of the validator (CRequiredValidator -> required, CStringValidator -> length …)

    So you have to map the validator classes to a number like

    CRequiredValidator => 100

    CStringValidator => 200

    You can use the array of CValidator::$builtInValidators to generate the validator code.

  2. the attribute (firstname, lastname …)

    The integer value can be the index+1 of the errorattribute in the models array attributeNames().

    firstname -> 1

    lastname -> 2

You easily can get the index of the errorattribute, because the errors are assigned for each attribute.

But the problem is, that you don’t know which type of validator did add the errormessage.

So you have to override the validate() and the addError() method.

Create a ‘ApiBaseModel’ (User extends ApiBaseModel) or alternative a ‘ApiHelper’ class with static methods or a ‘ApiBehavior’.




class ApiBaseModel extends CActiveRecord 

{

   protected $_apiErrors=array();

   private $_currentValidator;




   public function getApiErrors() 

   {

      return $this->_apiErrors;

   }    




   public function getValidatorApiCode($validator) 

   {

        $classes = array_values(CValidator::$builtInValidators);

        $idx = array_search(get_class($validator), $classes);

        return $idx !== false ? ($idx+1) * 100 : 10000; //= custom validators           

   }      




   public function getAttributeApiCode($attribute) 

   {

        $names = $this->attributeNames();

        return array_search($attribute, $names);           

   }   




        //the only difference: assign the _currentValidator   

        public function validate($attributes=null, $clearErrors=true)

	{

		$this->_currentValidator=null;

                if($clearErrors)

			$this->clearErrors();

		if($this->beforeValidate())

		{

			foreach($this->getValidators() as $validator)

                        {

                                $this->_currentValidator=$validator; //<---- set the current validator

				$validator->validate($this,$attributes);

                        }   

			$this->afterValidate();

			return !$this->hasErrors();

		}

		else

			return false;

	}  




        //assign the apiCode with the error message to the _apiErrors   

        public function addError($attribute,$error)

	{

            $apiCode = $this->getValidatorApiCode($this->_currentValidator) + $this->getValidatorApiCode($this->_currentValidator);

            if(!is_array($this->_apiErrors[$apiCode])

                $this->_apiErrors[$apiCode]=array();

            $this->_apiErrors[$apiCode][]=$error;

             

            parent::addError($attribute,$error);

	}

      

}  







Hi Jobio,

Thank you for the solution. I just wanted to get an error code for validation instead of a error message the I can directly send that error code to REST request. Anyway I achieved it another way.





	// Model rules

	public function rules()

	{

		// NOTE: you should only define rules for those attributes that

		// will receive user inputs.

		$rules = array(

			array('firstName, lastName', 'required', 'on'=>'create'),

		);

		

		if ($this->isApi) {

			$rules = array(

				array('firstName', 'required', 'message'=>100, 'on'=>'create'),

				array('lastName', 'required', 'message'=>101, 'on'=>'create'),

			);

		}

		

		return $rules;

	}

	

	// Controller action

	public function actionCreate()

	{

		$model = new User();

		$model->scenario = 'create';

		$model->isApi = true; // Saying that action related to API call and rules prepared according to it

		$model->attributes = $this->params;

		if ($model->validate()) {

			try {

				$model->save();

				$statusCode = RestServer::SUCCESS;

			} catch (Exception $e) {

				$statusCode = RestServer::FAILED;

			}

		} else {

			// Getting the first error of the $model->errors, So it will be error code

			$errors = $model->errors;

			$error = current($errors);

			$statusCode = $error[0];

		}

		

		$this->sendResponse($statusCode);// Sending REST response via another function.

	}

Thanks,

Aruna