Source Code: SmartFactory/RequestHandler.php

  1: <?php
  2: /**
  3:  * This file contains the implementation of the interface IRequestHandler
  4:  * in the class JsonRequestHandler for handling the JSON API requests.
  5:  *
  6:  * @package System
  7:  *
  8:  * @author Oleg Schildt
  9:  */
 10: 
 11: namespace SmartFactory;
 12: 
 13: use \SmartFactory\Interfaces\IErrorHandler;
 14: use \SmartFactory\Interfaces\IRequestHandler;
 15: 
 16: /**
 17:  * Abstract class for handling the JSON and XML requests.
 18:  *
 19:  * @author Oleg Schildt
 20:  */
 21: abstract class RequestHandler implements IRequestHandler
 22: {
 23:     /**
 24:      * Internal variable for storing the API action.
 25:      *
 26:      * @var string
 27:      *
 28:      * @author Oleg Schildt
 29:      */
 30:     protected string $action = "";
 31: 
 32:     /**
 33:      * Internal variable for storing the API parameter string.
 34:      *
 35:      * @var string
 36:      *
 37:      * @author Oleg Schildt
 38:      */
 39:     protected string $param_string = "";
 40: 
 41:     /**
 42:      * Internal variable for storing the response headers.
 43:      *
 44:      * @var array
 45:      *
 46:      * @author Oleg Schildt
 47:      */
 48:     protected array $response_headers = [];
 49: 
 50:     /**
 51:      * This is an auxiliary function for getting request headers and parsing the API url.
 52:      *
 53:      * It put the retrieved data into the corresponding properties.
 54:      *
 55:      * @return void
 56:      *
 57:      * @author Oleg Schildt
 58:      */
 59:     protected function processInputData(): void
 60:     {
 61:         if (empty($_SERVER['REQUEST_URI'])) {
 62:             return;
 63:         }
 64: 
 65:         $api_base = str_replace(basename($_SERVER['SCRIPT_NAME']), "", $_SERVER['SCRIPT_NAME']);
 66:         $api_request = str_replace($api_base, "", $_SERVER['REQUEST_URI']);
 67: 
 68:         if (preg_match("~^([^/.?&]+)([/?]?.*)~", $api_request, $matches)) {
 69:             $this->action = $matches[1];
 70:             $this->param_string = urldecode($matches[2]);
 71:         } else {
 72:             $this->action = $_REQUEST["action"] ?? "";
 73:             $this->param_string = urldecode(str_replace(basename($_SERVER['SCRIPT_NAME']), "", $api_request));
 74:         }
 75:     } // processInputData
 76: 
 77:     /**
 78:      * This is an auxiliary function for sending the response.
 79:      *
 80:      * @return void
 81:      *
 82:      * @author Oleg Schildt
 83:      */
 84:     abstract protected function sendResponse(): void;
 85: 
 86:     /**
 87:      * Adds a response header to the response.
 88:      *
 89:      * @param string $header
 90:      * The name of the response header.
 91:      *
 92:      * @param string $value
 93:      * The value of the response header.
 94:      *
 95:      * @return void
 96:      *
 97:      * @author Oleg Schildt
 98:      */
 99:     protected function addResponseHeader(string $header, string $value): void
100:     {
101:         $this->response_headers[$header] = $value;
102:     } // addResponseHeader
103: 
104:     /**
105:      * This is an auxiliary function for parsing the incoming data.
106:      *
107:      * @return void
108:      *
109:      * @throws SmartException
110:      * It might throw an exception if the content type or data is invalid.
111:      *
112:      * @author Oleg Schildt
113:      */
114:     abstract protected function parseInput(): void;
115: 
116:     /**
117:      * This is an auxiliary function for adding error messages, warnings, info messages to the response which might have been collected
118:      * during handling of the request.
119:      *
120:      * @return void
121:      *
122:      * @throws SmartException
123:      * It might throw an exception if the content type or data is invalid.
124:      *
125:      * @author Oleg Schildt
126:      */
127:     abstract protected function addMessagesToResponse(): void;
128: 
129:     /**
130:      * This function is called before the processing of the request. It should be overridden if you want
131:      * to perform some standard action before every API request of your library.
132:      * The usage example are - validation of the access tokens, checking maintenance mode and reporting it to
133:      * the user if it is active.
134:      *
135:      * @return void
136:      *
137:      * @author Oleg Schildt
138:      */
139:     protected function preprocessRequest(): void
140:     {
141:     } // preprocessRequest
142: 
143:     /**
144:      * This function is called after the processing of the request. It should be overridden if you want
145:      * to perform some standard action after every API request of your library.
146:      *
147:      * @return void
148:      *
149:      * @author Oleg Schildt
150:      */
151:     protected function postprocessRequest(): void
152:     {
153:     } // postprocessRequest
154: 
155:     /**
156:      * This is the main function that should be called to handle the API request.
157:      *
158:      * It detects the action, retrieves the request headers, parses the incoming JSON data and
159:      * tries to call the corresponding method for the API action.
160:      *
161:      * @return void
162:      *
163:      * @throws \Exception
164:      * It might throw exceptions in the case of any errors.
165:      *
166:      * @author Oleg Schildt
167:      */
168:     public function handleRequest(): void
169:     {
170:         try {
171:             $this->processInputData();
172: 
173:             $this->parseInput();
174: 
175:             if (empty($this->action)) {
176:                 throw new SmartException("The action of the API request cannot be defined!", SmartException::ERR_CODE_SYSTEM_ERROR);
177:             }
178: 
179:             $robject = new \ReflectionObject($this);
180:             if (!$robject->hasMethod($this->action)) {
181:                 throw new SmartException(sprintf("No handler is defined for the API request action '%s'!", $this->action), SmartException::ERR_CODE_SYSTEM_ERROR);
182:             }
183: 
184:             $rmethod = $robject->getMethod($this->action);
185: 
186:             if ($this->action == "handleRequest" || $rmethod->isConstructor() || $rmethod->isDestructor() || !$rmethod->isPublic()) {
187:                 throw new SmartException(sprintf("The name '%s' for the API action is not supported!", $this->action), SmartException::ERR_CODE_SYSTEM_ERROR);
188:             }
189: 
190:             $this->preprocessRequest();
191:             $rmethod->invoke($this);
192:             $this->postprocessRequest();
193:         } catch (SmartException $ex) {
194:             messenger()->addError($ex->getMessage(), $ex->getErrorDetails(), $ex->getErrorElement(), $ex->getErrorCode(), $ex->getTechnicalInfo(), $ex->getFile(), $ex->getLine());
195:         } catch (\Throwable $ex) {
196:             messenger()->addError("System error occurred!", [], "", SmartException::ERR_CODE_SYSTEM_ERROR, $ex->getMessage(), $ex->getFile(), $ex->getLine());
197:             singleton(IErrorHandler::class)->handleException($ex, E_USER_ERROR);
198:         }
199: 
200:         $this->sendResponse();
201:     } // handleRequest
202: } // RequestHandler