M src/Application.php => src/Application.php +19 -12
@@ 14,10 14,13 @@ final class Application
public function __construct(Container $container)
{
$this->container = $container;
- $this->router = new Router;
+ $this->router = new Router();
- $this->addRoute('GET', '/404', fn() => response('404 Page Not Found', 404));
- $this->addRoute('GET', '/405', fn() => response('405 Method Not Allowed', 405));
+ ErrorHandler::register($container['error_logger']);
+
+ $this->addRoute('GET', '/', fn() => response("Hello World!\n"));
+ $this->addRoute('GET', '/404', fn() => response("404 Page Not Found\n", 404));
+ $this->addRoute('GET', '/405', fn() => response("405 Method Not Allowed\n", 405));
}
/**
@@ 55,19 58,23 @@ final class Application
[$_, $handler, $_] = $this->router->dispatch('GET', '/405');
}
- foreach ($this->middlewares as $middleware) {
- $request = $middleware($request);
+ if (! $handler[0] instanceof \Closure) {
+ $handler[0] = $this->container[$handler[0]];
}
- foreach (array_pop($handler) as $middleware) {
- $request = $middleware($request);
- }
+ $middlewares = [
+ ...$this->middlewares,
+ ...array_pop($handler),
+ fn(Request $request) => $handler($request, $args),
+ ];
- if (! $handler[0] instanceof \Closure) {
- $handler[0] = $this->container[$handler[0]];
+ $action = fn(Request $input) => $input;
+
+ foreach (array_reverse($middlewares) as $middleware) {
+ $action = fn(Request $request) => $middleware($request, $action);
}
- $response = $handler($request, $args);
+ $response = $action($request);
if ($response instanceof Response) {
$response->send();
@@ 75,4 82,4 @@ final class Application
print $response;
}
}
-}>
\ No newline at end of file
+}
M src/common.php => src/common.php +17 -0
@@ 5,3 5,20 @@ namespace Thirdplace;
final class HttpException extends \Exception {}
+function retry(int $times, \Closure $fn, int $sleep = 0)
+{
+ // todo
+}
+
+function create_sane_stacktrace(\Throwable $e)
+{
+ $stackTrace[] = sprintf('%s:%s', $e->getFile(), $e->getLine());
+ foreach ($e->getTrace() as $trace) {
+ $stackTrace[] = sprintf(
+ '%s:%s',
+ $trace['file'] ?? '(no file)',
+ $trace['line'] ?? '(no line)'
+ );
+ }
+ return array_reverse($stackTrace);
+}
M => +2 -0
@@ 9,6 9,7 @@ final class Header
public const APPLICATION_JSON = 'application/json';
public const APPLICATION_RSS_XML = 'application/rss+xml';
public const APPLICATION_XML = 'application/xml';
public const APPLICATION_X_RSS_XML = 'application/x-rss+xml';
public const TEXT_PLAIN = 'text/plain';
public const TEXT_XML = 'text/xml';
@@ 25,6 26,7 @@ final class Header
$header = explode(':', $rawHeader);
if (count($header) === 1) {
// todo: throw HeaderException
throw new \Exception(sprintf('Invalid header string: "%s"', $rawHeader));
}
M src/http/Request.php => src/http/Request.php +18 -3
@@ 10,6 10,7 @@ final class Request
private array $cookies;
private array $files;
private array $server;
+ private string $body;
private array $headers;
private array $attributes;
@@ 24,9 25,18 @@ final class Request
$self->cookies = $_COOKIE;
$self->files = $_FILES;
$self->server = $_SERVER;
+ $self->body = file_get_contents("php://input");
- foreach (getallheaders() as $name => $value) {
- $self->headers[strtolower($name)] = $value;
+ if ($self->body === false) {
+ throw new HttpException('Failed to read raw body');
+ }
+
+ if (function_exists('getallheaders')) {
+ foreach (\getallheaders() as $name => $value) {
+ $self->headers[strtolower($name)] = $value;
+ }
+ } else {
+ $self->headers = [];
}
$self->attributes = [];
@@ 124,4 134,9 @@ final class Request
{
return $this->server[$key] ?? $default;
}
-}>
\ No newline at end of file
+
+ public function body(): string
+ {
+ return $this->body;
+ }
+}
M src/http/Response.php => src/http/Response.php +3 -20
@@ 15,8 15,7 @@ function text(string $body, int $code = 200): Response
function json(array $body, int $code = 200): Response
{
- // todo: remove dep
- $json = Json::encode($body, JSON_PRETTY_PRINT);
+ $json = Json::encode($body);
return response($json, $code)->withHeader('content-type', 'application/json');
}
@@ 36,6 35,7 @@ final class Response
public const TEMPORARY_REDIRECT = 307;
public const BAD_REQUEST = 400;
public const UNAUTHORIZED = 401;
+ public const FORBIDDEN = 403;
public const NOT_FOUND = 404;
public const METHOD_NOT_ALLOWED = 405;
public const TOO_MANY_REQUESTS = 429;
@@ 53,6 53,7 @@ final class Response
self::TEMPORARY_REDIRECT => 'Temporary Redirect',
self::BAD_REQUEST => 'Bad Request',
self::UNAUTHORIZED => 'Unauthorized',
+ self::FORBIDDEN => 'Forbidden',
self::NOT_FOUND => 'Not Found',
self::METHOD_NOT_ALLOWED => 'Method Not Allowed',
self::TOO_MANY_REQUESTS => 'Too Many Requests',
@@ 89,24 90,6 @@ final class Response
return $this->code === 200;
}
- public function isXml(): bool
- {
- $contentType = $this->header('content-type', 'foo;bar');
- $parts = explode(';', $contentType);
- $body = trim($this->body);
-
- return
- $body && (
- in_array($parts[0], [
- Header::TEXT_XML,
- Header::APPLICATION_XML,
- Header::APPLICATION_RSS_XML,
- 'application/x-rss+xml'
- ])
- || strpos($body, '<?xml') === 0 // possibly false positives
- || strpos($body, '<rss') === 0);
- }
-
public function withCode(int $code): self
{
$clone = clone $this;
M src/logger/ErrorHandler.php => src/logger/ErrorHandler.php +1 -1
@@ 22,7 22,7 @@ final class ErrorHandler
$e->getLine()
);
- $logger->log(Logger::ERROR, $message, ['e' => $e]);
+ $logger->error($message, ['e' => $e]);
exit(1);
});
M src/logger/StreamHandler.php => src/logger/StreamHandler.php +3 -13
@@ 15,28 15,18 @@ final class StreamHandler
public function __invoke(array $record): void
{
if (isset($record['context']['e'])) {
- /** @var \Throwable $e */
- $e = $record['context']['e'];
- $stackTrace[] = sprintf('%s:%s', $e->getFile(), $e->getLine());
- foreach ($e->getTrace() as $trace) {
- $stackTrace[] = sprintf(
- '%s:%s',
- $trace['file'] ?? '(no file)',
- $trace['line'] ?? '(no line)'
- );
- }
- $record['context']['e'] = array_reverse($stackTrace);
+ $record['context']['e'] = create_sane_stacktrace($record['context']['e']);
}
if ($record['context'] === []) {
$record['context'] = '';
} else {
$json = Json::encode($record['context']) ?: '["Unable to json encode context"]';
- $record['context'] = " $json";
+ $record['context'] = $json;
}
$result = sprintf(
- "[%s] %s.%s %s%s\n",
+ "[%s] %s.%s %s %s\n",
$record['created_at']->format('Y-m-d H:i:s'),
$record['name'],
$record['level_name'],