├── .gitignore ├── 404.html ├── README.md ├── index.php └── src ├── api_handler.php ├── app_autoloader.php ├── app_jwt.php ├── app_response.php └── db_classes ├── app_api_key.php └── data_access.php /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | composer.phar 3 | /.vscode/ 4 | /node_modules/ -------------------------------------------------------------------------------- /404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Page Not Found 9 | 10 | 11 | 12 | 13 | 14 |
15 |

Yeeeaaaah ... if you could just quit trying to access pages you shouldn't be accessing, that'd be greeeeat.

16 |
17 | 18 | 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PHP Restful API Starter Kit 2 | 3 | A starter kit for a handy PHP restful API. 4 | 5 | New articles in the future will be features built upon this foundation. 6 | 7 | This code accompanies my Medium article entitled, "How to Build a Simple Restful API in PHP" 8 | located at: https://medium.com/better-programming/how-to-build-a-simple-restful-api-in-php-c719f03cfa0a 9 | 10 | ## License ## 11 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 16 | -------------------------------------------------------------------------------- /index.php: -------------------------------------------------------------------------------- 1 | validateRequest($apiKey, $apiToken); 47 | } 48 | 49 | if ($res['response'] !== '200') { 50 | // if request is not valid, then raise a bad message. 51 | $returnArray = json_encode($res); 52 | echo($returnArray); 53 | } 54 | else { 55 | // if request is valid, execute command 56 | $res = $cApiHandler->execCommand($functionName, $functionParams); 57 | 58 | // encode and return 59 | $returnArray = json_encode($res, JSON_PRETTY_PRINT); 60 | echo($returnArray); 61 | 62 | } 63 | 64 | if (isset($cApiHandler)) {unset($cApiHandler);} 65 | 66 | } else { 67 | $returnArray = App_Response::getResponse('405'); 68 | echo(json_encode($returnArray)); 69 | } -------------------------------------------------------------------------------- /src/api_handler.php: -------------------------------------------------------------------------------- 1 | loadFunctionMap(); 18 | } 19 | 20 | //---------------------------------------------------------------------------------------------------------------------- 21 | public function execCommand($varFunctionName, $varFunctionParams) { 22 | 23 | // get the actual function name (if necessary) and the class it belongs to. 24 | $returnArray = $this->getCommand($varFunctionName); 25 | 26 | // if we don't get a function back, then raise the error 27 | if ($returnArray['success'] == FALSE) { 28 | return $returnArray; 29 | } 30 | 31 | $class = $returnArray['dataArray']['class']; 32 | $functionName = $returnArray['dataArray']['function_name']; 33 | 34 | // Execute User Profile Commands 35 | $cObjectClass = new $class(); 36 | $returnArray = $cObjectClass->$functionName($varFunctionParams); 37 | 38 | return $returnArray; 39 | 40 | } 41 | 42 | //---------------------------------------------------------------------------------------------------------------------- 43 | private function getCommand($varFunctionName) { 44 | 45 | // get the actual function name and the class it belongs to. 46 | if (isset($this->function_map[$varFunctionName])) { 47 | $dataArray['class'] = $this->function_map[$varFunctionName]['class']; 48 | $dataArray['function_name'] = $this->function_map[$varFunctionName]['function_name']; 49 | $returnArray = App_Response::getResponse('200'); 50 | $returnArray['dataArray'] = $dataArray; 51 | } else { 52 | $returnArray = App_Response::getResponse('405'); 53 | } 54 | 55 | return $returnArray; 56 | 57 | } 58 | 59 | //---------------------------------------------------------------------------------------------------- 60 | private function getToken($varParams) { 61 | 62 | // api key is required 63 | if (!isset($varParams['api_key']) || empty($varParams['api_key'])) { 64 | $returnArray = App_Response::getResponse('400'); 65 | return $returnArray; 66 | } 67 | 68 | $apiKey = $varParams['api_key']; 69 | 70 | // get the api key object 71 | $cApp_API_Key = new App_API_Key; 72 | $res = $cApp_API_Key->getRecordByAPIKey($apiKey); 73 | 74 | // if anything looks sketchy, bail. 75 | if ($res['response'] !== '200') { 76 | return $res; 77 | } 78 | 79 | $apiSecretKey = $res['dataArray'][0]['api_secret_key']; 80 | 81 | $payloadArray = array(); 82 | $payloadArray['apiKey'] = $apiKey; 83 | $token = JWT::encode($payloadArray, $apiSecretKey); 84 | 85 | $returnArray = App_Response::getResponse('200'); 86 | $returnArray['dataArray'] = array("token" => $token); 87 | 88 | return $returnArray; 89 | } 90 | 91 | //---------------------------------------------------------------------------------------------------------------------- 92 | private function loadFunctionMap() { 93 | 94 | // load up all public facing functions 95 | $this->function_map = [ 96 | 'getToken' => ['class' => 'API_Handler', 'function_name' => 'getToken'], 97 | ]; 98 | 99 | } 100 | 101 | //-------------------------------------------------------------------------------------------------------------------- 102 | public function validateRequest($varAPIKey = NULL, $varToken = NULL) { 103 | 104 | // this function requires and API key and token parameters 105 | if (!$varAPIKey || !$varToken) { 106 | $returnArray = App_Response::getResponse('403'); 107 | $returnArray['responseDescription'] .= " Missing API key or token."; 108 | return $returnArray; 109 | } 110 | 111 | // get the api key object 112 | $cApp_API_Key = new App_API_Key; 113 | $res = $cApp_API_Key->getRecordByAPIKey($varAPIKey); 114 | unset($cApp_API_Key); 115 | 116 | // if anything looks sketchy, bail. 117 | if ($res['response'] !== '200') { 118 | return $res; 119 | } 120 | 121 | // get the client API secret key. 122 | $apiSecretKey = $res['dataArray'][0]['api_secret_key']; 123 | 124 | // decode the token 125 | try { 126 | $payload = JWT::decode($varToken, $apiSecretKey, array('HS256')); 127 | } 128 | catch(Exception $e) { 129 | $returnArray = App_Response::getResponse('403'); 130 | $returnArray['responseDescription'] .= " ".$e->getMessage(); 131 | return $returnArray; 132 | } 133 | 134 | // get items out of the payload 135 | $apiKey = $payload->apiKey; 136 | if (isset($payload->exp)) {$expire = $payload->exp;} else {$expire = 0;} 137 | 138 | // if api keys don't match, kick'em out 139 | if ($apiKey !== $varAPIKey) { 140 | $returnArray = App_Response::getResponse('403'); 141 | $returnArray['responseDescription'] .= " Invalid API Key."; 142 | return $returnArray; 143 | } 144 | 145 | // if token is expired, kick'em out 146 | $currentTime = time(); 147 | if (($expire !== 0) && ($expire < $currentTime)) { 148 | $returnArray = App_Response::getResponse('403'); 149 | $returnArray['responseDescription'] .= " Token has expired."; 150 | return $returnArray; 151 | } 152 | 153 | $returnArray = App_Response::getResponse('200'); 154 | return $returnArray; 155 | 156 | } 157 | 158 | } // end of class -------------------------------------------------------------------------------- /src/app_autoloader.php: -------------------------------------------------------------------------------- 1 | './src/api_handler.php', 17 | 'App_Response' => './src/app_response.php', 18 | 'JWT' => './src/app_jwt.php', 19 | 20 | // database classes 21 | 'Data_Access' => './src/db_classes/data_access.php', 22 | 'App_API_Key' => './src/db_classes/app_api_key.php' 23 | 24 | ]; 25 | 26 | //---------------------------------------------------------------------------------------------------------------------- 27 | spl_autoload_register(function ($class) use ($mapping) { 28 | if (isset($mapping[$class])) { 29 | require_once $mapping[$class]; 30 | } 31 | }, true); -------------------------------------------------------------------------------- /src/app_jwt.php: -------------------------------------------------------------------------------- 1 | 9 | * @author Anant Narayanan 10 | * @license http://opensource.org/licenses/BSD-3-Clause 3-clause BSD 11 | * @link https://github.com/firebase/php-jwt 12 | */ 13 | 14 | if ((!defined('CONST_INCLUDE_KEY')) || (CONST_INCLUDE_KEY !== 'd4e2ad09-b1c3-4d70-9a9a-0e6149302486')) { 15 | // If someone tries to browse directly to this PHP file, send 404 and exit. It can only included 16 | // as part of our API. 17 | header("Location: /404.html", TRUE, 404); 18 | echo file_get_contents($_SERVER['DOCUMENT_ROOT'] . '/404.html'); 19 | die; 20 | } 21 | 22 | class JWT { 23 | /** 24 | * Decodes a JWT string into a PHP object. 25 | * 26 | * @param string $jwt The JWT 27 | * @param string|null $key The secret key 28 | * @param bool $verify Don't skip verification process 29 | * 30 | * @return object The JWT's payload as a PHP object 31 | * @throws UnexpectedValueException Provided JWT was invalid 32 | * @throws DomainException Algorithm was not provided 33 | * 34 | * @uses jsonDecode 35 | * @uses urlsafeB64Decode 36 | */ 37 | public static function decode($jwt, $key = null, $verify = true) 38 | { 39 | $tks = explode('.', $jwt); 40 | if (count($tks) != 3) { 41 | throw new UnexpectedValueException('Wrong number of segments'); 42 | } 43 | list($headb64, $bodyb64, $cryptob64) = $tks; 44 | if (null === ($header = JWT::jsonDecode(JWT::urlsafeB64Decode($headb64)))) { 45 | throw new UnexpectedValueException('Invalid segment encoding'); 46 | } 47 | if (null === $payload = JWT::jsonDecode(JWT::urlsafeB64Decode($bodyb64))) { 48 | throw new UnexpectedValueException('Invalid segment encoding'); 49 | } 50 | $sig = JWT::urlsafeB64Decode($cryptob64); 51 | if ($verify) { 52 | if (empty($header->alg)) { 53 | throw new DomainException('Empty algorithm'); 54 | } 55 | if ($sig != JWT::sign("$headb64.$bodyb64", $key, $header->alg)) { 56 | throw new UnexpectedValueException('Signature verification failed'); 57 | } 58 | } 59 | return $payload; 60 | } 61 | /** 62 | * Converts and signs a PHP object or array into a JWT string. 63 | * 64 | * @param object|array $payload PHP object or array 65 | * @param string $key The secret key 66 | * @param string $algo The signing algorithm. Supported 67 | * algorithms are 'HS256', 'HS384' and 'HS512' 68 | * 69 | * @return string A signed JWT 70 | * @uses jsonEncode 71 | * @uses urlsafeB64Encode 72 | */ 73 | public static function encode($payload, $key, $algo = 'HS256') 74 | { 75 | $header = array('typ' => 'JWT', 'alg' => $algo); 76 | $segments = array(); 77 | $segments[] = JWT::urlsafeB64Encode(JWT::jsonEncode($header)); 78 | $segments[] = JWT::urlsafeB64Encode(JWT::jsonEncode($payload)); 79 | $signing_input = implode('.', $segments); 80 | $signature = JWT::sign($signing_input, $key, $algo); 81 | $segments[] = JWT::urlsafeB64Encode($signature); 82 | return implode('.', $segments); 83 | } 84 | /** 85 | * Sign a string with a given key and algorithm. 86 | * 87 | * @param string $msg The message to sign 88 | * @param string $key The secret key 89 | * @param string $method The signing algorithm. Supported 90 | * algorithms are 'HS256', 'HS384' and 'HS512' 91 | * 92 | * @return string An encrypted message 93 | * @throws DomainException Unsupported algorithm was specified 94 | */ 95 | public static function sign($msg, $key, $method = 'HS256') 96 | { 97 | $methods = array( 98 | 'HS256' => 'sha256', 99 | 'HS384' => 'sha384', 100 | 'HS512' => 'sha512', 101 | ); 102 | if (empty($methods[$method])) { 103 | throw new DomainException('Algorithm not supported'); 104 | } 105 | return hash_hmac($methods[$method], $msg, $key, true); 106 | } 107 | /** 108 | * Decode a JSON string into a PHP object. 109 | * 110 | * @param string $input JSON string 111 | * 112 | * @return object Object representation of JSON string 113 | * @throws DomainException Provided string was invalid JSON 114 | */ 115 | public static function jsonDecode($input) 116 | { 117 | $obj = json_decode($input); 118 | if (function_exists('json_last_error') && $errno = json_last_error()) { 119 | JWT::_handleJsonError($errno); 120 | } else if ($obj === null && $input !== 'null') { 121 | throw new DomainException('Null result with non-null input'); 122 | } 123 | return $obj; 124 | } 125 | /** 126 | * Encode a PHP object into a JSON string. 127 | * 128 | * @param object|array $input A PHP object or array 129 | * 130 | * @return string JSON representation of the PHP object or array 131 | * @throws DomainException Provided object could not be encoded to valid JSON 132 | */ 133 | public static function jsonEncode($input) 134 | { 135 | $json = json_encode($input); 136 | if (function_exists('json_last_error') && $errno = json_last_error()) { 137 | JWT::_handleJsonError($errno); 138 | } else if ($json === 'null' && $input !== null) { 139 | throw new DomainException('Null result with non-null input'); 140 | } 141 | return $json; 142 | } 143 | /** 144 | * Decode a string with URL-safe Base64. 145 | * 146 | * @param string $input A Base64 encoded string 147 | * 148 | * @return string A decoded string 149 | */ 150 | public static function urlsafeB64Decode($input) 151 | { 152 | $remainder = strlen($input) % 4; 153 | if ($remainder) { 154 | $padlen = 4 - $remainder; 155 | $input .= str_repeat('=', $padlen); 156 | } 157 | return base64_decode(strtr($input, '-_', '+/')); 158 | } 159 | /** 160 | * Encode a string with URL-safe Base64. 161 | * 162 | * @param string $input The string you want encoded 163 | * 164 | * @return string The base64 encode of what you passed in 165 | */ 166 | public static function urlsafeB64Encode($input) 167 | { 168 | return str_replace('=', '', strtr(base64_encode($input), '+/', '-_')); 169 | } 170 | /** 171 | * Helper method to create a JSON error. 172 | * 173 | * @param int $errno An error number from json_last_error() 174 | * 175 | * @return void 176 | */ 177 | private static function _handleJsonError($errno) 178 | { 179 | $messages = array( 180 | JSON_ERROR_DEPTH => 'Maximum stack depth exceeded', 181 | JSON_ERROR_CTRL_CHAR => 'Unexpected control character found', 182 | JSON_ERROR_SYNTAX => 'Syntax error, malformed JSON' 183 | ); 184 | throw new DomainException( 185 | isset($messages[$errno]) 186 | ? $messages[$errno] 187 | : 'Unknown JSON error: ' . $errno 188 | ); 189 | } 190 | } -------------------------------------------------------------------------------- /src/app_response.php: -------------------------------------------------------------------------------- 1 | $success, 'response' => $response, 'responseDescription' => $responseDescription); 82 | return $returnArray; 83 | 84 | } 85 | 86 | } // end class -------------------------------------------------------------------------------- /src/db_classes/app_api_key.php: -------------------------------------------------------------------------------- 1 | dbConnect(); 27 | 28 | // if we get anything but a good response ... 29 | if ($res['response'] != '200') { 30 | echo "Houston? We have a problem."; 31 | die; 32 | } 33 | } 34 | 35 | //---------------------------------------------------------------------------------------------------- 36 | public function getRecordByAPIKey($varAPIKey = NULL) { 37 | 38 | // job category is required 39 | if (!isset($varAPIKey) || $varAPIKey === '') { 40 | $responseArray = App_Response::getResponse('403'); 41 | return $responseArray; 42 | } 43 | 44 | // build the query 45 | $query = "SELECT * FROM " . CONST_DB_SCHEMA . "." . $this->object_view_name; 46 | $query .= " WHERE (api_key = '" . $varAPIKey . "') AND (status_flag = 1);"; 47 | 48 | $res = $this->getResultSetArray($query); 49 | 50 | // if nothing comes back, then return a failure 51 | if ($res['response'] !== '200') { 52 | $responseArray = App_Response::getResponse('403'); 53 | } else { 54 | $responseArray = $res; 55 | } 56 | 57 | // send back what we got 58 | return $responseArray; 59 | 60 | } 61 | 62 | } // end class -------------------------------------------------------------------------------- /src/db_classes/data_access.php: -------------------------------------------------------------------------------- 1 | connect_errno) { 23 | // if an error occurred, raise it. 24 | $responseArray = App_Response::getResponse('500'); 25 | $responseArray['message'] = 'MySQL error: ' . $GLOBALS['dbConnection']->connect_errno . ' ' . $GLOBALS['dbConnection']->connect_error; 26 | 27 | } else { 28 | // success 29 | $responseArray = App_Response::getResponse('200'); 30 | $responseArray['message'] = 'Database connection successful.'; 31 | } 32 | 33 | return $responseArray; 34 | 35 | } 36 | 37 | //-------------------------------------------------------------------------------------------------------------------- 38 | protected function getResultSetArray($varQuery) { 39 | 40 | // attempt the query 41 | $rsData = $GLOBALS['dbConnection']->query($varQuery); 42 | 43 | if (isset($GLOBALS['dbConnection']->errno) && ($GLOBALS['dbConnection']->errno != 0)) { 44 | // if an error occurred, raise it. 45 | $responseArray = App_Response::getResponse('500'); 46 | $responseArray['message'] = 'Internal server error. MySQL error: ' . $GLOBALS['dbConnection']->errno . ' ' . $GLOBALS['dbConnection']->error; 47 | } else { 48 | // success 49 | $rowCount = $rsData->num_rows; 50 | 51 | if ($rowCount != 0) { 52 | // move result set to an associative array 53 | $rsArray = $rsData->fetch_all(MYSQLI_ASSOC); 54 | 55 | // add array to return 56 | $responseArray = App_Response::getResponse('200'); 57 | $responseArray['dataArray'] = $rsArray; 58 | 59 | } else { 60 | // no data returned 61 | $responseArray = App_Response::getResponse('204'); 62 | $responseArray['message'] = 'Query did not return any results.'; 63 | } 64 | 65 | } 66 | 67 | return $responseArray; 68 | 69 | } 70 | 71 | } --------------------------------------------------------------------------------