Yii Framework Forum: REST support - Yii Framework Forum

Jump to content

Page 1 of 1
  • You cannot start a new topic
  • This topic is locked

REST support Rate Topic: ***** 1 Votes

#1 User is offline   Digital God 

  • Standard Member
  • PipPip
  • Yii
  • Group: Members
  • Posts: 178
  • Joined: 30-January 09

Posted 24 March 2009 - 04:59 AM

Hi, maybe there is some requests like this, but i haven't found them.
So, it is possible to add some restful support to yii? for example to CHttpRequest class?add some function to filter/secure request data before using them.

i've found only one way to get PUT and DELETE request in php
file_get_contents("php://input");


so, there is simple code

    $request->method = $_SERVER['REQUEST_METHOD'];
    $request->get = $_GET;
    $request->post = $_POST;
    if(  $request->method == "POST" ||
      $request->method == "PUT" )
    {
      $request->input = file_get_contents('php://input');

      if($request->method == "POST") {
        $request->post = $_POST;
      } else {
        $request->put = self::getPutParameters($request->input);
      }
    }

$request is CHttpRequest object

and getPutParameters is something like

  getPutParameters($input) {
    $data = $input;
    if(function_exists('mb_parse_str')) {
    mb_parse_str($data, $outputdata);
    } else {
      parse_str($data, $outputdata);
    }
      return $outputdata;
  }


i think many people will need this feature
1

#2 User is offline   qiang 

  • Yii Project Lead
  • Yii
  • Group: Yii Dev Team
  • Posts: 5,907
  • Joined: 04-October 08
  • Location:DC, USA

Posted 24 March 2009 - 06:55 AM

Yes, we already have a ticket requesting for this feature. We will add support for it.
0

#3 User is offline   lucifurious 

  • Standard Member
  • PipPip
  • Yii
  • Group: Members
  • Posts: 184
  • Joined: 15-March 09
  • Location:Atlanta, GA.

Posted 24 March 2009 - 01:01 PM

Here's a helper object I threw together to pull http data. Hope it helps.

Usage:


$_sResults = CAppHelpers::getRequest( 'http://www.yiiframework.com' );



<?php
/**
* CAppHelpers class file.
*
* @author Jerry Ablan <jablan@pogostick.com>
* @link http://www.pogostick.com.com/
* @copyright Copyright &copy; 2009 Pogostick, LLC
* @license http://www.pogostick.com/license/
*/

/**
* CHelpers provides helper methods
*
* @author Jerry Ablan <jablan@pogostick.com>
* @version $Id: CAppHelpers.php 17 2009-03-21 21:46:58Z jablan $
* @package system.web.helpers
* @since 1.0.3
*/
class CAppHelpers
{
/**
* Make the REST request
*
* @param unknown_type $sUrl
* @param unknown_type $sQueryString
* @return unknown
*/
public static function getRequest( $sUrl, $sQueryString = '', $sNewAgent = "" )
{
$sAgent = $sNewAgent;

if ( $sNewAgent == "" )
$sAgent = "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0; .NET CLR 2.0.50727; .NET CLR 3.0.04506; InfoPath.3)";

if ( function_exists( 'curl_init' ) )
{
// Use CURL if installed...
$oConn = curl_init();
curl_setopt( $oConn, CURLOPT_URL, $sUrl . ( $sQueryString != '' ? "?" . $sQueryString : '' ) );
curl_setopt( $oConn, CURLOPT_RETURNTRANSFER, true );
curl_setopt( $oConn, CURLOPT_USERAGENT, $sAgent );
curl_setopt( $oConn, CURLOPT_TIMEOUT, 60 );
curl_setopt( $oConn, CURLOPT_FOLLOWLOCATION, true );
$sResult = curl_exec( $oConn );
curl_close( $oConn );
}
else
{
// Non-CURL based version...
$oContext =
array('http' =>
array('method' => 'POST',
'header' => 'Content-type: application/x-www-form-urlencoded'."rn".
'User-Agent: ' . $sAgent ."rn".
'Content-length: ' . strlen($post_string),
'content' => $post_string)
);

$oContextId = stream_context_create( $oContext );

$oSocket = fopen( $sUrl . "?" . $sQueryString, 'r', false, $oContextId );

if ( $oSocket )
{
$sResult = '';

while ( !feof( $oSocket ) )
$sResult .= fgets( $oSocket, 4096 );

fclose( $oSocket );
}
}

return( $sResult );
}

/**
* Parse HTML field for a tag...
*
* @param mixed $sData
* @param mixed $sTag
* @param mixed $sTagEnd
* @param mixed $iStart
* @param mixed $sNear
* @return string
*/
public static function suckTag( $sData, $sTag, $sTagEnd, $iStart = 0, $sNear = null )
{
$_sResult = "";
$_l = strlen( $sTag );

// If near value given, get position of that as start
if ( $sNear != null )
{
$_iStart = stripos( $sData, $sNear, $iStart );
if ( $_iStart >= 0 )
$iStart = $_iStart + strlen( $sNear );
}

$_i = stripos( $sData, $sTag, $iStart );
$_k = strlen( $sTagEnd );

if ( $_i >= 0 )
{
$_j = stripos( $sData, $sTagEnd, $_i + $_l );

if ( $_j >= 0 )
{
$iStart = $_i;
$_sResult = substr( $sData, $_i + $_l,  $_j - $_i - $_l );
}

return( trim( $_sResult ) );
}

return( null );
}

/**
* Checks to see if the passed in data is an Url
*
* @param string $sData
* @return bool
*/
public static function isUrl( $sData )
{
return( ( @parse_url( $sData ) ) ? TRUE : FALSE );
}
}

0

#4 User is offline   wei 

  • Advanced Member
  • Yii
  • Group: Yii Dev Team
  • Posts: 147
  • Joined: 04-October 08

Posted 24 March 2009 - 05:24 PM

Be aware that prior to php 5.2.10. and curl pre-7.19.4 versions CURLOPT_FOLLOWLOCATION =true will unconditionally follow to all protocols supported. This includes FILE:///etc/path/to/passwords

Yes, PHP 5.2.10 not PHP 5.2.1

0

#5 User is offline   Digital God 

  • Standard Member
  • PipPip
  • Yii
  • Group: Members
  • Posts: 178
  • Joined: 30-January 09

Posted 25 March 2009 - 01:16 AM

the best solution is input stream - it is faster then curl, and users don't need to install curl extension.
0

#6 User is offline   swafsarl 

  • Newbie
  • Yii
  • Group: Members
  • Posts: 17
  • Joined: 20-February 10

Posted 13 March 2011 - 09:30 PM

View PostDigital God, on 24 March 2009 - 04:59 AM, said:

and getPutParameters is something like
 getPutParameters($input) {
    $data = $input;
    if(function_exists('mb_parse_str')) {
     mb_parse_str($data, $outputdata);
    } else {
      parse_str($data, $outputdata);
    }
      return $outputdata;
  }


Cant add a more secure script? something that detect sql injection, or mysql stuff, or whatever javascript?
0

#7 User is offline   Zephyr 

  • Newbie
  • Yii
  • Group: Members
  • Posts: 17
  • Joined: 31-July 11

Posted 31 October 2011 - 05:13 PM

I added some adjustment for those interested:

- Authentication put in Init since all of the actions would require some authentication
- The instance of the models are auto created, no more long switches, through
$m = $this->_model; //string name of the model
$model = $m::model()->findByPk($this->_id);

Just fill in your model names in the array.
- Error if the model doesnt exist in the init
- body message of error is a seperate view
- Authentication through UserIdentity Component
- Use of Yii::app()->request->getParam() instead of $_GET
- removed some unnessesary is_null checks
- instead of loading the model and then perfoming a delete, I choose to use deleteByPK method.

I find the controller more cleaner now. Still there is room for improvement, maybe people can help me out:
- Instead of calling _checkAuth() from the init. It would be cleaner if authentication goes through accessrules. I couldnt get it to work though.
- Models that can be used in the REST API are now defined in allowed_models array. Neater would be using accessrules too, or read which models Yii has access too...
- Status code in seperate component or behaviour.


Small note. My models are all capitalized, thats why I had to add a ucfirst to the model param. The model param is lowercase by default (and I didnt want to make the url casesensitive).

Note: This is not my code, kudos goes to the original poster(Digital God). I just adjusted to suit my needs and where I saw room for improvement

The code:

RestController.php (probally the only thing you need to adjust is the allowed_models array)
<?php
/**
 * Controller is the customized base controller class.
 * All controller classes for this application should extend from this base class.
 */
class RestController extends CController
{
    private $_model = false;
    private $_id = false;
    private $allowed_models = array('Locations');

    public $format = "json";
    public $layout = false;
    
    const APPLICATION_ID = 'ASCCPE';
    /*
    public function filters()
	{
		return array(
			'accessControl', // perform access control for CRUD operations
		);
	}
	public function accessRules()
	{
		return array(
            array('allow', // allow admin user to perform 'admin' and 'delete' actions
                'verbs'=>array('GET'),
				'users'=>array('*'),
             ),
			array('allow', // allow admin user to perform 'admin' and 'delete' actions
				'actions'=>array('list','delete','create','update'),
                'expression'=> $this->_checkAuth(),
                 'message'=>'Access Denied.'
			),
			array('deny',  // deny all users
				'users'=>array('*'),

			),
		);
	} */


    public function init(){
        //load the model from GET/POST
        $this->_checkAuth();
        $model_request = Yii::app()->request->getParam('model', false);

        if(!empty($model_request)){

            //fix. Models in my db begin with a capital letter, but requests are auto lowercased.
            //either remove this line, update the database to be lowercased or make all the models in allowed_models lowercased.
            //I prefered keeping original model name and just uppercase the request. Your pick.
            $model_request = ucfirst($model_request);

            //check if the model is allowed to be reached (in the array of allowed_models)
            //could be pretified by looking at accessrules somehow...
            if(in_array($model_request, $this->allowed_models)){
                $this->_model = $model_request;
                //load possible id from GET/POST, else set to false
                $this->_id = Yii::app()->request->getParam('id', false);
            } else {
                //throw a 501 if the model doesn't exist/isn't allowed
                $this->_model = false;
                $this->_sendResponse(501, 'Error: Mode <b>'.$_SERVER['REQUEST_METHOD'].'</b> is not implemented for model <b>'.$this->_model.'</b>');
                exit();
            }
        }
    }
    public function actionIndex()
    {
       print "test";
    }
     public function actionView()
    {
       //no need to check if id is send. If no id is defined, route will send it to list.
       $m = $this->_model;
       $model = $m::model()->findByPk($this->_id);
       if(!$model) {
            $this->_sendResponse(404, 'No Item found with id '. $this->_id);
       } else {
            $this->_sendResponse(200, $this->_getObjectEncoded($this->_model, $model->attributes));
       }
        exit;
    }
    public function actionCreate()
    {
       $m = $this->_model;
       $model = new $m;

        // Try to assign POST values to attributes
        foreach($_POST as $var=>$value) {
            // Does the model have this attribute?
            if($model->hasAttribute($var)) {
                $model->$var = $value;
            } else {
                // No, raise an error
                $this->_sendResponse(500, sprintf('Parameter <b>%s</b> is not allowed for model <b>%s</b>', $var, $this->_model) );
            }
        }
        if($model->save()) {
            // Saving was OK
            $this->_sendResponse(200, $this->_getObjectEncoded($this->_model, $model->attributes) );
        } else {
            // Errors occurred
            $msg = "<h1>Error</h1>";
            $msg .= sprintf("Couldn't create model <b>%s</b>", $this->_model);
            $msg .= "<ul>";
            foreach($model->errors as $attribute=>$attr_errors) {
                $msg .= "<li>Attribute: $attribute</li>";
                $msg .= "<ul>";
                foreach($attr_errors as $attr_error) {
                    $msg .= "<li>$attr_error</li>";
                }
                $msg .= "</ul>";
            }
            $msg .= "</ul>";
            $this->_sendResponse(500, $msg );
        }

        var_dump($_REQUEST);
    }
    public function actionUpdate()
    {
        // Check if id was submitted via GET
        if(!$this->_id){
            $this->_sendResponse(500, 'Error: Parameter <b>id</b> is missing' );
        }
        // Get PUT parameters
        parse_str(file_get_contents('php://input'), $put_vars);

       $m = $this->_model;
       $model = $m::model()->findByPk($this->_id);
       if(!$model) {
           $this->_sendResponse(404, 'No Item found with id '. $this->_id);
       } else {
            $this->_sendResponse(200, $this->_getObjectEncoded($this->_model, $model->attributes));
       }
        // Try to assign PUT parameters to attributes
        foreach($put_vars as $var=>$value) {
            // Does model have this attribute?
            if($model->hasAttribute($var)) {
                $model->$var = $value;
            } else {
                // No, raise error
                $this->_sendResponse(500, sprintf('Parameter <b>%s</b> is not allowed for model <b>%s</b>', $var, $this->_model) );
            }
        }
        // Try to save the model
        if($model->save()) {
            $this->_sendResponse(200, sprintf('The model <b>%s</b> with id <b>%s</b> has been updated.', $this->_model,  $this->_id) );
        } else {
            $msg = "<h1>Error</h1>";
            $msg .= sprintf("Couldn't update model <b>%s</b>", $this->_model);
            $msg .= "<ul>";
            foreach($model->errors as $attribute=>$attr_errors) {
                $msg .= "<li>Attribute: $attribute</li>";
                $msg .= "<ul>";
                foreach($attr_errors as $attr_error) {
                    $msg .= "<li>$attr_error</li>";
                }
                $msg .= "</ul>";
            }
            $msg .= "</ul>";
            $this->_sendResponse(500, $msg );
        }
    }
    public function actionDelete()
    {
        // Check if id was submitted via GET
        if(!$this->_id){
            $this->_sendResponse(500, 'Error: Parameter <b>id</b> is missing' );
        }
       $m = $this->_model;
       //delete without loading complete model
       if($m::model()->deleteByPk($this->_id)){
          $this->_sendResponse(200, sprintf("Model <b>%s</b> with ID <b>%s</b> has been deleted.",$this->_model, $this->_id) );
       } else {
          //delete failed
           $this->_sendResponse(500, sprintf("Error: Couldn't delete model <b>%s</b> with ID <b>%s</b>.",$this->_model, $this->_id) );
       }
    }
    public function actionList()
    {
       $m = $this->_model;
       $models = $m::model()->findAll();

        if(is_null($models)) {
            $this->_sendResponse(200, sprintf('No items where found for model <b>%s</b>', $this->_model) );
        } else {
            $rows = array();
            foreach($models as $model)
                $rows[] = $model->attributes;

            $this->_sendResponse(200, CJSON::encode($rows));
        }
    }

    private function _sendResponse($status = 200, $body = '', $content_type = 'text/html')
    {
        $status_header = 'HTTP/1.1 ' . $status . ' ' . $this->_getStatusCodeMessage($status);
        // set the status
        header($status_header);
        // set the content type
        header('Content-type: ' . $content_type);

        // pages with body are easy
        if($body != '')
        {
            // send the body
            echo $body;
            exit;
        }
        // we need to create the body if none is passed
        else
        {
            // create some body messages
            $message = '';

            // this is purely optional, but makes the pages a little nicer to read
            // for your users.  Since you won't likely send a lot of different status codes,
            // this also shouldn't be too ponderous to maintain
            switch($status)
            {
                case 401:
                    $message = 'You must be authorized to view this page.';
                    break;
                case 404:
                    $message = 'The requested URL ' . $_SERVER['REQUEST_URI'] . ' was not found.';
                    break;
                case 500:
                    $message = 'The server encountered an error processing your request.';
                    break;
                case 501:
                    $message = 'The requested method is not implemented.';
                    break;
            }

            // servers don't always have a signature turned on (this is an apache directive "ServerSignature On")
            $signature = ($_SERVER['SERVER_SIGNATURE'] == '') ? $_SERVER['SERVER_SOFTWARE'] . ' Server at ' . $_SERVER['SERVER_NAME'] . ' Port ' . $_SERVER['SERVER_PORT'] : $_SERVER['SERVER_SIGNATURE'];

            $this->render('message', array(
                 'status' => $status,
                 'statusmessage' => $this->_getStatusCodeMessage($status),
                 'signature' => $signature
               ));
            exit;
        }
    }
    private function _getStatusCodeMessage($status)
    {
        // these could be stored in a .ini file and loaded
        // via parse_ini_file()... however, this will suffice
        // for an example
        $codes = Array(
            100 => 'Continue',
            101 => 'Switching Protocols',
            200 => 'OK',
            201 => 'Created',
            202 => 'Accepted',
            203 => 'Non-Authoritative Information',
            204 => 'No Content',
            205 => 'Reset Content',
            206 => 'Partial Content',
            300 => 'Multiple Choices',
            301 => 'Moved Permanently',
            302 => 'Found',
            303 => 'See Other',
            304 => 'Not Modified',
            305 => 'Use Proxy',
            306 => '(Unused)',
            307 => 'Temporary Redirect',
            400 => 'Bad Request',
            401 => 'Unauthorized',
            402 => 'Payment Required',
            403 => 'Forbidden',
            404 => 'Not Found',
            405 => 'Method Not Allowed',
            406 => 'Not Acceptable',
            407 => 'Proxy Authentication Required',
            408 => 'Request Timeout',
            409 => 'Conflict',
            410 => 'Gone',
            411 => 'Length Required',
            412 => 'Precondition Failed',
            413 => 'Request Entity Too Large',
            414 => 'Request-URI Too Long',
            415 => 'Unsupported Media Type',
            416 => 'Requested Range Not Satisfiable',
            417 => 'Expectation Failed',
            500 => 'Internal Server Error',
            501 => 'Not Implemented',
            502 => 'Bad Gateway',
            503 => 'Service Unavailable',
            504 => 'Gateway Timeout',
            505 => 'HTTP Version Not Supported'
        );

        return (isset($codes[$status])) ? $codes[$status] : '';
    }
    public function _checkAuth()
    {
        // Check if we have the USERNAME and PASSWORD HTTP headers set?
        if(!(isset($_SERVER['HTTP_X_'.self::APPLICATION_ID.'_USERNAME']) and isset($_SERVER['HTTP_X_'.self::APPLICATION_ID.'_PASSWORD']))) {
            // Error: Unauthorized
            $this->_sendResponse(401);
            exit;
        }
        $username = $_SERVER['HTTP_X_'.self::APPLICATION_ID.'_USERNAME'];
        $password = $_SERVER['HTTP_X_'.self::APPLICATION_ID.'_PASSWORD'];

        // Find the user
        $user_identity = new UserIdentity($username,$password);
        $user_identity->authenticate();

        if($user_identity->errorCode===UserIdentity::ERROR_USERNAME_INVALID) {
            // Error: Unauthorized
            $this->_sendResponse(401, 'Error: User Name is invalid');
        } else if($user_identity->errorCode===UserIdentity::ERROR_PASSWORD_INVALID) {
            // Error: Unauthorized
           $this->_sendResponse(401, 'Error: User Password is invalid');
            //return false;
        }
    }
    public function actionAccessDenied(){
         $this->_sendResponse(401, 'Error: User Password is invalid');
    }
    private function _getObjectEncoded($model, $array)
    {
        if(isset($_GET['format']))
            $this->format = $_GET['format'];

        if($this->format=='json')
        {
            return CJSON::encode($array);
        }
        elseif($this->format=='xml')
        {
            $result = '<?xml version="1.0">';
            $result .= "\n<$model>\n";
            foreach($array as $key=>$value)
                $result .= "    <$key>".utf8_encode($value)."</$key>\n";
            $result .= '</'.$model.'>';
            return $result;
        }
        else
        {
            return;
        }
    }
}


the UserIdentity Component (uses User model, adjust to your needs)
class UserIdentity extends CUserIdentity
{
    private $_id;
	public function authenticate()
	{
        $record=Users::model()->findByAttributes(array('Username'=>$this->username));
        if($record===null)
            $this->errorCode=self::ERROR_USERNAME_INVALID;
        else if($record->Password!==md5($this->password))
            $this->errorCode=self::ERROR_PASSWORD_INVALID;
        else
        {
            $this->_id=$record->UserId;
            $this->errorCode=self::ERROR_NONE;
        }
        return !$this->errorCode;
	}
    public function getId()
    {
        return $this->_id;
    }
}


the message view (put in view/rest)
<!DOCTYPE HTML>
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
        <title><?php echo $status . ' ' . $statusmessage ?></title>
    </head>
    <body>
        <h1><?php echo $statusmessage; ?></h1>
        <p><?php echo $message; ?></p>
        <hr />
        <address><?php echo $signature; ?></address>
    </body>
</html>


the url rewrite I changed a little bit (basicly I switched the default urlmanagement around to allow the restcontroller be put in a module).

		'urlManager'=>array(
			'urlFormat'=>'path',
            'showScriptName'=>false,
			'rules'=>array(
                  // REST patterns for API module
                array('rest/view', 'pattern'=>'api/<model:\w+>/<id:\d+>', 'verb'=>'GET'),
                array('rest/list', 'pattern'=>'api/<model:\w+>', 'verb'=>'GET'),
                array('rest/update', 'pattern'=>'api/<model:\w+>/<id:\d+>', 'verb'=>'PUT'),
                array('rest/delete', 'pattern'=>'api/<model:\w+>/<id:\d+>', 'verb'=>'DELETE'),
                array('rest/create', 'pattern'=>'api/<model:\w+>', 'verb'=>'POST'),
                //default controller/action path
                '<controller:\w+>/<id:\d+>'=>'<controller>/view',
				'<controller:\w+>/<action:\w+>/<id:\d+>'=>'<controller>/<action>',
				'<controller:\w+>/<action:\w+>'=>'<controller>/<action>',
                
			),
		),

0

Share this topic:


Page 1 of 1
  • You cannot start a new topic
  • This topic is locked

1 User(s) are reading this topic
0 members, 1 guests, 0 anonymous users