Skip to content

Commit ce47be5

Browse files
committed
Configure error pages in routes.php
1 parent 16a08d9 commit ce47be5

File tree

7 files changed

+75
-33
lines changed

7 files changed

+75
-33
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ All notable changes to this project will be documented in this file.
1111
* Repositories now have a `->remove()` method to delete model objects.
1212
* `ViewFactory` to instanciate a view object whenever needed
1313
* Signal in the `ViewFactory` to make implementations of other templating engines possible.
14+
* Configure error pages for HTTP status codes in your `routes.php`.
1415

1516
### Changed
1617

classes/Http/HttpErrorException.php

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
<?php
2+
namespace AppZap\PHPFramework\Http;
3+
4+
class HttpErrorException extends \Exception {}

classes/Mail/TransportFactory.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -65,4 +65,4 @@ protected function createSmtpTransport() {
6565
return $transport;
6666
}
6767

68-
}
68+
}

classes/Mvc/Dispatcher.php

+16-7
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
namespace AppZap\PHPFramework\Mvc;
33

44
use AppZap\PHPFramework\Cache\CacheFactory;
5+
use AppZap\PHPFramework\Http\HttpErrorException;
56
use AppZap\PHPFramework\Mvc\View\ViewFactory;
67
use AppZap\PHPFramework\SignalSlot\Dispatcher as SignalSlotDispatcher;
78

@@ -42,8 +43,8 @@ class Dispatcher {
4243
public function __construct() {
4344
SignalSlotDispatcher::emitSignal(self::SIGNAL_CONSTRUCT);
4445
$this->cache = CacheFactory::getCache();
45-
$this->determineRequestMethod();
4646
$this->viewFactory = ViewFactory::getInstance();
47+
$this->determineRequestMethod();
4748
}
4849

4950
/**
@@ -103,15 +104,23 @@ protected function getRouter($uri) {
103104
}
104105

105106
/**
106-
* @param $uri
107+
* @param string $uri
107108
* @return string
109+
* @throws \Exception
108110
*/
109111
protected function dispatchUncached($uri) {
110-
$router = $this->getRouter($uri);
111-
if (is_callable($router->getResponder())) {
112-
$output = $this->dispatchCallable($router);
113-
} else {
114-
$output = $this->dispatchController($router);
112+
try {
113+
$router = $this->getRouter($uri);
114+
if (is_callable($router->getResponder())) {
115+
$output = $this->dispatchCallable($router);
116+
} else {
117+
$output = $this->dispatchController($router);
118+
}
119+
} catch (HttpErrorException $e) {
120+
if (!$e->getCode()) {
121+
throw new \Exception('HttpErrorException was thrown without HTTP Status code', 1421830421);
122+
}
123+
return $this->dispatchUncached($e->getCode());
115124
}
116125
return $output;
117126
}

classes/Mvc/Router.php

+43-16
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
namespace AppZap\PHPFramework\Mvc;
33

44
use AppZap\PHPFramework\Configuration\Configuration;
5+
use AppZap\PHPFramework\Http\HttpErrorException;
56
use AppZap\PHPFramework\SignalSlot\Dispatcher as SignalSlotDispatcher;
67

78
class Router {
@@ -20,21 +21,23 @@ class Router {
2021

2122
/**
2223
* @param string $resource
23-
* @throws ApplicationPartMissingException
24+
* @throws HttpErrorException
2425
* @throws InvalidHttpResponderException
2526
*/
2627
public function __construct($resource) {
2728
$routes = $this->collectRoutesDefinitions();
2829
$this->route($routes, $resource);
2930

30-
// check if the responder is valid
31-
if (is_string($this->responder)) {
32-
if (!class_exists($this->responder)) {
33-
throw new InvalidHttpResponderException('Controller ' . $this->responder . ' for uri "' . $resource . '" not found!', 1415129223);
34-
}
35-
} elseif(!isset($this->responder)) {
36-
throw new InvalidHttpResponderException('Route ' . $resource . ' could not be routed.', 1415136995);
37-
} elseif (!is_callable($this->responder)) {
31+
if (!isset($this->responder)) {
32+
HttpStatus::setStatus(HttpStatus::STATUS_404_NOT_FOUND);
33+
throw new HttpErrorException('Resource not routable', 404);
34+
}
35+
36+
if (is_string($this->responder) && !class_exists($this->responder)) {
37+
throw new InvalidHttpResponderException('Controller ' . $this->responder . ' for uri "' . $resource . '" not found!', 1415129223);
38+
}
39+
40+
if (!is_string($this->responder) && !is_callable($this->responder)) {
3841
throw new InvalidHttpResponderException('The responder must either be a class string, a callable or an array of subpaths', 1415129333);
3942
}
4043
}
@@ -54,34 +57,58 @@ protected function collectRoutesDefinitions() {
5457
if (!is_array($applicationRoutes)) {
5558
throw new InvalidHttpResponderException('The routes file did not return an array with routes', 1415135585);
5659
}
57-
$routes = array_merge($routes, $applicationRoutes);
60+
$routes = $applicationRoutes + $routes;
5861
}
5962
return $routes;
6063
}
6164

6265
/**
6366
* @param array $routes
64-
* @param string $resource
67+
* @param mixed $resource
6568
*/
6669
protected function route($routes, $resource) {
70+
if (is_int($resource)) {
71+
$this->routeHttpStatusCode($routes, $resource);
72+
return;
73+
}
6774
$resource = ltrim($resource, '/');
68-
foreach ($routes as $regex => $regexResponder) {
75+
foreach ($routes as $regex => $responder) {
76+
if ($responder === FALSE) {
77+
continue;
78+
}
6979
$regex = $this->enhanceRegex($regex);
70-
if ($regexResponder !== FALSE && preg_match($regex, $resource, $matches)) {
80+
if (preg_match($regex, $resource, $matches)) {
7181
$matchesCount = count($matches);
7282
for ($i = 1; $i < $matchesCount; $i++) {
7383
$this->parameters[] = $matches[$i];
7484
}
75-
if (is_array($regexResponder)) {
76-
$this->route($regexResponder, preg_replace($regex, '', $resource));
85+
if (is_array($responder)) {
86+
$this->route($responder, preg_replace($regex, '', $resource));
7787
} else {
78-
$this->responder = $regexResponder;
88+
$this->responder = $responder;
7989
}
8090
break;
8191
}
8292
}
8393
}
8494

95+
/**
96+
* @param array $routes
97+
* @param int $httpStatusCode
98+
*/
99+
protected function routeHttpStatusCode($routes, $httpStatusCode) {
100+
if (isset($routes[$httpStatusCode])) {
101+
$this->responder = $routes[$httpStatusCode];
102+
return;
103+
}
104+
// convert e.g. 405 to 400
105+
$baseStatusCode = (int) floor($httpStatusCode / 100) * 100;
106+
if (isset($routes[$baseStatusCode])) {
107+
$this->responder = $routes[$baseStatusCode];
108+
return;
109+
}
110+
}
111+
85112
/**
86113
* @return array
87114
*/

classes/Mvc/View/ViewInterface.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -38,4 +38,4 @@ public function setTemplatesDirectory($templatesDirectory);
3838
*/
3939
public function render($templateName = NULL);
4040

41-
}
41+
}

tests/Unit/Mvc/RouterTest.php

+9-8
Original file line numberDiff line numberDiff line change
@@ -73,8 +73,8 @@ public function neitherClassNorCallableButInteger() {
7373

7474
/**
7575
* @test
76-
* @expectedException \AppZap\PHPFramework\Mvc\InvalidHttpResponderException
77-
* @expectedExceptionCode 1415136995
76+
* @expectedException \AppZap\PHPFramework\Http\HttpErrorException
77+
* @expectedExceptionCode 404
7878
*/
7979
public function resourceNotRoutable() {
8080
$this->loadRoutesFile('classnames');
@@ -87,13 +87,14 @@ public function resourceNotRoutable() {
8787
public function routeToClassNames() {
8888
$this->loadRoutesFile('classnames');
8989
$router = new Router('/');
90-
$responder_class = $router->getResponder();
91-
$this->assertTrue(class_exists($responder_class));
92-
$this->assertSame('\AppZap\PHPFramework\Tests\Mvc\Responder_Index', $responder_class);
90+
$responderClass = $router->getResponder();
91+
$this->assertTrue(is_string($responderClass), '$responderClass (' . gettype($responderClass) . ') should be string');
92+
$this->assertTrue(class_exists($responderClass));
93+
$this->assertSame('\AppZap\PHPFramework\Tests\Mvc\Responder_Index', $responderClass);
9394
$router = new Router('/foo');
94-
$responder_class = $router->getResponder();
95-
$this->assertTrue(class_exists($responder_class));
96-
$this->assertSame('\AppZap\PHPFramework\Tests\Mvc\Responder_Foo', $responder_class);
95+
$responderClass = $router->getResponder();
96+
$this->assertTrue(class_exists($responderClass));
97+
$this->assertSame('\AppZap\PHPFramework\Tests\Mvc\Responder_Foo', $responderClass);
9798
}
9899

99100
/**

0 commit comments

Comments
 (0)