├── .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 | }
--------------------------------------------------------------------------------