それがすべて始まった方法
プロジェクトの開発中に、iOSおよびAndroidプラットフォーム上のアプリケーションと、すべての情報が保存されているサイト(実際にはmysqlデータベース、写真、ファイル、その他のコンテンツ)とのクライアントサーバーインタラクションを整理する必要に直面しました。
解決する必要があるタスクは非常に簡単です。
ユーザー登録/承認;
特定のデータ(商品のリストなど)の送受信。
そして、サーバー側とやり取りするためのAPIを作成したかったのです。その大部分は実用的な関心事です。
入力データ
私の処分で:
サーバー-Apache、PHP 5.0、MySQL 5.0
クライアント-Android、iOSデバイス、任意のブラウザー
サーバーのリクエストとレスポンスにJSONデータ形式を使用することにしました-PHPとAndroidのシンプルさとネイティブサポートのため。 ここでIOSが動揺しました-ネイティブJSONサポートがありません(ここではサードパーティの開発を使用する必要がありました)。
また、GETリクエストとPOSTリクエストの両方でリクエストを送信できることも決定されました(PHPの$ _REQUESTがここで役立ちました)。 このソリューションにより、利用可能なブラウザでGETリクエストを介してAPIをテストできました。
クエリの外観を次のようにすることが決定されました。
http:// [サーバーアドレス] / [apiフォルダーへのパス] /?[api_name]。[method_name] = [JSON形式{"Hello": "Hello world"}]
apiフォルダーへのパスは、リクエストを行う必要があるディレクトリです。そのルートはindex.phpファイルです-関数の呼び出しとエラーの処理を担当します
名前はapiです。便宜上、グループのAPI(ユーザー、データベース、コンテンツなど)を分離することにしました。 この場合、各APIには名前が付けられています
メソッド名-指定されたAPIで呼び出されるメソッドの名前
JSON-メソッドパラメーターのJSONオブジェクトの文字列表現
スケルトンAPI
サーバー側のスケルトンAPIは、いくつかの基本クラスで構成されています。
index.php-すべてのAPI呼び出しのApacheアカウントのディレクトリインデックスファイル。パラメーターを解析し、APIメソッドを呼び出します。
MySQLiWorker-MySQLiを介してMySQLデータベースを操作するための単一クラス
apiBaseCalss.php-システム内のすべてのAPIの親クラス-正しく機能するには、このクラスから各APIを継承する必要があります
apiEngine.php-システムのメインクラス-渡されたパラメーターを解析し(index.phpでの予備解析後)、目的のapiクラスを(require_onceメソッド経由で)接続し、その中の目的のメソッドを呼び出し、結果をJSON形式で返します
apiConstants.php-API呼び出しおよびエラーを渡すための定数を持つクラス
apitest.php-生産バージョンに含まれる前に新しいメソッドをテストするためのテストAPI
メカニズム全体は次のとおりです。
サーバーにリクエストを送信します-たとえば、 www.example.com / api /?apitest.helloWorld = {}
サーバー側では、index.phpファイル-渡されたパラメーターを解析します。 Index.phpは、渡されたパラメーター$ _REQUESTのリストから常に最初の要素のみを取得します。これは、 www.example.com / api /?apitest.helloWorld = {}&apitest.helloWorld2の形式の設計が、apitestのhelloWorldメソッドのみを呼び出すことを意味します。 helloWorld2メソッド呼び出しは失敗します
それぞれの詳細
テキストの下に多くのスペースをとらないように、十分なファイルを文書化しようとしました。 ただし、コメントのないファイルでは、説明を行います。
Index.php
前に言ったように、これはApacheの入力インデックスファイルです。つまり、 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); } ?>
まず、コンテンツタイプをtext / html(メソッド自体で変更できます)とエンコードをUTF-8に設定します。
次に、彼らが私たちに何かを求めていることを確認します。 そうでない場合、エラー付きでJSONを出力します。
リクエストパラメータがある場合は、APIエンジンファイル-apiEngine.phpを含め、渡されたパラメータでエンジンクラスを作成し、apiメソッドを呼び出します。
呼び出しを1つだけ処理することに決めたため、ループを終了します。
apiEngine.php
2番目に重要なのはapiEngineクラスです。これは、apiとそのメソッドを呼び出すためのエンジンです。
<?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
このクラスは、定数の保存にのみ使用されます。
<?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
データベースを操作するための単一クラス。 言い換えれば、これは普通の孤独です-ネットワーク上にそのような例がたくさんあります。
<?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
さて、ここでシステムの最も重要なクラスの1つ、つまりシステム内のすべてのAPIの基本クラスに進みます。
<?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; } } ?>
ご覧のとおり、このクラスには次のようないくつかの「ユーティリティ」メソッドが含まれています。
現在のAPIがデータベースで動作する場合、データベースに接続するコンストラクター。
デストラクタ-リソースのリリースを監視-データベースとの確立された接続を切断
createDefaultJson-メソッド応答のデフォルトJSONを作成します
fillJSON-要求が1つのレコードのみを返すと想定される場合、このメソッドは、データベースからの応答の最初の行のデータで応答のJSONを埋めます
独自のAPIを作成する
これがこのAPIのバックボーン全体です。 apitestという最初のAPIを作成する例を使用して、これをすべて使用する方法を見てみましょう。 そして、その中にいくつかの簡単な関数を書きます:
パラメータなしのもの
彼女はパラメータとそれらを私たちに返しますので、彼女がそれらを読んだことがわかります
バイナリデータを返すもの
そして、次のコンテンツのapitest.phpクラスを作成します
<?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"); } } ?>
テスト方法の利便性のために、テストの迅速なリクエストを行うことができるアドレスを追加します。
そして、3つの方法があります
helloAPI
function helloAPI() { $retJSON = $this->createDefaultJson(); $retJSON->withoutParams = 'It\'s method called without parameters'; return $retJSON; }
これは、パラメーターを使用しない単純な方法です。 彼のGETアドレスはwww.example.com/api/?apitest.helloAPI= {}です
実行の結果はそのようなページになります(ブラウザ内)
helloAPIWithParams
このメソッドはパラメーターを受け取ります。 TestParamOneは必須であり、チェックを行います。 渡されない場合、JSONがエラーで発行されます
function helloAPIWithParams($apiMethodParams) { $retJSON = $this->createDefaultJson(); if (isset($apiMethodParams->TestParamOne)){ // , $retJSON->retParameter=$apiMethodParams->TestParamOne; }else{ $retJSON->errorno= APIConstants::$ERROR_PARAMS; } return $retJSON; }
実行結果
helloAPIResponseBinary
そして、最後のhelloAPIResponseBinaryメソッド-バイナリデータを返します-存在しないページに関するハブの写真(例として)
function helloAPIResponseBinary($apiMethodParams){ header('Content-type: image/jpeg'); echo file_get_contents("http://habrahabr.ru/i/error-404-monster.jpg"); }
ご覧のように、グラフィックコンテンツを表示するためのヘッダーの代替があります。
結果は次のようになります
やることがあります
さらなる開発のために、呼び出し要求に対する権利の差別化を導入するためにユーザーを承認する必要があります-いくつかは自由のままにしておくべきであり、いくつかはユーザーの承認のみが必要です。
参照資料
テストのために、githubにすべてのファイルを投稿しました-simpleAPI