diff --git a/composer.json b/composer.json index 5a1f0cec..c73b2b5e 100644 --- a/composer.json +++ b/composer.json @@ -10,7 +10,8 @@ "8fold/commonmark-fluent-markdown": "^0.10.0", "8fold/php-html-builder": "^0.5.3", "nyholm/psr7": "^1.4", - "laminas/laminas-httphandlerrunner": "^2.1" + "laminas/laminas-httphandlerrunner": "^2.1", + "filp/whoops": "^2.14" }, "require-dev": { "squizlabs/php_codesniffer": "^3.6", diff --git a/composer.lock b/composer.lock index 3f764d15..c9d286ae 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "e00fe921bef6464942fa1c04f87d2e5a", + "content-hash": "f641dbea110889eee30df183d5b2a298", "packages": [ { "name": "8fold/commonmark-abbreviations", @@ -307,6 +307,77 @@ }, "time": "2021-08-13T13:06:58+00:00" }, + { + "name": "filp/whoops", + "version": "2.14.4", + "source": { + "type": "git", + "url": "https://github.com/filp/whoops.git", + "reference": "f056f1fe935d9ed86e698905a957334029899895" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/filp/whoops/zipball/f056f1fe935d9ed86e698905a957334029899895", + "reference": "f056f1fe935d9ed86e698905a957334029899895", + "shasum": "" + }, + "require": { + "php": "^5.5.9 || ^7.0 || ^8.0", + "psr/log": "^1.0.1 || ^2.0 || ^3.0" + }, + "require-dev": { + "mockery/mockery": "^0.9 || ^1.0", + "phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.3", + "symfony/var-dumper": "^2.6 || ^3.0 || ^4.0 || ^5.0" + }, + "suggest": { + "symfony/var-dumper": "Pretty print complex values better with var-dumper available", + "whoops/soap": "Formats errors as SOAP responses" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Whoops\\": "src/Whoops/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Filipe Dobreira", + "homepage": "https://github.com/filp", + "role": "Developer" + } + ], + "description": "php error handling for cool kids", + "homepage": "https://filp.github.io/whoops/", + "keywords": [ + "error", + "exception", + "handling", + "library", + "throwable", + "whoops" + ], + "support": { + "issues": "https://github.com/filp/whoops/issues", + "source": "https://github.com/filp/whoops/tree/2.14.4" + }, + "funding": [ + { + "url": "https://github.com/denis-sokolov", + "type": "github" + } + ], + "time": "2021-10-03T12:00:00+00:00" + }, { "name": "graham-campbell/result-type", "version": "v1.0.3", @@ -1190,6 +1261,56 @@ }, "time": "2018-10-30T16:46:14+00:00" }, + { + "name": "psr/log", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "ef29f6d262798707a9edd554e2b82517ef3a9376" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/ef29f6d262798707a9edd554e2b82517ef3a9376", + "reference": "ef29f6d262798707a9edd554e2b82517ef3a9376", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/2.0.0" + }, + "time": "2021-07-14T16:41:46+00:00" + }, { "name": "symfony/deprecation-contracts", "version": "v2.4.0", @@ -1776,77 +1897,6 @@ }, "time": "2020-10-16T08:27:54+00:00" }, - { - "name": "filp/whoops", - "version": "2.14.4", - "source": { - "type": "git", - "url": "https://github.com/filp/whoops.git", - "reference": "f056f1fe935d9ed86e698905a957334029899895" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/filp/whoops/zipball/f056f1fe935d9ed86e698905a957334029899895", - "reference": "f056f1fe935d9ed86e698905a957334029899895", - "shasum": "" - }, - "require": { - "php": "^5.5.9 || ^7.0 || ^8.0", - "psr/log": "^1.0.1 || ^2.0 || ^3.0" - }, - "require-dev": { - "mockery/mockery": "^0.9 || ^1.0", - "phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.3", - "symfony/var-dumper": "^2.6 || ^3.0 || ^4.0 || ^5.0" - }, - "suggest": { - "symfony/var-dumper": "Pretty print complex values better with var-dumper available", - "whoops/soap": "Formats errors as SOAP responses" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.7-dev" - } - }, - "autoload": { - "psr-4": { - "Whoops\\": "src/Whoops/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Filipe Dobreira", - "homepage": "https://github.com/filp", - "role": "Developer" - } - ], - "description": "php error handling for cool kids", - "homepage": "https://filp.github.io/whoops/", - "keywords": [ - "error", - "exception", - "handling", - "library", - "throwable", - "whoops" - ], - "support": { - "issues": "https://github.com/filp/whoops/issues", - "source": "https://github.com/filp/whoops/tree/2.14.4" - }, - "funding": [ - { - "url": "https://github.com/denis-sokolov", - "type": "github" - } - ], - "time": "2021-10-03T12:00:00+00:00" - }, { "name": "myclabs/deep-copy", "version": "1.10.2", @@ -3100,56 +3150,6 @@ }, "time": "2021-03-05T17:36:06+00:00" }, - { - "name": "psr/log", - "version": "2.0.0", - "source": { - "type": "git", - "url": "https://github.com/php-fig/log.git", - "reference": "ef29f6d262798707a9edd554e2b82517ef3a9376" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/ef29f6d262798707a9edd554e2b82517ef3a9376", - "reference": "ef29f6d262798707a9edd554e2b82517ef3a9376", - "shasum": "" - }, - "require": { - "php": ">=8.0.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Log\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common interface for logging libraries", - "homepage": "https://github.com/php-fig/log", - "keywords": [ - "log", - "psr", - "psr-3" - ], - "support": { - "source": "https://github.com/php-fig/log/tree/2.0.0" - }, - "time": "2021-07-14T16:41:46+00:00" - }, { "name": "sebastian/cli-parser", "version": "1.0.1", diff --git a/public/index.php b/public/index.php index 7099503d..8a43411f 100644 --- a/public/index.php +++ b/public/index.php @@ -1,32 +1,224 @@ minified() + ->smartPunctuation(); + // Inject environment variables to global $_SERVER array Dotenv\Dotenv::createImmutable(__DIR__ . '/../')->load(); -$server = JoshBruce\Site\Server::init($_SERVER); +$projectRoot = implode('/', array_slice(explode('/', __DIR__), 0, -1)); + +/** + * Verifying setup is valid. + */ +$requestRequiredServerGlobals = [ + 'APP_ENV', + 'CONTENT_UP', + 'CONTENT_FOLDER', + 'REQUEST_SCHEME', + 'HTTP_HOST', + 'REQUEST_URI' +]; + +// TESTING +// unset($_SERVER['APP_ENV']); +$requestHasRequiredServerGlobals = true; +foreach ($requestRequiredServerGlobals as $key) { + if (! array_key_exists($key, $_SERVER)) { + $requestHasRequiredServerGlobals = false; + break; + } +} + +if (! $requestHasRequiredServerGlobals) { + $content = JoshBruce\Site\Content::init($projectRoot, 0, '/500-errors') + ->for('/500.md'); + + JoshBruce\Site\Emitter::emitWithResponse( + 500, + [ + 'Cache-Control' => [ + 'no-cache', + 'must-revalidate' + ] + ], + Eightfold\HTMLBuilder\Document::create( + $markdownConverter->getFrontMatter($content->markdown())['title'] + )->body( + $markdownConverter->convert($content->markdown()) + )->build() + ); + exit; +} + +if ($_SERVER['APP_ENV'] !== 'production') { + $erroHandler = new Whoops\Run; + $erroHandler->pushHandler(new Whoops\Handler\PrettyPageHandler); + $erroHandler->register(); +} + +/** + * Verifying specified content area exists. + */ + +// TESTING +// $_SERVER['CONTENT_FOLDER'] = '/does/not/exist'; +$content = JoshBruce\Site\Content::init( + $projectRoot, + $_SERVER['CONTENT_UP'], + $_SERVER['CONTENT_FOLDER'] +); + +if (! $content->folderDoesExist()) { + $content = JoshBruce\Site\Content::init($projectRoot, 0, '/500-errors') + ->for('/502.md'); + + JoshBruce\Site\Emitter::emitWithResponse( + 502, + [ + 'Cache-Control' => [ + 'no-cache', + 'must-revalidate' + ] + ], + Eightfold\HTMLBuilder\Document::create( + $markdownConverter->getFrontMatter($content->markdown())['title'] + )->body( + $markdownConverter->convert($content->markdown()) + )->build() + ); + exit; +} /** - * We'll probably replace with the middleware pattern from PSR-7. + * Bootsrap is complete: local response time 19ms * - * 1. Check .env file has the required members; 500 response on failure. - * 2. Check the content folder can be accessed; 502 response on failure. - * 3. Start the app and determine response. + * Does the requested content exist? */ -$response = $server->response(); -if ($response->isOk() and $env = JoshBruce\Site\Environment::init($server)) { - // server global set up correctly - start environment - $response = $env->response(); - if ($response->isOk() and $app = JoshBruce\Site\App::init($env)) { - // environment can connect to content - start app - $response = $app->response(); +$requestUri = $_SERVER['REQUEST_URI']; +if ($requestUri === '/') { + $requestUri = ''; +} + +// TESTING +// $requestUri = '/does/not/exist'; // 404 +// +// $requestUri = '/assets/favicons/favicon-16x16.png'; // file +// +// Check browser address becomes /design-your-life +// if ($requestUri !== '/design-your-life') { // redirecting +// $requestUri = '/self-improvement'; // redirecting +// } // redirecting + +$requestIsForFile = strpos($requestUri, '.') > 0; + +$localFilePath = $requestUri . '/content.md'; +if ($requestIsForFile) { + $folderMap = [ + '/css' => '/.assets/styles', + '/js' => '/.assets/scripts', + '/assets' => '/.assets' + ]; + + $parts = explode('/', $requestUri); + $parts = array_filter($parts); + $first = array_shift($parts); + + $folderMapKey = '/' . $first; + + if (array_key_exists($folderMapKey, $folderMap)) { + $replace = $folderMap[$folderMapKey]; + + $localFilePath = str_replace($folderMapKey, $replace, $requestUri); } } -$response->emit(); +$content = $content->for($localFilePath); +if ($content->notFound()) { + $content = $content->for(path: '/.errors/404.md'); + JoshBruce\Site\Emitter::emitWithResponse( + 404, + [ + 'Cache-Control' => [ + 'no-cache', + 'must-revalidate' + ] + ], + Eightfold\HTMLBuilder\Document::create( + $markdownConverter->getFrontMatter($content->markdown())['title'] + )->body( + $markdownConverter->convert($content->markdown()) + )->build() + ); + exit; +} + +/** + * Target file exists: local response time 27ms + * + * Handle file + */ +if ($requestIsForFile) { + JoshBruce\Site\Emitter::emitWithResponseFile( + 200, + [ + 'Cache-Control' => ['max-age=2592000'], + 'Content-Type' => $content->mimeType() + ], + $content->filePath() + ); + exit; +} + +/** + * Target file wants to redirect us: local response time 40ms + */ +$redirectPath = $content->redirectPath(); +if (strlen($redirectPath) > 0) { + $scheme = $_SERVER['REQUEST_SCHEME']; + $serverName = $_SERVER['HTTP_HOST']; + $requestDomain = $scheme . '://' . $serverName; + JoshBruce\Site\Emitter::emitWithResponse( + 301, + [ + 'Location' => $requestDomain . $redirectPath + ] + ); + exit; +} + +/** + * Process HTML response: local response time 75ms (90ms with table content) + */ + +$markdownConverter = $markdownConverter->tables(); + +$headers['Content-Type'] = $content->mimeType(); + +$headElements = JoshBruce\Site\PageComponents\Favicons::create(); +$headElements[] = Eightfold\HTMLBuilder\Element::link() + ->props('rel stylesheet', 'href /css/main.css'); + +$body = Eightfold\HTMLBuilder\Document::create( + $markdownConverter->getFrontMatter($content->markdown())['title'] + )->head( + ...$headElements + )->body( + JoshBruce\Site\PageComponents\Navigation::create($content) + ->build(), + $markdownConverter->convert($content->markdown()) + )->build(); +JoshBruce\Site\Emitter::emitWithResponse(200, $headers, $body); exit; diff --git a/src/App.php b/src/App.php deleted file mode 100644 index 2cf28769..00000000 --- a/src/App.php +++ /dev/null @@ -1,160 +0,0 @@ - '/.assets/styles', - '/js' => '/.assets/scripts', - '/assets' => '/.assets' - ]; - - public static function init(Environment $environment): App - { - return new App($environment); - } - - final public function __construct(private Environment $environment) - { - } - - public function content(): Content - { - return $this->environment()->content(); - } - - public function response(): Response|ResponseFile - { - $status = 200; - $headers = [ - 'Cache-Control' => ['max-age=600'] - ]; - - $content = $this->content() - ->for(path: $this->localFilePathWithoutRoot()); - if ($content->notFound()) { - // MANUAL: Our tests don't run in a browser environemtn; therefore, - // don't believe it's possible to write an automated test for - // this given current setup. - // don't like that this path doesn't return early. - // TODO: refactor this - // - believe a defalt page template would resolve the issue - $content = $this->content()->for(path: '/.errors/404.md'); - - $status = 404; - $headers = [ - 'Cache-Control' => [ - 'no-cache', - 'must-revalidate', - - ] - ]; - - } elseif ($this->isRequestingFile()) { - $headers = [ - 'Cache-Control' => ['max-age=2592000'], - 'Content-Type' => $content->mimeType() - ]; - return ResponseFile::create( - status: $status, - headers: $headers, - file: $content->filePath() - ); - - } elseif ($this->isRedirecting()) { - return Response::create( - status: 301, - headers: [ - 'Location' => $this->requestDomain() . - $this->content()->redirectPath() - ] - ); - } - - $headers['Content-Type'] = $content->mimeType(); - - $headElements = Favicons::create(); - $headElements[] = HtmlElement::link() - ->props('rel stylesheet', 'href /css/main.css'); - // $headElements[] = HtmlElement::script()->props('src /js/menu.js'); - - $body = HtmlDocument::create($content->title()) - ->head(...$headElements) - ->body( - Navigation::create($this->content())->build(), - $content->html() - )->build(); - - return Response::create( - status: $status, - headers: $headers, - body: $body - ); - } - - private function isRequestingFile(): bool - { - // Informal check, because I don't need to be defensive and account for - // a URL request path with a period in it - I'll only use hyphens. - return strpos($this->requestUri(), '.') > 0; - } - - private function isRedirecting(): bool - { - return strlen($this->content()->redirectPath()) > 0; - } - - private function environment(): Environment - { - return $this->environment; - } - - private function localFilePathWithoutRoot(): string - { - if ($this->isRequestingFile()) { - $parts = explode('/', $this->requestUri()); - $parts = array_filter($parts); - $first = array_shift($parts); - $search = '/' . $first; - - if (array_key_exists($search, self::HIDDEN)) { - $replace = self::HIDDEN[$search]; - - return str_replace($search, $replace, $this->requestUri()); - } - } - return $this->requestUri() . '/content.md'; - } - - private function requestDomain(): string - { - $scheme = $_SERVER['REQUEST_SCHEME']; - $serverName = $_SERVER['HTTP_HOST']; - return $scheme . '://' . $serverName; - } - - private function requestMethod(): string - { - return strtolower($_SERVER['REQUEST_METHOD']); - } - - private function requestUri(): string - { - return $_SERVER['REQUEST_URI']; - } -} diff --git a/src/Content.php b/src/Content.php index 84a3be4d..6e3ed209 100644 --- a/src/Content.php +++ b/src/Content.php @@ -17,12 +17,7 @@ class Content private string $markdown = ''; /** - * @var Markdown - */ - private $markdownConverter; - - /** - * @var array + * @var array */ private array $frontMatter = []; @@ -45,6 +40,11 @@ public function __construct( ) { } + public function folderDoesExist(): bool + { + return file_exists($this->root()) and is_dir($this->root()); + } + public function for(string $path): Content { $this->path = $path; @@ -56,71 +56,14 @@ public function for(string $path): Content return $this; } - public function exists(): bool - { - return file_exists($this->filePath()); - } - public function notFound(): bool { return ! $this->exists(); } - public function path(): string - { - return $this->path; - } - - /** - * @return array - */ - public function frontMatter(): array - { - if (count($this->frontMatter) === 0) { - $markdown = ''; - if (strlen($this->markdown) === 0) { - $markdown = $this->markdown(); - } - - $this->frontMatter = $this->markdownConverter() - ->getFrontMatter($markdown); - } - return $this->frontMatter; - } - - public function title(): string - { - if ($this->exists()) { - $fm = $this->frontMatter(); - $title = $fm['title']; - if (is_string($title)) { - return $title; - } - } - return ''; - } - - public function redirectPath(): string - { - $fm = $this->frontMatter(); - if ( - array_key_exists('redirect', $fm) and - $redirect = $fm['redirect'] and - is_string($redirect) - ) { - return $redirect; - } - return ''; - } - - public function html(): string - { - return $this->markdownConverter()->convert($this->markdown()); - } - public function filePath(): string { - return $this->root() . $this->path(); + return $this->root() . $this->path; } public function mimetype(): string @@ -145,7 +88,23 @@ public function mimetype(): string return $type; } - private function markdown(): string + /** + * @return array + */ + public function frontMatter(): array + { + if (count($this->frontMatter) === 0) { + $markdown = ''; + if (strlen($this->markdown) === 0) { + $markdown = $this->markdown(); + } + + $this->frontMatter = Markdown::create()->getFrontMatter($markdown); + } + return $this->frontMatter; + } + + public function markdown(): string { if (strlen($this->markdown) === 0 and $this->exists()) { $markdown = file_get_contents($this->filePath()); @@ -159,23 +118,34 @@ private function markdown(): string return $this->markdown; } - public function isValid(): bool + public function redirectPath(): string { - return file_exists($this->root()) and is_dir($this->root()); + $fm = $this->frontMatter(); + if ( + array_key_exists('redirect', $fm) and + $redirect = $fm['redirect'] and + is_string($redirect) + ) { + return $redirect; + } + return ''; } - public function root(): string + private function exists(): bool { - if (strlen($this->root) === 0) { - $contentStart = $this->projectRoot(); + return file_exists($this->filePath()); + } - $contentParts = explode('/', $contentStart); - $contentUp = $this->contentUp(); + private function root(): string + { + if (strlen($this->root) === 0) { + $contentParts = explode('/', $this->projectRoot); + $contentUp = $this->contentUp; if (is_int($contentUp) and $contentUp > 0) { $contentParts = array_slice($contentParts, 0, -1 * $contentUp); } - $contentFolder = explode('/', $this->contentFolder()); + $contentFolder = explode('/', $this->contentFolder); $contentFolder = array_filter($contentFolder); $contentParts = array_merge($contentParts, $contentFolder); @@ -183,30 +153,4 @@ public function root(): string } return $this->root; } - - private function projectRoot(): string - { - return $this->projectRoot; - } - - private function contentUp(): int - { - return $this->contentUp; - } - - private function contentFolder(): string - { - return $this->contentFolder; - } - - private function markdownConverter(): Markdown - { - if (! isset($this->markdownConverter)) { - $this->markdownConverter = Markdown::create() - ->minified() - ->smartPunctuation() - ->tables(); - } - return $this->markdownConverter; - } } diff --git a/src/Emitter.php b/src/Emitter.php index 65eafb16..b111a01e 100644 --- a/src/Emitter.php +++ b/src/Emitter.php @@ -4,11 +4,40 @@ namespace JoshBruce\Site; +use Nyholm\Psr7\Factory\Psr17Factory as PsrFactory; use Nyholm\Psr7\Response as PsrResponse; use Laminas\HttpHandlerRunner\Emitter\SapiStreamEmitter as PsrEmitter; class Emitter { + /** + * @param array $headers [description] + */ + public static function emitWithResponse( + int $status, + array $headers, + string $body = '' + ): void { + $factory = new PsrFactory(); + $stream = $factory->createStream($body); + $response = new PsrResponse($status, $headers, $stream); + self::emit($response); + } + + /** + * @param array $headers [description] + */ + public static function emitWithResponseFile( + int $status, + array $headers, + string $file + ): void { + $factory = new PsrFactory(); + $stream = $factory->createStreamFromFile($file); + $response = new PsrResponse($status, $headers, $stream); + self::emit($response); + } + public static function emit(PsrResponse $response): void { $emitter = new PsrEmitter(); diff --git a/src/Environment.php b/src/Environment.php deleted file mode 100644 index 45c39152..00000000 --- a/src/Environment.php +++ /dev/null @@ -1,90 +0,0 @@ -content()->isValid()) { - return Response::create(status: 200); - } - - // Custom content instance required - // - // This somewhat unreadable one-liner basically creates a fully qualified - // path to the root of the project, without using relative syntax - $projectRoot = implode('/', array_slice(explode('/', __DIR__), 0, -1)); - - $content = Content::init($projectRoot, 0, '/500-errors')->for('/502.md'); - return Response::create( - status: 502, - headers: [ - 'Cache-Control' => [ - 'no-cache', - 'must-revalidate' - ] - ], - body: HtmlDocument::create( - $content->title() - )->body( - $content->html() - )->build() - ); - } - - public function content(): Content - { - if ($this->content === null) { - $this->content = Content::init( - $this->projectRoot(), - $this->server()->contentUp(), - $this->server()->contentFolder() - ); - } - return $this->content; - } - - private function projectRoot(): string - { - if (strlen($this->projectRoot) === 0) { - $start = __DIR__; - $parts = explode('/', $start); - $parts = array_slice($parts, 0, -1); - $this->projectRoot = implode('/', $parts); - } - return $this->projectRoot; - } - - private function server(): Server - { - return $this->server; - } -} diff --git a/src/Response.php b/src/Response.php deleted file mode 100644 index cec70e64..00000000 --- a/src/Response.php +++ /dev/null @@ -1,82 +0,0 @@ -|string> $headers - */ - public static function create( - int $status = 200, - array $headers = [], - string $body = '' - ): Response { - return new Response($status, $headers, $body); - } - - /** - * @param array|string> $headers - */ - public function __construct( - private int $status = 200, - private array $headers = [], - private string $body = '' - ) { - } - - private function psrResponse(): PsrResponse - { - if ($this->psrResponse === null) { - $factory = new PsrFactory(); - $stream = $factory->createStream($this->body); - - $this->psrResponse = new PsrResponse( - status: $this->status, - headers: $this->headers, - body: $stream, - version: '2' - ); - } - return $this->psrResponse; - } - - public function getStatusCode(): int - { - return $this->psrResponse()->getStatusCode(); - } - - public function isOk(): bool - { - return $this->getStatusCode() === 200; - } - - public function getBody(): string - { - return $this->body; - } - - public function emit(): void - { - Emitter::emit($this->psrResponse()); - } -} diff --git a/src/ResponseFile.php b/src/ResponseFile.php deleted file mode 100644 index 3e5d1820..00000000 --- a/src/ResponseFile.php +++ /dev/null @@ -1,77 +0,0 @@ -|string> $headers - * - */ - public static function create( - int $status = 200, - array $headers = [], - string $file = '' - ): ResponseFile { - return new ResponseFile($status, $headers, $file); - } - - /** - * @param array|string> $headers - */ - public function __construct( - private int $status = 200, - private array $headers = [], - private string $file = '' - ) { - } - - private function psrResponse(): PsrResponse - { - if ($this->psrResponse === null) { - $factory = new PsrFactory(); - $stream = $factory->createStreamFromFile($this->file); - $this->psrResponse = new PsrResponse( - status: $this->status, - headers: $this->headers, - body: $stream, - version: '2' - ); - } - return $this->psrResponse; - } - - public function getStatusCode(): int - { - return $this->psrResponse()->getStatusCode(); - } - - public function isOk(): bool - { - return $this->getStatusCode() === 200; - } - - public function emit(): void - { - Emitter::emit($this->psrResponse()); - } -} diff --git a/src/Server.php b/src/Server.php deleted file mode 100644 index 9241450e..00000000 --- a/src/Server.php +++ /dev/null @@ -1,132 +0,0 @@ - - */ -class Server implements ArrayAccess -{ - private const REQUIRED = [ - 'APP_ENV', - 'CONTENT_UP', - 'CONTENT_FOLDER' - ]; - - // private string $projectRoot = ''; - - /** - * @param array $serverGlobals - */ - public static function init(array $serverGlobals): Server - { - return new Server($serverGlobals); - } - - /** - * @param array $serverGlobals - */ - public function __construct(private array $serverGlobals) - { - } - - public function response(): Response - { - if ($this->hasRequiredValues()) { - return Response::create(status: 200); - } - - // Custom content instance required - // - // This somewhat unreadable one-liner basically creates a fully qualified - // path to the root of the project, without using relative syntax - $projectRoot = implode('/', array_slice(explode('/', __DIR__), 0, -1)); - - $content = Content::init($projectRoot, 0, '/500-errors')->for('/500.md'); - return Response::create( - status: 500, - headers: [ - 'Cache-Control' => [ - 'no-cache', - 'must-revalidate' - ] - ], - body: HtmlDocument::create( - $content->title() - )->body( - $content->html() - )->build() - ); - } - - public function contentUp(): int - { - return intval($this->offsetGet('CONTENT_UP')); - } - - public function contentFolder(): string - { - return strval($this->offsetGet('CONTENT_FOLDER')); - } - - // public function projectRoot(): string - // { - // if (strlen($this->projectRoot) === 0) { - // $start = __DIR__; - // $parts = explode('/', $start); - // $parts = array_slice($parts, 0, -1); - // $this->projectRoot = implode('/', $parts); - // } - // return $this->projectRoot; - // } - - private function hasRequiredValues(): bool - { - foreach (static::REQUIRED as $offset) { - if (! $this->offsetExists($offset)) { - return false; - } - } - return true; - } - - /** - * ArrayAccess methods - */ - public function offsetExists(mixed $offset): bool - { - return ( - is_string($offset) and - array_key_exists($offset, $this->serverGlobals) - ); - } - - public function offsetGet(mixed $offset): string|int - { - if ($this->offsetExists($offset)) { - return $this->serverGlobals[$offset]; - } - return ''; - } - - public function offsetSet(mixed $offset, mixed $value): void - { - } - - public function offsetUnset(mixed $offset): void - { - } -} diff --git a/tests/AppTest.php b/tests/App_.php similarity index 100% rename from tests/AppTest.php rename to tests/App_.php diff --git a/tests/ContentTest.php b/tests/ContentTest.php index 9e17d41a..685f70b3 100644 --- a/tests/ContentTest.php +++ b/tests/ContentTest.php @@ -35,5 +35,5 @@ 'text/html' ); - expect($this->baseContent->isValid())->toBeTrue(); + expect($this->baseContent->folderDoesExist())->toBeTrue(); })->group('content'); diff --git a/tests/EnvironmentTest.php b/tests/Environment_.php similarity index 100% rename from tests/EnvironmentTest.php rename to tests/Environment_.php diff --git a/tests/IndexTest.php b/tests/Index_.php similarity index 100% rename from tests/IndexTest.php rename to tests/Index_.php diff --git a/tests/ServerTest.php b/tests/Server_.php similarity index 100% rename from tests/ServerTest.php rename to tests/Server_.php