📜 ⬆️ ⬇️

We write our API for the site using Apache, PHP and MySQL

How it all began



Developing a project, I was faced with the need to organize client-server interaction of applications on iOS and Android platforms with my website on which all information was stored - the actual database on mysql, pictures, files and other content.
The tasks that had to be solved are quite simple:
user registration / authorization;
sending / receiving certain data (for example, a list of goods).

And it was here that I wanted to write my API for interacting with the server side - most of it for practical interest.
')

Input data



I had at my disposal:
Server - Apache, PHP 5.0, MySQL 5.0
Client - Android, iOS devices, any browser

I decided that I would use the JSON data format for requests to the server and responses from it — for its simplicity and native support in PHP and Android. Here I was upset by iOS - it does not have native support for JSON (I had to use third-party development here).


It was also decided that requests can be sent both through GET and POST requests ($ _REQUEST in PHP helped here). This solution allowed testing API via GET requests in any available browser.

The appearance of the requests was decided to do this:
http: // [server address] / [api folder path] /? [api_name]. [method_name] = [JSON of the form {"Hello": "Hello world"}]]

The path to the api folder is the directory to which requests should be made, in the root of which is the index.php file - it is responsible for calling functions and handling errors
The name api - for convenience, I decided to separate the group API - user, database, content, and so on. In this case, each api got its name.
Method name - the name of the method to be called in the specified api
JSON - a string representation of a JSON object for method parameters

Skeleton API



The server side skeleton API consists of several base classes:
index.php - index file of the Apache directory; all API calls fall on it, it performs parameter parsing and API method calls
MySQLiWorker - a single class for working with the MySQL database through MySQLi
apiBaseCalss.php - parent class for all APIs in the system - each API must be inherited from this class for proper operation
apiEngine.php - the main class of the system - parses the passed parameters (after their preliminary parsing in index.php) connects the necessary class api (via the require_once method), calls the required method in it and returns the result in JSON format
apiConstants.php - class with constants for api calls and error transfer
apitest.php - test api for testing new methods before including them in the production version

The whole mechanism is as follows:
We make a request to the server - for example www.example.com/api/?apitest.helloWorld= {}
On the server side, the file index.php - parses the passed parameters. Index.php always takes only the first element from the list of passed parameters $ _REQUEST - this means that a construct like www.example.com/api/?apitest.helloWorld= {} & apitest.helloWorld2 will only call the helloWorld method in apitest. Calling the helloWorld2 method will not occur.

Now more about each



I tried to document the files enough not to take up much space under the text. However, in those files where there are no comments, I will give a description.

Index.php


As I said before, this is an input index file for Apache, which means it will accept all calls of the type www.example.com/api .

<?php header('Content-type: text/html; charset=UTF-8'); if (count($_REQUEST)>0){ require_once 'apiEngine.php'; foreach ($_REQUEST as $apiFunctionName => $apiFunctionParams) { $APIEngine=new APIEngine($apiFunctionName,$apiFunctionParams); echo $APIEngine->callApiFunction(); break; } }else{ $jsonError->error='No function called'; echo json_encode($jsonError); } ?> 


First of all, we set the content type - text / html (then you can change it in the methods themselves) and the encoding - UTF-8.
Further we check that something is requested from us. If not, then output JSON with an error.
If there are request parameters, we connect the API engine file - apiEngine.php and create the engine class with the passed parameters and make the call to the api method.
We leave the loop as we decided that we will handle only one call.

apiEngine.php


The second most important class is apiEngine - it is an engine for calling api and their methods.
 <?php require_once('MySQLiWorker.php'); require_once ('apiConstants.php'); class APIEngine { private $apiFunctionName; private $apiFunctionParams; //    API   API     static function getApiEngineByName($apiName) { require_once 'apiBaseClass.php'; require_once $apiName . '.php'; $apiClass = new $apiName(); return $apiClass; } // //$apiFunctionName -  API      apitest_helloWorld //$apiFunctionParams - JSON      function __construct($apiFunctionName, $apiFunctionParams) { $this->apiFunctionParams = stripcslashes($apiFunctionParams); //      [0] -  API, [1] -    API $this->apiFunctionName = explode('_', $apiFunctionName); } // JSON  function createDefaultJson() { $retObject = json_decode('{}'); $response = APIConstants::$RESPONSE; $retObject->$response = json_decode('{}'); return $retObject; } //       function callApiFunction() { $resultFunctionCall = $this->createDefaultJson();// JSON  $apiName = strtolower($this->apiFunctionName[0]);// API     if (file_exists($apiName . '.php')) { $apiClass = APIEngine::getApiEngineByName($apiName);//  API $apiReflection = new ReflectionClass($apiName);//       try { $functionName = $this->apiFunctionName[1];//    $apiReflection->getMethod($functionName);//   $response = APIConstants::$RESPONSE; $jsonParams = json_decode($this->apiFunctionParams);//    JSON  if ($jsonParams) { if (isset($jsonParams->responseBinary)){//    JSON,      zip, png  .  return $apiClass->$functionName($jsonParams);//   API }else{ $resultFunctionCall->$response = $apiClass->$functionName($jsonParams);//   API   JSON  } } else { //   JSON   $resultFunctionCall->errno = APIConstants::$ERROR_ENGINE_PARAMS; $resultFunctionCall->error = 'Error given params'; } } catch (Exception $ex) { //  $resultFunctionCall->error = $ex->getMessage(); } } else { //  API   $resultFunctionCall->errno = APIConstants::$ERROR_ENGINE_PARAMS; $resultFunctionCall->error = 'File not found'; $resultFunctionCall->REQUEST = $_REQUEST; } return json_encode($resultFunctionCall); } } ?> 


apiConstants.php


This class is used only for storing constants.

 <?php class APIConstants { //  -   JSON  public static $RESULT_CODE="resultCode"; // -      JSON   apiEngine public static $RESPONSE="response"; //  public static $ERROR_NO_ERRORS = 0; //    public static $ERROR_PARAMS = 1; //   SQL    public static $ERROR_STMP = 2; //    public static $ERROR_RECORD_NOT_FOUND = 3; //     .         public static $ERROR_ENGINE_PARAMS = 100; // zip  public static $ERROR_ENSO_ZIP_ARCHIVE = 1001; } ?> 


MySQLiWorker.php


Single class to work with the base. In other matters, this is an ordinary loner - there are many such examples on the net.

 <?php class MySQLiWorker { protected static $instance; // object instance public $dbName; public $dbHost; public $dbUser; public $dbPassword; public $connectLink = null; //      new MySQLiWorker private function __construct() { /* ... */ } //      private function __clone() { /* ... */ } //     unserialize private function __wakeup() { /* ... */ } //   public static function getInstance($dbName, $dbHost, $dbUser, $dbPassword) { if (is_null(self::$instance)) { self::$instance = new MySQLiWorker(); self::$instance->dbName = $dbName; self::$instance->dbHost = $dbHost; self::$instance->dbUser = $dbUser; self::$instance->dbPassword = $dbPassword; self::$instance->openConnection(); } return self::$instance; } //            ->bind function prepareParams($params) { $retSTMTString = ''; foreach ($params as $value) { if (is_int($value) || is_double($value)) { $retSTMTString.='d'; } if (is_string($value)) { $retSTMTString.='s'; } } return $retSTMTString; } //   public function openConnection() { if (is_null($this->connectLink)) { $this->connectLink = new mysqli($this->dbHost, $this->dbUser, $this->dbPassword, $this->dbName); $this->connectLink->query("SET NAMES utf8"); if (mysqli_connect_errno()) { printf(" : %s\n", mysqli_connect_error()); $this->connectLink = null; } else { mysqli_report(MYSQLI_REPORT_ERROR); } } return $this->connectLink; } //    public function closeConnection() { if (!is_null($this->connectLink)) { $this->connectLink->close(); } } //     public function stmt_bind_assoc(&$stmt, &$out) { $data = mysqli_stmt_result_metadata($stmt); $fields = array(); $out = array(); $fields[0] = $stmt; $count = 1; $currentTable = ''; while ($field = mysqli_fetch_field($data)) { if (strlen($currentTable) == 0) { $currentTable = $field->table; } $fields[$count] = &$out[$field->name]; $count++; } call_user_func_array('mysqli_stmt_bind_result', $fields); } } ?> 


apiBaseClass.php


Well, here we come to one of the most important classes of the system - the base class for all APIs in the system.

 <?php class apiBaseClass { public $mySQLWorker=null;//     //    function __construct($dbName=null,$dbHost=null,$dbUser=null,$dbPassword=null) { if (isset($dbName)){//          $this->mySQLWorker = MySQLiWorker::getInstance($dbName,$dbHost,$dbUser,$dbPassword); } } function __destruct() { if (isset($this->mySQLWorker)){ //     , $this->mySQLWorker->closeConnection(); //         } } //  JSON   function createDefaultJson() { $retObject = json_decode('{}'); return $retObject; } // JSON     MySQLiWorker function fillJSON(&$jsonObject, &$stmt, &$mySQLWorker) { $row = array(); $mySQLWorker->stmt_bind_assoc($stmt, $row); while ($stmt->fetch()) { foreach ($row as $key => $value) { $key = strtolower($key); $jsonObject->$key = $value; } break; } return $jsonObject; } } ?> 


As you can see, this class contains several “utility” methods, such as:
the constructor in which the connection to the database is made, if the current API is going to work with the database;
destructor - monitors the release of resources - disconnection of the established connection with the database
createDefaultJson - creates default JSON for method response
fillJSON - if it is assumed that the request returns only one record, then this method will fill in the JSON for the response with data from the first line of the response from the database

Create your own API



That's actually the backbone of this API. Now let's look at how to use all this on the example of creating the first API called apitest. And we will write in it a couple of simple functions:
one without parameters
one with the parameters and they will return it to us so that it can be seen that she has read them
one that returns us binary data

And so create a class apitest.php with the following content

 <?php class apitest extends apiBaseClass { //http://www.example.com/api/?apitest.helloAPI={} function helloAPI() { $retJSON = $this->createDefaultJson(); $retJSON->withoutParams = 'It\'s method called without parameters'; return $retJSON; } //http://www.example.com/api/?apitest.helloAPIWithParams={"TestParamOne":"Text of first parameter"} function helloAPIWithParams($apiMethodParams) { $retJSON = $this->createDefaultJson(); if (isset($apiMethodParams->TestParamOne)){ //   ,    $retJSON->retParameter=$apiMethodParams->TestParamOne; }else{ $retJSON->errorno= APIConstants::$ERROR_PARAMS; } return $retJSON; } //http://www.example.com/api/?apitest.helloAPIResponseBinary={"responseBinary":1} function helloAPIResponseBinary($apiMethodParams){ header('Content-type: image/png'); echo file_get_contents("http://habrahabr.ru/i/error-404-monster.jpg"); } } ?> 


For the convenience of testing methods, I append to them the address at which I can make a quick request for testing.

And so we have three methods
helloAPI


 function helloAPI() { $retJSON = $this->createDefaultJson(); $retJSON->withoutParams = 'It\'s method called without parameters'; return $retJSON; } 


This is a simple method without parameters. His address for the GET call is www.example.com/api/?apitest.helloAPI= {}

The result will be the following page (in the browser)

helloAPI

helloAPIWithParams

This method takes in parameters. TestParamOne is mandatory, for it we will do the check. It will not be passed to it, then JSON will be returned with an error

 function helloAPIWithParams($apiMethodParams) { $retJSON = $this->createDefaultJson(); if (isset($apiMethodParams->TestParamOne)){ //   ,    $retJSON->retParameter=$apiMethodParams->TestParamOne; }else{ $retJSON->errorno= APIConstants::$ERROR_PARAMS; } return $retJSON; } 

Result of performance

helloAPIWithParams

helloAPIResponseBinary


And the last method helloAPIResponseBinary - returns binary data - a picture of a habr about a nonexistent page (as an example)
 function helloAPIResponseBinary($apiMethodParams){ header('Content-type: image/jpeg'); echo file_get_contents("http://habrahabr.ru/i/error-404-monster.jpg"); } 

As you can see, there is a header substitution for displaying graphic content.
The result will be

helloAPIResponseBinary

There is work to do



For further development, it is necessary to make the authorization of users in order to introduce a distinction between the rights to call requests - some left free, and some only when authorizing a user.

Links



For testing laid out all files on github - simpleAPI

Source: https://habr.com/ru/post/143317/


All Articles