1: <?php
2:
3: namespace Racoon\Api;
4:
5:
6: use Racoon\Api\Auth\AuthInterface;
7: use Racoon\Api\Auth\NoAuthenticator;
8: use Racoon\Api\Exception\Exception;
9: use Racoon\Api\Exception\InvalidJsonException;
10: use Racoon\Api\Response\Format\FormatterInterface;
11: use Racoon\Api\Response\Generate\DetailedResponse;
12: use Racoon\Api\Response\Generate\GeneratorInterface;
13: use Racoon\Api\Response\Format\JsonFormatter;
14: use Racoon\Router\Router;
15:
16: /**
17: * The main Racoon API class, through which the entire application should accessed and processed.
18: * @package Racoon\Api
19: */
20: class App
21: {
22:
23: /**
24: * Where Racoon should look in the GET / POST array for the JSON request string.
25: * @var string
26: */
27: protected $jsonKeyName = 'json';
28:
29: /**
30: * Where Racoon is going to look for the json input data.
31: * Available values: request, body
32: * @var string
33: */
34: protected $jsonInputMethod = 'request';
35:
36: /**
37: * The Authenticator that should be used by the application.
38: * @var AuthInterface
39: */
40: protected $authenticator;
41:
42: /**
43: * The current Request in the Racoon Application.
44: * @var Request
45: */
46: protected $request;
47:
48: /**
49: * The name of the Request class Racoon should use.
50: * This allows you to add additional functionality to the Request.
51: * @var string
52: */
53: protected $requestClass;
54:
55: /**
56: * The current Router object being used by the Application.
57: * @var Router
58: */
59: protected $router;
60:
61: /**
62: * The Formatter to be used by Racoon when formatting the request response.
63: * @var FormatterInterface
64: */
65: protected $responseFormatter;
66:
67: /**
68: * The Generator to be used by Racoon when putting the request response together.
69: * @var GeneratorInterface
70: */
71: protected $responseGenerator;
72:
73: /**
74: * Defines whether or not the Controller requires a Schema to be set.
75: * @var bool
76: */
77: protected $requiresSchema;
78:
79:
80: /**
81: * App constructor.
82: * Set some default options.
83: */
84: public function __construct()
85: {
86: $this->setRequestClass('\\Racoon\\Api\\Request');
87: $this->authenticator = new NoAuthenticator();
88: $this->router = new Router();
89: $this->responseFormatter = new JsonFormatter();
90: $this->responseGenerator = new DetailedResponse();
91: $this->setRequiresSchema(false);
92: }
93:
94:
95: /**
96: * Creates the Request for Racoon to use.
97: */
98: public function createRequest()
99: {
100: $reflectionClass = new \ReflectionClass($this->getRequestClass());
101: $this->request = $reflectionClass->newInstance();
102: $this->getResponseGenerator()->setRequest($this->request);
103: }
104:
105:
106: /**
107: * @throws Exception\InvalidJsonException
108: */
109: protected function setupRequest()
110: {
111: $json = $this->getJsonStringFromRequest();
112: $uri = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : null;
113: $requestMethod = isset($_SERVER['REQUEST_METHOD']) ? $_SERVER['REQUEST_METHOD'] : null;
114:
115: $this->request
116: ->setRequestJson($json)
117: ->setHttpMethod($requestMethod)
118: ->setUri($uri);
119: }
120:
121:
122: /**
123: * @return string|null
124: */
125: protected function getJsonStringFromRequest()
126: {
127: $json = null;
128: $jsonInputMethod = $this->getJsonInputMethod();
129:
130: switch ($jsonInputMethod) {
131: case 'request':
132: $json = isset($_REQUEST[$this->getJsonKeyName()]) ? $_REQUEST[$this->getJsonKeyName()] : null;
133: break;
134:
135: case 'body':
136: $json = file_get_contents('php://input');
137: break;
138: }
139:
140: return $json;
141: }
142:
143:
144: /**
145: * Runs the Application.
146: * @throws Exception
147: * @throws \Exception
148: */
149: public function run()
150: {
151: if ($this->getRequest() === null) {
152: $this->createRequest();
153: }
154:
155: try {
156: $this->setupRequest();
157: $this->authenticator->authenticate($this->request);
158: $this->router->init();
159: $this->request->process($this->router, $this->getRequiresSchema());
160: } catch (\Exception $e) {
161: if (method_exists($e, 'shouldDisplayAsError') && is_callable([$e, 'shouldDisplayAsError'])) {
162: if ($e->shouldDisplayAsError()) {
163: $this->request->setDisplayException($e);
164: } else {
165: http_response_code(500);
166: throw $e;
167: }
168: } else {
169: http_response_code(500);
170: throw $e;
171: }
172: }
173:
174: $this->request->setEndTime(microtime(true));
175:
176: $response = $this->getResponseGenerator()->generate();
177: $httpResponseCode = $this->getResponseGenerator()->getHttpResponseCode();
178:
179: $formattedResponse = null;
180:
181: try {
182: $formattedResponse = $this->responseFormatter->format($response);
183: } catch (Exception $e) {
184: // Attach the request to the exception.
185: if (! is_object($e->getRequest())) {
186: $e->setRequest($this);
187: }
188:
189: http_response_code(500);
190: throw $e;
191: }
192:
193: $contentType = $this->responseFormatter->getContentType();
194: if (! headers_sent()) {
195: if ($contentType !== null) {
196: header("Content-Type: {$contentType}");
197: }
198: http_response_code($httpResponseCode);
199: }
200:
201: echo $formattedResponse;
202: }
203:
204:
205: /**
206: * Returns where Racoon should look in the GET / POST array for the JSON request string.
207: * @return string
208: */
209: public function getJsonKeyName()
210: {
211: return $this->jsonKeyName;
212: }
213:
214:
215: /**
216: * Sets where Racoon should look in the GET / POST array for the JSON request string.
217: * @param string $jsonKeyName
218: */
219: public function setJsonKeyName($jsonKeyName)
220: {
221: $this->jsonKeyName = $jsonKeyName;
222: }
223:
224:
225: /**
226: * Returns the Authenticator that should be used by the application.
227: * @return AuthInterface
228: */
229: public function getAuthenticator()
230: {
231: return $this->authenticator;
232: }
233:
234:
235: /**
236: * Sets the Authenticator that should be used by the application.
237: * @param AuthInterface $authenticator
238: */
239: public function setAuthenticator(AuthInterface $authenticator)
240: {
241: $this->authenticator = $authenticator;
242: }
243:
244:
245: /**
246: * Returns the Router being used by the Application.
247: * @return Router
248: */
249: public function getRouter()
250: {
251: return $this->router;
252: }
253:
254:
255: /**
256: * Returns the Formatter to be used by Racoon when formatting the request response.
257: * @return FormatterInterface
258: */
259: public function getResponseFormatter()
260: {
261: return $this->responseFormatter;
262: }
263:
264:
265: /**
266: * Sets the Formatter to be used by Racoon when formatting the request response.
267: * @param FormatterInterface $responseFormatter
268: */
269: public function setResponseFormatter($responseFormatter)
270: {
271: $this->responseFormatter = $responseFormatter;
272: }
273:
274:
275: /**
276: * Returns the Generator to be used by Racoon when putting the request response together.
277: * @return GeneratorInterface
278: */
279: public function getResponseGenerator()
280: {
281: return $this->responseGenerator;
282: }
283:
284:
285: /**
286: * Sets the Generator to be used by Racoon when putting the request response together.
287: * @param GeneratorInterface $responseGenerator
288: */
289: public function setResponseGenerator($responseGenerator)
290: {
291: $this->responseGenerator = $responseGenerator;
292: }
293:
294:
295: /**
296: * Returns the name of the Request class Racoon should use.
297: * @return string
298: */
299: public function getRequestClass()
300: {
301: return $this->requestClass;
302: }
303:
304:
305: /**
306: * Sets the name of the Request class Racoon should use.
307: * @param string $requestClass
308: */
309: public function setRequestClass($requestClass)
310: {
311: $this->requestClass = $requestClass;
312: }
313:
314:
315: /**
316: * Returns the current Request being used by Racoon.
317: * @return Request
318: */
319: public function getRequest()
320: {
321: return $this->request;
322: }
323:
324:
325: /**
326: * Returns whether or not a Schema is required for the current Request.
327: * @return boolean
328: */
329: public function getRequiresSchema()
330: {
331: return $this->requiresSchema;
332: }
333:
334:
335: /**
336: * Sets whether or not a Schema is required for the current Request.
337: * @param boolean $requiresSchema
338: */
339: public function setRequiresSchema($requiresSchema)
340: {
341: $this->requiresSchema = $requiresSchema;
342: }
343:
344:
345: /**
346: * Returns where Racoon is going to look for the json input data.
347: * @return string
348: */
349: public function getJsonInputMethod()
350: {
351: return $this->jsonInputMethod;
352: }
353:
354:
355: /**
356: * Sets where Racoon is going to look for the json input data.
357: * @param string $jsonInputMethod
358: * @throws InvalidJsonException
359: */
360: public function setJsonInputMethod($jsonInputMethod)
361: {
362: $validMethods = [
363: 'request',
364: 'body',
365: ];
366: if (! in_array($jsonInputMethod, $validMethods)) {
367: $validMethodsString = implode(', ', $validMethods);
368: throw new InvalidJsonException($this->getRequest(), "Invalid JSON Input Method: {$jsonInputMethod}. Must be one of: {$validMethodsString}");
369: }
370: $this->jsonInputMethod = $jsonInputMethod;
371: }
372:
373: }