1: <?php
2: 3: 4: 5: 6: 7:
8:
9: namespace Wei;
10:
11: 12: 13: 14: 15: 16: 17: 18: 19:
20: class App extends Base
21: {
22: 23: 24:
25: const FORWARD = 1302;
26:
27: 28: 29: 30: 31:
32: protected $controllerFormat = 'controllers\%controller%';
33:
34: 35: 36: 37: 38:
39: protected $defaultController = 'index';
40:
41: 42: 43: 44: 45:
46: protected $defaultAction = 'index';
47:
48: 49: 50: 51: 52:
53: protected $namespace;
54:
55: 56: 57: 58: 59:
60: protected $controller;
61:
62: 63: 64: 65: 66:
67: protected $action;
68:
69: 70: 71: 72: 73:
74: protected $controllerInstances = array();
75:
76: 77: 78: 79: 80: 81:
82: protected $controllerMap = array();
83:
84: 85: 86: 87: 88: 89: 90:
91: public function __invoke(array $options = array())
92: {
93: $options && $this->setOption($options);
94:
95:
96: $request = $this->request;
97: $paramSet = $this->router->matchParamSet($request->getPathInfo(), $request->getMethod());
98:
99:
100: $notFound = array();
101: foreach ($paramSet as $params) {
102: $response = $this->dispatch($params['controller'], $params['action'], $params, false);
103: if (is_array($response)) {
104: $notFound = array_merge($notFound, $response);
105: } else {
106: return $response;
107: }
108: }
109: throw $this->buildException($notFound);
110: }
111:
112: 113: 114: 115: 116: 117: 118: 119: 120:
121: public function dispatch($controller, $action = null, array $params = array(), $throwException = true)
122: {
123: $notFound = array();
124: $action || $action = $this->defaultAction;
125: $classes = $this->getControllerClasses($controller);
126:
127: foreach ($classes as $class) {
128: if (!class_exists($class)) {
129: $notFound['controllers'][$controller][] = $class;
130: continue;
131: }
132: if (!$this->isActionAvailable($class, $action)) {
133: $notFound['actions'][$action][$controller][] = $class;
134: continue;
135: }
136:
137:
138: $this->setController($controller);
139: $this->setAction($action);
140: $this->request->set($params);
141: try {
142: $instance = $this->getControllerInstance($class);
143: return $this->execute($instance, $action);
144: } catch (\RuntimeException $e) {
145: if ($e->getCode() === self::FORWARD) {
146: return $this;
147: } else {
148: throw $e;
149: }
150: }
151: }
152:
153: if ($throwException) {
154: throw $this->buildException($notFound);
155: } else {
156: return $notFound;
157: }
158: }
159:
160: 161: 162: 163: 164: 165:
166: protected function buildException(array $notFound)
167: {
168: $notFound += array('controllers' => array(), 'actions' => array());
169:
170:
171: $message = 'The page you requested was not found';
172: if ($this->wei->isDebug()) {
173: $detail = $this->request->get('debug-detail');
174: foreach ($notFound['controllers'] as $controller => $classes) {
175: $message .= sprintf('%s - controller "%s" not found', "\n", $controller);
176: $detail && $message .= sprintf(' (class "%s")', implode($classes, '", "'));
177: }
178: foreach ($notFound['actions'] as $action => $controllers) {
179: foreach ($controllers as $controller => $classes) {
180: $message .= sprintf('%s - action method "%s" not found in controller "%s"', "\n", $action, $controller);
181: $detail && $message .= sprintf(' (class "%s")', implode($classes, '", "'));
182: }
183: }
184: }
185:
186:
187: return new \RuntimeException($message, 404);
188: }
189:
190: 191: 192: 193: 194: 195: 196:
197: protected function execute($instance, $action)
198: {
199: $app = $this;
200: $wei = $this->wei;
201: $middleware = $this->getMiddleware($instance, $action);
202:
203: $callback = function () use ($instance, $action, $app) {
204: $response = $instance->$action($app->request, $app->response);
205: return $app->handleResponse($response);
206: };
207:
208: $next = function () use (&$middleware, &$next, $callback, $wei) {
209: $config = array_splice($middleware, 0, 1);
210: if ($config) {
211: $class = key($config);
212: $service = new $class(array('wei' => $wei) + $config[$class]);
213: $result = $service($next);
214: } else {
215: $result = $callback();
216: }
217: return $result ?: $wei->response;
218: };
219:
220: return $next()->send();
221: }
222:
223: 224: 225: 226: 227: 228: 229:
230: protected function getMiddleware($instance, $action)
231: {
232: $results = array();
233: $middleware = (array)$instance->getOption('middleware');
234: foreach ($middleware as $class => $options) {
235: if ((!isset($options['only']) || in_array($action, (array)$options['only'])) &&
236: (!isset($options['except']) || !in_array($action, (array)$options['except']))
237: ) {
238: $results[$class] = $options;
239: }
240: }
241: return $results;
242: }
243:
244: 245: 246: 247: 248: 249: 250:
251: public function handleResponse($response)
252: {
253: switch (true) {
254:
255: case is_array($response) :
256: $content = $this->view->render($this->getDefaultTemplate(), $response);
257: return $this->response->setContent($content);
258:
259:
260: case is_scalar($response) || is_null($response) :
261: return $this->response->setContent($response);
262:
263:
264: case $response instanceof Response :
265: return $response;
266:
267: default :
268: throw new \InvalidArgumentException(sprintf(
269: 'Expected argument of type array, printable variable or \Wei\Response, "%s" given',
270: is_object($response) ? get_class($response) : gettype($response)
271: ));
272: }
273: }
274:
275: 276: 277: 278: 279:
280: public function getNamespace()
281: {
282: if (!$this->namespace) {
283: $this->namespace = $this->request->get('namespace');
284: }
285: return $this->namespace;
286: }
287:
288: 289: 290: 291: 292: 293:
294: public function setNamespace($namespace)
295: {
296: $this->namespace = $namespace;
297: return $this;
298: }
299:
300: 301: 302: 303: 304:
305: public function getController()
306: {
307: return $this->controller ?: $this->defaultController;
308: }
309:
310: 311: 312: 313: 314: 315:
316: public function setController($controller)
317: {
318: $this->controller = $controller;
319: return $this;
320: }
321:
322: 323: 324: 325: 326:
327: public function getAction()
328: {
329: return $this->action ?: $this->defaultAction;
330: }
331:
332: 333: 334: 335: 336: 337:
338: public function setAction($action)
339: {
340: $this->action = $action;
341: return $this;
342: }
343:
344: 345: 346: 347: 348:
349: public function getControllerAction()
350: {
351: return $this->getController() . '/' . $this->getAction();
352: }
353:
354: 355: 356: 357: 358: 359:
360: public function getControllerClasses($controller)
361: {
362: $classes = array();
363:
364:
365: $namespace = $this->getNamespace();
366: $controller = strtr($controller, array('/' => '\\'));
367:
368:
369: $class = str_replace(
370: array('%namespace%', '%controller%'),
371: array($namespace, $controller),
372: $this->controllerFormat
373: );
374:
375:
376: $upperLetter = strrpos($class, '\\') + 1;
377: $class[$upperLetter] = strtoupper($class[$upperLetter]);
378: $classes[] = $class;
379:
380:
381: if (isset($this->controllerMap[$controller])) {
382: $classes[] = $this->controllerMap[$controller];
383: }
384:
385: return $classes;
386: }
387:
388: public function setControllerMap(array $controllerMap)
389: {
390: $this->controllerMap += $controllerMap;
391: }
392:
393: 394: 395: 396: 397: 398:
399: protected function getControllerInstance($class)
400: {
401: if (!isset($this->controllerInstances[$class])) {
402: $this->controllerInstances[$class] = new $class(array(
403: 'wei' => $this->wei,
404: 'app' => $this,
405: ));
406: }
407: return $this->controllerInstances[$class];
408: }
409:
410: 411: 412: 413: 414: 415: 416: 417: 418: 419: 420: 421: 422:
423: public function isActionAvailable($object, $action)
424: {
425: try {
426: $ref = new \ReflectionMethod($object, $action);
427: if ($ref->isPublic() && $action === $ref->name && $action[0] !== '_') {
428: return true;
429: } else {
430: return false;
431: }
432: } catch (\ReflectionException $e) {
433: return false;
434: }
435: }
436:
437: 438: 439: 440: 441:
442: public function preventPreviousDispatch()
443: {
444: throw new \RuntimeException('Forwarding, please ignore me', self::FORWARD);
445: }
446:
447: 448: 449: 450: 451: 452:
453: public function getDefaultTemplate()
454: {
455: return lcfirst($this->controller) . '/' . $this->action . $this->view->getExtension();
456: }
457:
458: 459: 460: 461: 462: 463:
464: public function forward($controller, $action = null)
465: {
466: $this->dispatch($controller, $action);
467: $this->preventPreviousDispatch();
468: }
469: }