From e68b26f77871d0ad12fdaff666d4aa7fbdc1dd32 Mon Sep 17 00:00:00 2001 From: Josh Bruce Date: Wed, 27 Oct 2021 09:14:29 -0500 Subject: [PATCH 01/11] 500 & 502 in place - deleted Server and Environment --- public/index.php | 138 +++++++++++++++++++++++++++++++++++++++++++- src/App.php | 7 ++- src/Content.php | 2 +- src/Emitter.php | 12 ++++ src/Environment.php | 90 ----------------------------- src/Server.php | 132 ------------------------------------------ 6 files changed, 152 insertions(+), 229 deletions(-) delete mode 100644 src/Environment.php delete mode 100644 src/Server.php diff --git a/public/index.php b/public/index.php index 7099503d..da89366c 100644 --- a/public/index.php +++ b/public/index.php @@ -1,12 +1,144 @@ minified() + ->smartPunctuation(); + +// Inject environment variables to global $_SERVER array +Dotenv\Dotenv::createImmutable(__DIR__ . '/../')->load(); + +$projectRoot = implode('/', array_slice(explode('/', __DIR__), 0, -1)); + ini_set('display_errors', '1'); ini_set('display_startup_errors', '1'); error_reporting(E_ALL); -require __DIR__ . '/../vendor/autoload.php'; +/** + * Verifying setup is valid. + */ +$requestRequiredServerGlobals = [ + 'APP_ENV', + 'CONTENT_UP', + 'CONTENT_FOLDER' +]; -// Inject environment variables to global $_SERVER array -Dotenv\Dotenv::createImmutable(__DIR__ . '/../')->load(); +// 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; +} + +/** + * 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->isValid()) { + $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; +} + +/** + * Bootsrap is complete: local response time 19ms + * + * Does the requested content exist? + */ + +$requestUri = $_SERVER['REQUEST_URI']; + +// TESTING +// $requestUri = '/does/not/exist'; +$localFilePath = $requestUri . '/content.md'; + +$requestIsForFile = strpos('.', $requestUri) > 0; +if ($requestIsForFile) { + die('requesting html'); +} + + + + + + +$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( + $content->title() + )->body( + $content->html() + )->build() + ); + exit; +} +// $requestContent = $content->for() + + + + +die(var_dump($requestHasRequiredServerGlobals)); $server = JoshBruce\Site\Server::init($_SERVER); diff --git a/src/App.php b/src/App.php index 2cf28769..44162db9 100644 --- a/src/App.php +++ b/src/App.php @@ -48,9 +48,10 @@ public function response(): Response|ResponseFile $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. + // MANUAL: Our automated 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 diff --git a/src/Content.php b/src/Content.php index 84a3be4d..aa8bc6a4 100644 --- a/src/Content.php +++ b/src/Content.php @@ -145,7 +145,7 @@ public function mimetype(): string return $type; } - private function markdown(): string + public function markdown(): string { if (strlen($this->markdown) === 0 and $this->exists()) { $markdown = file_get_contents($this->filePath()); diff --git a/src/Emitter.php b/src/Emitter.php index 65eafb16..978c5abd 100644 --- a/src/Emitter.php +++ b/src/Emitter.php @@ -4,11 +4,23 @@ 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 { + 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); + } 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/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 - { - } -} From 2a6f920873cbf39bdbeaefefba6e9c8dcaeef862 Mon Sep 17 00:00:00 2001 From: Josh Bruce Date: Wed, 27 Oct 2021 09:37:32 -0500 Subject: [PATCH 02/11] can respond with file --- public/index.php | 50 ++++++++++++++++++++++++++++++++++++------------ src/Emitter.php | 13 +++++++++++++ 2 files changed, 51 insertions(+), 12 deletions(-) diff --git a/public/index.php b/public/index.php index da89366c..90f09eac 100644 --- a/public/index.php +++ b/public/index.php @@ -97,22 +97,36 @@ * * Does the requested content exist? */ - $requestUri = $_SERVER['REQUEST_URI']; +if ($requestUri === '/') { + $requestUri = ''; +} // TESTING -// $requestUri = '/does/not/exist'; -$localFilePath = $requestUri . '/content.md'; - -$requestIsForFile = strpos('.', $requestUri) > 0; -if ($requestIsForFile) { - die('requesting html'); -} +// $requestUri = '/does/not/exist'; // 404 +// $requestUri = '/assets/favicons/favicon-16x16.png'; // file +$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); + $search = '/' . $first; + if (array_key_exists($search, $folderMap)) { + $replace = $folderMap[$search]; + $localFilePath = str_replace($search, $replace, $requestUri); + } +} $content = $content->for($localFilePath); if ($content->notFound()) { @@ -133,11 +147,23 @@ ); exit; } -// $requestContent = $content->for() - - - +/** + * 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; +} die(var_dump($requestHasRequiredServerGlobals)); $server = JoshBruce\Site\Server::init($_SERVER); diff --git a/src/Emitter.php b/src/Emitter.php index 978c5abd..4e68e513 100644 --- a/src/Emitter.php +++ b/src/Emitter.php @@ -21,6 +21,19 @@ public static function emitWithResponse( $response = new PsrResponse($status, $headers, $stream); self::emit($response); } + + public static function emitWithResponseFile( + int $status, + array $headers, + string $file + ) + { + $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(); From 7660142a0155f8a6a12acf8db349aadefd8a89f0 Mon Sep 17 00:00:00 2001 From: Josh Bruce Date: Wed, 27 Oct 2021 10:42:10 -0500 Subject: [PATCH 03/11] Update index.php --- public/index.php | 61 ++++++++++++++++++++++++++++++++++-------------- 1 file changed, 43 insertions(+), 18 deletions(-) diff --git a/public/index.php b/public/index.php index 90f09eac..61e4094a 100644 --- a/public/index.php +++ b/public/index.php @@ -25,7 +25,10 @@ $requestRequiredServerGlobals = [ 'APP_ENV', 'CONTENT_UP', - 'CONTENT_FOLDER' + 'CONTENT_FOLDER', + 'REQUEST_SCHEME', + 'HTTP_HOST', + 'REQUEST_URI' ]; // TESTING @@ -104,7 +107,13 @@ // 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; @@ -164,27 +173,43 @@ ); exit; } -die(var_dump($requestHasRequiredServerGlobals)); - -$server = JoshBruce\Site\Server::init($_SERVER); /** - * We'll probably replace with the middleware pattern from PSR-7. - * - * 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. + * Target file wants to redirect us: local response time 40ms */ -$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(); - } +$redirectPath = $content->redirectPath(); +if (strlen($redirectPath) > 0) { + $scheme = $_SERVER['REQUEST_SCHEME']; + $serverName = $_SERVER['HTTP_HOST']; + $requestDomain = $scheme . '://' . $serverName; + JoshBruce\Site\Emitter::emitWithResponse( + 301, + [ + 'Referrer' => $requestDomain . $requestUri, + 'Location' => $requestDomain . $redirectPath + ] + ); + exit; } -$response->emit(); +/** + * Process HTML response: local response time 75ms + */ + +$headers['Content-Type'] = $content->mimeType(); + +$headElements = JoshBruce\Site\PageComponents\Favicons::create(); +$headElements[] = Eightfold\HTMLBuilder\Element::link() + ->props('rel stylesheet', 'href /css/main.css'); +// $headElements[] = HtmlElement::script()->props('src /js/menu.js'); + +$body = Eightfold\HTMLBuilder\Document::create( + $markdownConverter->getFrontMatter($content->markdown())['title'] + )->body( + JoshBruce\Site\PageComponents\Navigation::create($content) + ->build(), + $markdownConverter->convert($content->markdown()) + )->build(); +JoshBruce\Site\Emitter::emitWithResponse(200, $headers, $body); exit; From 84ee92e974f225ba9d02eb71e81793082e29f195 Mon Sep 17 00:00:00 2001 From: Josh Bruce Date: Wed, 27 Oct 2021 10:43:31 -0500 Subject: [PATCH 04/11] deleting unused files --- src/App.php | 161 ------------------------------------------- src/Response.php | 82 ---------------------- src/ResponseFile.php | 77 --------------------- 3 files changed, 320 deletions(-) delete mode 100644 src/App.php delete mode 100644 src/Response.php delete mode 100644 src/ResponseFile.php diff --git a/src/App.php b/src/App.php deleted file mode 100644 index 44162db9..00000000 --- a/src/App.php +++ /dev/null @@ -1,161 +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 automated 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/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()); - } -} From 7d0813ad2b11d0b7ce2b830910f7a90a9c5c3816 Mon Sep 17 00:00:00 2001 From: Josh Bruce Date: Wed, 27 Oct 2021 17:38:08 -0500 Subject: [PATCH 05/11] add error handling --- composer.json | 3 +- composer.lock | 244 +++++++++++++++++++++++------------------------ error.log | 10 ++ public/index.php | 23 +++-- 4 files changed, 150 insertions(+), 130 deletions(-) create mode 100644 error.log 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/error.log b/error.log new file mode 100644 index 00000000..62238d5c --- /dev/null +++ b/error.log @@ -0,0 +1,10 @@ +Array +( +) +Array +( +) +Array +( +) +LoggingLogging \ No newline at end of file diff --git a/public/index.php b/public/index.php index 61e4094a..dd484c32 100644 --- a/public/index.php +++ b/public/index.php @@ -1,4 +1,8 @@ minified() - ->smartPunctuation(); + ->minified() + ->smartPunctuation(); // Inject environment variables to global $_SERVER array Dotenv\Dotenv::createImmutable(__DIR__ . '/../')->load(); $projectRoot = implode('/', array_slice(explode('/', __DIR__), 0, -1)); -ini_set('display_errors', '1'); -ini_set('display_startup_errors', '1'); -error_reporting(E_ALL); - /** * Verifying setup is valid. */ @@ -62,6 +62,11 @@ exit; } +if ($_SERVER['APP_ENV'] !== 'production') { + (new Whoops\Run)->pushHandler(new Whoops\Handler\PrettyPageHandler) + ->register(); +} + /** * Verifying specified content area exists. */ @@ -193,9 +198,11 @@ } /** - * Process HTML response: local response time 75ms + * 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(); @@ -205,6 +212,8 @@ $body = Eightfold\HTMLBuilder\Document::create( $markdownConverter->getFrontMatter($content->markdown())['title'] + )->head( + ...$headElements )->body( JoshBruce\Site\PageComponents\Navigation::create($content) ->build(), From 7852099e2e2eb7d78911b41474bd4578bb3b2fb0 Mon Sep 17 00:00:00 2001 From: Josh Bruce Date: Wed, 27 Oct 2021 19:15:40 -0500 Subject: [PATCH 06/11] appreciated what I learned here --- public/index.php | 17 ++++---- src/Content.php | 109 ++++++++++------------------------------------- src/Emitter.php | 8 ++-- 3 files changed, 35 insertions(+), 99 deletions(-) diff --git a/public/index.php b/public/index.php index dd484c32..26b6fc81 100644 --- a/public/index.php +++ b/public/index.php @@ -1,7 +1,7 @@ isValid()) { +if (! $content->folderDoesExist()) { $content = JoshBruce\Site\Content::init($projectRoot, 0, '/500-errors') ->for('/502.md'); @@ -130,10 +130,11 @@ '/assets' => '/.assets' ]; - $parts = explode('/', $requestUri); - $parts = array_filter($parts); - $first = array_shift($parts); - $search = '/' . $first; + $parts = explode('/', $requestUri); + $parts = array_filter($parts); + $first = array_shift($parts); + + $folderMapKey = '/' . $first; if (array_key_exists($search, $folderMap)) { $replace = $folderMap[$search]; @@ -190,7 +191,6 @@ JoshBruce\Site\Emitter::emitWithResponse( 301, [ - 'Referrer' => $requestDomain . $requestUri, 'Location' => $requestDomain . $redirectPath ] ); @@ -208,7 +208,6 @@ $headElements = JoshBruce\Site\PageComponents\Favicons::create(); $headElements[] = Eightfold\HTMLBuilder\Element::link() ->props('rel stylesheet', 'href /css/main.css'); -// $headElements[] = HtmlElement::script()->props('src /js/menu.js'); $body = Eightfold\HTMLBuilder\Document::create( $markdownConverter->getFrontMatter($content->markdown())['title'] diff --git a/src/Content.php b/src/Content.php index aa8bc6a4..fa7d2a2b 100644 --- a/src/Content.php +++ b/src/Content.php @@ -14,18 +14,6 @@ class Content private string $path = '/'; - private string $markdown = ''; - - /** - * @var Markdown - */ - private $markdownConverter; - - /** - * @var array - */ - private array $frontMatter = []; - public static function init( string $projectRoot, int $contentUp, @@ -45,6 +33,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,21 +49,11 @@ 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 */ @@ -82,22 +65,23 @@ public function frontMatter(): array $markdown = $this->markdown(); } - $this->frontMatter = $this->markdownConverter() - ->getFrontMatter($markdown); + $this->frontMatter = Markdown::create()->getFrontMatter($markdown); } return $this->frontMatter; } - public function title(): string + public function markdown(): string { - if ($this->exists()) { - $fm = $this->frontMatter(); - $title = $fm['title']; - if (is_string($title)) { - return $title; + if (strlen($this->markdown) === 0 and $this->exists()) { + $markdown = file_get_contents($this->filePath()); + + if (is_bool($markdown)) { + $markdown = ''; } + + $this->markdown = $markdown; } - return ''; + return $this->markdown; } public function redirectPath(): string @@ -113,16 +97,6 @@ public function redirectPath(): string return ''; } - public function html(): string - { - return $this->markdownConverter()->convert($this->markdown()); - } - - public function filePath(): string - { - return $this->root() . $this->path(); - } - public function mimetype(): string { $type = mime_content_type($this->filePath()); @@ -145,37 +119,26 @@ public function mimetype(): string return $type; } - public function markdown(): string + private function exists(): bool { - if (strlen($this->markdown) === 0 and $this->exists()) { - $markdown = file_get_contents($this->filePath()); - - if (is_bool($markdown)) { - $markdown = ''; - } - - $this->markdown = $markdown; - } - return $this->markdown; + return file_exists($this->filePath()); } - public function isValid(): bool + private function filePath(): string { - return file_exists($this->root()) and is_dir($this->root()); + return $this->root() . $this->path; } - public function root(): string + private function root(): string { if (strlen($this->root) === 0) { - $contentStart = $this->projectRoot(); - - $contentParts = explode('/', $contentStart); - $contentUp = $this->contentUp(); + $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 +146,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 4e68e513..a0c22f67 100644 --- a/src/Emitter.php +++ b/src/Emitter.php @@ -16,8 +16,8 @@ public static function emitWithResponse( string $body = '' ): void { - $factory = new PsrFactory(); - $stream = $factory->createStream($body); + $factory = new PsrFactory(); + $stream = $factory->createStream($body); $response = new PsrResponse($status, $headers, $stream); self::emit($response); } @@ -28,8 +28,8 @@ public static function emitWithResponseFile( string $file ) { - $factory = new PsrFactory(); - $stream = $factory->createStreamFromFile($file); + $factory = new PsrFactory(); + $stream = $factory->createStreamFromFile($file); $response = new PsrResponse($status, $headers, $stream); self::emit($response); } From c79b1eaefd8d4d1c0370d6fae6e3d034187bde75 Mon Sep 17 00:00:00 2001 From: Josh Bruce Date: Wed, 27 Oct 2021 19:16:51 -0500 Subject: [PATCH 07/11] Delete error.log --- error.log | 10 ---------- 1 file changed, 10 deletions(-) delete mode 100644 error.log diff --git a/error.log b/error.log deleted file mode 100644 index 62238d5c..00000000 --- a/error.log +++ /dev/null @@ -1,10 +0,0 @@ -Array -( -) -Array -( -) -Array -( -) -LoggingLogging \ No newline at end of file From 3343673a17b29133f0700ba2629c5ccc5c1ac8ff Mon Sep 17 00:00:00 2001 From: Josh Bruce Date: Wed, 27 Oct 2021 19:18:29 -0500 Subject: [PATCH 08/11] Update index.php --- public/index.php | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/public/index.php b/public/index.php index 26b6fc81..5c83b6d9 100644 --- a/public/index.php +++ b/public/index.php @@ -1,7 +1,7 @@ pushHandler(new Whoops\Handler\PrettyPageHandler) - ->register(); + $erroHandler = new Whoops\Run; + $erroHandler->pushHandler(new Whoops\Handler\PrettyPageHandler); + $erroHandler->register(); } /** From 904da499a1aab8cf6085fbb7e8dff7bb6c3cfae8 Mon Sep 17 00:00:00 2001 From: Josh Bruce Date: Wed, 27 Oct 2021 19:26:39 -0500 Subject: [PATCH 09/11] stan and style --- public/index.php | 10 ++++---- src/Content.php | 61 +++++++++++++++++++++++++++--------------------- src/Emitter.php | 12 ++++++---- 3 files changed, 47 insertions(+), 36 deletions(-) diff --git a/public/index.php b/public/index.php index 5c83b6d9..8a43411f 100644 --- a/public/index.php +++ b/public/index.php @@ -137,10 +137,10 @@ $folderMapKey = '/' . $first; - if (array_key_exists($search, $folderMap)) { - $replace = $folderMap[$search]; + if (array_key_exists($folderMapKey, $folderMap)) { + $replace = $folderMap[$folderMapKey]; - $localFilePath = str_replace($search, $replace, $requestUri); + $localFilePath = str_replace($folderMapKey, $replace, $requestUri); } } @@ -156,9 +156,9 @@ ] ], Eightfold\HTMLBuilder\Document::create( - $content->title() + $markdownConverter->getFrontMatter($content->markdown())['title'] )->body( - $content->html() + $markdownConverter->convert($content->markdown()) )->build() ); exit; diff --git a/src/Content.php b/src/Content.php index fa7d2a2b..6e3ed209 100644 --- a/src/Content.php +++ b/src/Content.php @@ -14,6 +14,13 @@ class Content private string $path = '/'; + private string $markdown = ''; + + /** + * @var array + */ + private array $frontMatter = []; + public static function init( string $projectRoot, int $contentUp, @@ -54,6 +61,33 @@ public function notFound(): bool return ! $this->exists(); } + public function filePath(): string + { + return $this->root() . $this->path; + } + + public function mimetype(): string + { + $type = mime_content_type($this->filePath()); + if (is_bool($type) and $type === false) { + return ''; + } + + if ($type === 'text/plain') { + $extensionMap = [ + 'md' => 'text/html', + 'css' => 'text/css', + 'js' => 'text/javascript' + ]; + + $parts = explode('.', $this->filePath()); + $extension = array_pop($parts); + + $type = $extensionMap[$extension]; + } + return $type; + } + /** * @return array */ @@ -97,38 +131,11 @@ public function redirectPath(): string return ''; } - public function mimetype(): string - { - $type = mime_content_type($this->filePath()); - if (is_bool($type) and $type === false) { - return ''; - } - - if ($type === 'text/plain') { - $extensionMap = [ - 'md' => 'text/html', - 'css' => 'text/css', - 'js' => 'text/javascript' - ]; - - $parts = explode('.', $this->filePath()); - $extension = array_pop($parts); - - $type = $extensionMap[$extension]; - } - return $type; - } - private function exists(): bool { return file_exists($this->filePath()); } - private function filePath(): string - { - return $this->root() . $this->path; - } - private function root(): string { if (strlen($this->root) === 0) { diff --git a/src/Emitter.php b/src/Emitter.php index a0c22f67..b111a01e 100644 --- a/src/Emitter.php +++ b/src/Emitter.php @@ -10,24 +10,28 @@ class Emitter { + /** + * @param array $headers [description] + */ public static function emitWithResponse( int $status, array $headers, string $body = '' - ): void - { + ): 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); From 0c1423f2f81cfd6253d2bbee23659f78945e8a84 Mon Sep 17 00:00:00 2001 From: Josh Bruce Date: Wed, 27 Oct 2021 20:05:53 -0500 Subject: [PATCH 10/11] Update ContentTest.php --- tests/ContentTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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'); From aea9bffa7d9133f3d457ab1f5cb86cbf21721649 Mon Sep 17 00:00:00 2001 From: Josh Bruce Date: Wed, 27 Oct 2021 20:08:52 -0500 Subject: [PATCH 11/11] rename deprecated test files --- tests/{AppTest.php => App_.php} | 0 tests/{EnvironmentTest.php => Environment_.php} | 0 tests/{IndexTest.php => Index_.php} | 0 tests/{ServerTest.php => Server_.php} | 0 4 files changed, 0 insertions(+), 0 deletions(-) rename tests/{AppTest.php => App_.php} (100%) rename tests/{EnvironmentTest.php => Environment_.php} (100%) rename tests/{IndexTest.php => Index_.php} (100%) rename tests/{ServerTest.php => Server_.php} (100%) 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/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