PHP HOW TO: create server API
In one my project, i need to be able to connect to and communicate with the server and use some of its features. I had to create a simple API that will be used for communication. This is my simple solution, how to create API.
Requirements
The main requirements were:
- easy to use
- easily expandable
- exchange data using json
My application is written in PHP (NETTE framework), but the sample is easily copied to another platform.
How to call api
I used a JSON in which I define all. I define the method which you want to call, and the data that I want to pass to the api. My JSON looks like this:
1 2 3 4 5 |
{ "method":"myMethodName", "param1":"value1", "param2":"value2" } |
This JSON define method with name “myMethodName” which i want call, and two params, which i need set into this method.
Api will be called very simply as follows:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
//api url $url = "http://myserver.com/myapi/"; //call with params $content = json_encode(array('method' => 'getContact', 'email' => 'test@test.com')); $curl = curl_init($url); curl_setopt($curl, CURLOPT_HEADER, false); curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); //set Json curl_setopt($curl, CURLOPT_HTTPHEADER, array("Content-type: application/json")); //call with POST curl_setopt($curl, CURLOPT_POST, true); curl_setopt($curl, CURLOPT_POSTFIELDS, $content); //result from api is JSON $json_response = curl_exec($curl); curl_close($curl); //decode output Json $response = json_decode($json_response); |
Result from API
All methods api will have the same result. The return value is again JSON. Always will contain information about the error and the result. The structure will be as follows:
1 2 3 4 5 6 7 |
{ "error":false, "message":null, "data":{ "key":"value", "key2":"value2" } |
- Error – true|false value. If an error occurs, it will be true
- Message – If an error occurs, the message will be added into result
- Data – In case of success, contains data
In the previous example we can add following code:
1 2 3 4 5 6 |
//checks result if( $response->error == false ){ var_dump($response->data ); }else{ echo $response->message; } |
Create API class
First, we define class and methods that will return results. We can do this as follows:
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 |
class ApiPresenter extends AnyBasePresenter { //output array private $outputJson = array( 'error' => true, 'message' => 'Unknow method', 'data' => array() ); //input array private $inputJson = array(); /** * function sets the error message and sends it to the output */ private function _setError($message){ $this->outputJson['message'] = 'Method: ' . (array_key_exists('method', $this->inputJson['method']) ? $this->inputJson['method'] : 'unknow') . ': ' . $message; $this->outputJson['error'] = true; //terminate app $this->terminate(); } /** * Sets the data at the output * @param array $user */ private function _setSuccess($user){ $this->outputJson['message'] = NULL; $this->outputJson['error'] = false; $this->outputJson['data'] = $user; $this->terminate(); } /** * terminate app and push output */ public function terminate(){ Header('Content-Type: application/json'); echo json_encode($this->outputJson); parent::terminate(); } } |
Methods _setError() and _setSuccess() will call in case of success or failure. The method terminate () is called whenever api end. The method returns the result to the OUTPUT.
Now we can create a constructor that calls method defined in JSON. In my case I use function with name startup(), which is used in Nette.
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 |
/** * method is called first from whole class, it is possible to replace with the constructor of class */ public function startup(){ //parent startup method parent::startup(); //parse input json $this->_setInputJson(); //check if exists json and contain key "method" if( $this->inputJson && $this->inputJson['method'] ){ //check if exists method if ( !String::startsWith('_', $this->inputJson['method'] ) && method_exists( $this, $this->inputJson['method'] )){ //and call method from class $methodname = $this->inputJson['method']; $this->$methodname(); }else{ //set error and push to output $this->_setError('unknown method'); } } } /** * function load json from std::input */ private function _setInputJson(){ $post = file_get_contents('php://input'); $inputJson = (array)json_decode($post); if( count($inputJson) ){ //convert input json vales to lower strings $this->inputJson = $this->_lowerArray($inputJson); } } /** * function convert array keys and values to lower strings * @param array * @return array */ private function _lowerArray( $array ){ foreach( $array as $id => $val ){ $array[String::lower($id)] = (is_array($val) ? $this->_lowerArray($val) : String::lower(trim($val))); } return $array; } |
Function converts the input data to JSON object. Next checks whether a called method exists, and if so, it starts this method.
Finaly we can write first API method (this code is only example):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
/** * Api method getContact */ private function getcontact(){ /** ... some code inside the function ... */ if( !array_key_exists( 'email', $this->inputJson ) ){ $this->_setError('Please enter email'); } $user = $this->getModelClass()->getUserByEmail($this->inputJson['email']); if( $user ){ $this->_setSuccess($user); }else{ $this->_setError('User not found'); } $this->terminate(); } |
Recap solution
Finally, a whole class, which contains one API method
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 |
class ApiPresenter extends AnyBasePresenter { //output array private $outputJson = array( 'error' => true, 'message' => 'Unknow method', 'data' => array() ); //input array private $inputJson = array(); /** * method is called first from whole class, it is possible to replace with the constructor of class */ public function startup(){ //parent startup method parent::startup(); //parse input json $this->_setInputJson(); //check if exists json and contain key "method" if( $this->inputJson && $this->inputJson['method'] ){ //check if exists method if ( !String::startsWith('_', $this->inputJson['method'] ) && method_exists( $this, $this->inputJson['method'] )){ //and call method from class $methodname = $this->inputJson['method']; $this->$methodname(); }else{ //set error and push to output $this->_setError('unknown method'); } } } /** * function sets the error message and sends it to the output */ private function _setError($message){ $this->outputJson['message'] = 'Method: ' . (array_key_exists('method', $this->inputJson['method']) ? $this->inputJson['method'] : 'unknow') . ': ' . $message; $this->outputJson['error'] = true; //terminate app $this->terminate(); } /** * Sets the data at the output * @param array $user */ private function _setSuccess($user){ $this->outputJson['message'] = NULL; $this->outputJson['error'] = false; $this->outputJson['data'] = $user; $this->terminate(); } /** * function load json from std::input */ private function _setInputJson(){ $post = file_get_contents('php://input'); $inputJson = (array)json_decode($post); if( count($inputJson) ){ //convert input json vales to lower strings $this->inputJson = $this->_lowerArray($inputJson); } } /** * function convert array keys and values to lower strings * @param array * @return array */ private function _lowerArray( $array ){ foreach( $array as $id => $val ){ $array[String::lower($id)] = (is_array($val) ? $this->_lowerArray($val) : String::lower(trim($val))); } return $array; } /** * terminate app and push output */ public function terminate(){ Header('Content-Type: application/json'); echo json_encode($this->outputJson); parent::terminate(); } /** * default page render */ public function renderDefault() { $this->terminate(); } /******* api methods ******/ /** * Api method getContact */ private function getcontact(){ /** ... some code inside the function ... */ if( !array_key_exists( 'email', $this->inputJson ) ){ $this->_setError('Please enter email'); } $user = $this->getModelClass()->getUserByEmail($this->inputJson['email']); if( $user ){ $this->_setSuccess($user); }else{ $this->_setError('User not found'); } $this->terminate(); } /******* end api methods *******/ /** * Method return model class * @return ApiModel */ public function getModelClass(){ static $instance = NULL; if( !($instance instanceof ApiModel ) ){ $instance = new ApiModel; } return $instance; } } |
Seems to be working and fine. I would just think to use some standards like: http://www.jsonrpc.org/specification – what I see you’re very close.
The difference is in json {“method”: “methodName”, “params”: {“param1”: “value1”, …}} – small difference, but more complex is nice to have it in associative array probably… But my point of view…
For example, by using reflection you can easily test if the parameters are correct for the called method…