~thirdplace/components

fa9997838574093e21a8d8fbbad8eeda1c400090 — Dag 1 year, 4 months ago 0ccfef9
refactor
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 src/http/Header.php => src/http/Header.php +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'],