<?php
/**
* Process Ext.Direct HTTP requests
*
* @author jbruni
*
*/
class ExtDirectRequest
{
/**
* @var array Actions to be executed in this request
*/
public $actions = array();
/**
* @var string Name of the API class (ExtDirectActions descendant)
*/
public $actions_class;
/**
* @param string $actions_class Name of the API class (ExtDirectActions descendant)
*/
public function __construct( $actions_class )
{
$this->actions_class = $actions_class;
if ( isset( $_POST['extAction'] ) )
$this->getFormAction();
else
$this->getRequestActions();
}
/**
* Instantiate actions to be executed in this request using "extAction" (form)
*/
protected function getFormAction()
{
$extParameters = $_POST;
foreach( array( 'extAction', 'extMethod', 'extTID', 'extUpload' ) as $variable )
{
if ( !isset( $extParameters[$variable] ) )
$$variable = '';
else
{
$$variable = $extParameters[$variable];
unset( $extParameters[$variable] );
}
}
$this->actions[] = new $this->actions_class( $extAction, $extMethod, $extParameters, $extTID, $extUpload );
}
/**
* Instantiate actions to be executed in this request (without "extAction")
*/
protected function getRequestActions()
{
$input = file_get_contents( 'php://input' );
$request = json_decode( $input );
if ( !is_array( $request ) )
$request = array( $request );
foreach( $request as $rpc )
{
foreach( array( 'type', 'action', 'method', 'data', 'tid' ) as $variable )
$$variable = ( isset( $rpc->$variable ) ? $rpc->$variable : '' );
if ( $type == 'rpc' )
$this->actions[] = new $this->actions_class( $action, $method, $data, $tid, false );
}
}
}
/**
* Store HTTP response contents for output
*
* @author jbruni
*
*/
class ExtDirectResponse
{
/**
* @var array HTTP headers to be sent in the response
*/
public $headers = array();
/**
* @var content HTTP body to be sent in the response
*/
public $content = '';
}
/**
* Ext.Direct API controller
*
* @author jbruni
*
*/
class ExtDirectController
{
/**
* @var string Name of the API class (ExtDirectActions descendant)
*/
public $actions_class;
/**
* @var ExtDirectRequest Object to process HTTP request
*/
public $request;
/**
* @var ExtDirectResponse Object to store HTTP response
*/
public $response;
/**
* @var string Filename of Javascript template file
*/
public $template_file = 'app.js';
/**
* @var array Contents of the template elements
*/
public $template_elements = array();
/**
* @param string $actions_class Name of the API class (ExtDirectActions descendant)
* @param boolean $autorun If true, automatically run the controller
*/
public function __construct( $actions_class, $autorun = true )
{
$this->actions_class = $actions_class;
$this->request = new ExtDirectRequest( $this->actions_class );
$this->response = new ExtDirectResponse();
if ( $autorun )
{
$this->run();
$this->output();
exit();
}
}
/**
* @return string Parsed Javascript template, including the API declaration of the class (Ext.Direct.Provider object actions configuration)
*/
public function getJavascript()
{
$template = file_get_contents( $this->template_file );
$this->template_elements['[%actions%]'] = $this->getActionsAPI( $this->actions_class );
return strtr( $template, $this->template_elements );
}
/**
* @param string $class Name of the API class (ExtDirectActions descendant)
* @return string API declaration of the class (Ext.Direct.Provider object "actions" configuration)
*/
protected function getActionsAPI( $actions_class )
{
$methods = array();
$reflection = new ReflectionClass( $actions_class );
foreach( $reflection->getMethods() as $method )
if ( $method->isPublic() && ( $method->getDeclaringClass()->name == $actions_class ) )
$methods[] = array( 'name' => $method->getName(), 'len' => $method->getNumberOfRequiredParameters() );
return json_encode( $methods );
}
/**
* Process the request, execute the actions, and generate the response
*/
public function run()
{
if ( empty( $this->request->actions ) )
$this->response->content = $this->getJavascript();
else
{
$response = array();
foreach( $this->request->actions as $action )
$response[] = $action->run();
if ( count( $response ) > 1 )
$this->response->content = utf8_encode( json_encode( $response ) );
else
$this->response->content = utf8_encode( json_encode( $response[0] ) );
}
$this->response->headers[] = 'Content-Type: text/javascript';
}
/**
* Output response contents
*/
public function output()
{
foreach( $this->response->headers as $header )
header( $header );
echo $this->response->content;
}
}
/**
* Base class for Ext.Direct APIs
*
* @author jbruni
*
*/
abstract class ExtDirectActions
{
/**
* @var string Class name
*/
public $action;
/**
* @var string Method name
*/
public $method;
/**
* @var mixed Method parameters
*/
public $parameters;
/**
* @var integer Unique identifier for the transaction
*/
public $transaction_id;
/**
* @var boolean True if there is a file upload; false otherwise
*/
public $upload = false;
/**
* @var boolean Set this to true to allow exception detailed information in the output
*/
public $debug = false;
/**
* @param string $action Class name
* @param string $method Method name
* @param mixed $parameters Method parameters
* @param integer $transaction_id Unique identifier for the transaction
* @param boolean $upload True if there is a file upload; false otherwise
* @param boolean $debug Set this to true to allow exception detailed information in the output
*/
final public function __construct( $action, $method, $parameters, $transaction_id, $upload = false, $debug = null )
{
foreach( array( 'action', 'method', 'parameters', 'transaction_id', 'upload', 'debug' ) as $parameter )
$this->$parameter = $$parameter;
}
/**
* @return array Result of the action execution
*/
public function run()
{
$response = array(
'type' => 'rpc',
'tid' => $this->transaction_id,
'action' => get_class( $this ),
'method' => $this->method
);
try
{
$result = $this->callAction();
$response['result'] = $result;
}
catch ( Exception $e )
{
$response['result'] = 'Failure';
if ( $this->debug )
$response = array(
'type' => 'exception',
'tid' => $this->transaction_id,
'message' => $e->getMessage(),
'where' => $e->getTraceAsString()
);
}
array_walk_recursive( $response, array( $this, 'utf8_encode' ) );
return $response;
}
/**
* @param mixed $value If it is a string, it will be UTF8 encoded
* @param mixed $key Not used (passed by "array_walk_recursive" function)
* @return mixed UTF8 encoded string, or unchanged value if not a string
*/
protected function &utf8_encode( &$value, $key )
{
if ( is_string( $value ) )
$value = utf8_encode( $value );
return $value;
}
/**
* @return mixed Result of the action
*/
protected function callAction()
{
$class = get_class( $this );
if ( $this->action != $class )
throw new Exception( 'Only calls to ' . $class . ' are allowed; tried to call ' . $this->action, E_USER_ERROR );
if ( !method_exists( $class, $this->method ) )
throw new Exception( 'Call to undefined or not allowed ' . $class . ' method ' . $this->method, E_USER_ERROR );
$method = new ReflectionMethod( $class, $this->method );
$params = $method->getNumberOfRequiredParameters();
if ( count( $this->parameters ) < $params )
throw new Exception( 'Call to ' . $class . ' method ' . $this->method . ' needs at least ' . $params . ' parameters', E_USER_ERROR );
return call_user_func_array( array( $this, $this->method ), $this->parameters );
}
}
?>
|