From 1732a61c9ef04094b840e899f2e21bc8a860f225 Mon Sep 17 00:00:00 2001 From: Chris Kankiewicz Date: Sat, 22 Feb 2020 23:24:50 -0700 Subject: [PATCH] Added debug mode to allow access to detailed error messages --- .env.example | 2 + app/config/app.php | 11 ++ app/src/Bootstrap/AppManager.php | 48 +++++-- app/src/Middlewares/WhoopsMiddleware.php | 72 ++++++++++ app/src/Providers/WhoopsProvider.php | 37 +++++ composer.json | 1 + composer.lock | 157 ++++++++++++++------- tests/Bootstrap/AppManangerTest.php | 19 +++ tests/Middlewares/WhoopsMiddlewareTest.php | 73 ++++++++++ 9 files changed, 364 insertions(+), 56 deletions(-) create mode 100644 app/src/Middlewares/WhoopsMiddleware.php create mode 100644 app/src/Providers/WhoopsProvider.php create mode 100644 tests/Middlewares/WhoopsMiddlewareTest.php diff --git a/.env.example b/.env.example index 11b1785..88e1e25 100644 --- a/.env.example +++ b/.env.example @@ -1,3 +1,5 @@ +DEBUG=false + DARK_MODE=false DISPLAY_READMES=true diff --git a/app/config/app.php b/app/config/app.php index dd48cfd..c82e345 100644 --- a/app/config/app.php +++ b/app/config/app.php @@ -3,6 +3,17 @@ use App\Support\Helpers; return [ + /** + * Enable application debugging and display error messages. + * + * !!! WARNING !!! + * It is recommended that debug remains OFF unless troubleshooting an issue. + * Leaving this enabled WILL cause leakage of sensitive server information. + * + * Default value: false + */ + 'debug' => Helpers::env('DEBUG'), + /** * Enable dark mode? * diff --git a/app/src/Bootstrap/AppManager.php b/app/src/Bootstrap/AppManager.php index b3440f0..ab1208c 100644 --- a/app/src/Bootstrap/AppManager.php +++ b/app/src/Bootstrap/AppManager.php @@ -2,11 +2,13 @@ namespace App\Bootstrap; +use App\Middlewares; use App\Providers; use DI\Bridge\Slim\Bridge; use DI\Container; use Invoker\CallableResolver; -use Middlewares; +use Middlewares as HttpMiddlewares; +use PHLAK\Config\Config; use Slim\App; use Tightenco\Collect\Support\Collection; @@ -17,6 +19,12 @@ class AppManager Providers\ConfigProvider::class, Providers\FinderProvider::class, Providers\TwigProvider::class, + Providers\WhoopsProvider::class, + ]; + + /** @const Array of application middlewares */ + protected const MIDDLEWARES = [ + Middlewares\WhoopsMiddleware::class ]; /** @var Container The applicaiton container */ @@ -26,7 +34,7 @@ class AppManager protected $callableResolver; /** - * Create a new Provider object. + * Create a new AppManager object. * * @param \DI\Container $container * @param \Invoker\CallableResolver $callableResolver @@ -46,13 +54,16 @@ class AppManager { $this->registerProviders(); $app = Bridge::create($this->container); - $app->add(new Middlewares\Expires([ - 'application/zip' => '+1 hour', - 'text/json' => '+1 hour', - ])); + $this->registerMiddlewares($app); - $errorMiddleware = $app->addErrorMiddleware(true, true, true); - $errorMiddleware->setDefaultErrorHandler(ErrorHandler::class); + $this->container->call(function (App $app, Config $config): void { + if (! $config->get('app.debug', false)) { + return; + } + + $errorMiddleware = $app->addErrorMiddleware(true, true, true); + $errorMiddleware->setDefaultErrorHandler(ErrorHandler::class); + }); return $app; } @@ -72,4 +83,25 @@ class AppManager } ); } + + /** + * Register application middlewares. + * + * @param \Slim\App $app + * + * @return void + */ + protected function registerMiddlewares(App $app): void + { + Collection::make(self::MIDDLEWARES)->each( + function (string $middleware) use ($app): void { + $app->add($middleware); + } + ); + + $app->add(new HttpMiddlewares\Expires([ + 'application/zip' => '+1 hour', + 'text/json' => '+1 hour', + ])); + } } diff --git a/app/src/Middlewares/WhoopsMiddleware.php b/app/src/Middlewares/WhoopsMiddleware.php new file mode 100644 index 0000000..531a244 --- /dev/null +++ b/app/src/Middlewares/WhoopsMiddleware.php @@ -0,0 +1,72 @@ +whoops = $whoops; + $this->pageHandler = $pageHandler; + $this->jsonHandler = $jsonHandler; + $this->config = $config; + } + + /** + * Invoke the WhoopseMiddleware class. + * + * @param \Psr\Http\Message\ServerRequestInterface $request + * @param \Psr\Http\Server\RequestHandlerInterface $handler + * + * @return \Psr\Http\Message\ResponseInterface + */ + public function __invoke(Request $request, RequestHandler $handler): ResponseInterface + { + $this->pageHandler->addDataTable('Application Config', $this->config->split('app')->toArray()); + $this->pageHandler->setPageTitle( + sprintf('%s • Directory Lister', $this->pageHandler->getPageTitle()) + ); + + $this->whoops->pushHandler($this->pageHandler); + + if (in_array('application/json', explode(',', $request->getHeaderLine('Accept')))) { + $this->whoops->pushHandler($this->jsonHandler); + } + + $this->whoops->register(); + + return $handler->handle($request); + } +} diff --git a/app/src/Providers/WhoopsProvider.php b/app/src/Providers/WhoopsProvider.php new file mode 100644 index 0000000..a612edc --- /dev/null +++ b/app/src/Providers/WhoopsProvider.php @@ -0,0 +1,37 @@ +container = $container; + } + + /** + * Initialize and register the Whoops component. + * + * @return void + */ + public function __invoke(): void + { + $this->container->set(PrettyPageHandler::class, new PrettyPageHandler); + $this->container->set(JsonResponseHandler::class, new JsonResponseHandler); + $this->container->set(RunInterface::class, new Run); + } +} diff --git a/composer.json b/composer.json index 20ef167..4e8d636 100644 --- a/composer.json +++ b/composer.json @@ -13,6 +13,7 @@ "php": ">=7.2", "ext-zip": "*", "erusev/parsedown-extra": "^0.8.1", + "filp/whoops": "^2.7", "middlewares/cache": "^2.0", "phlak/config": "^6.1", "php-di/php-di": "^6.0", diff --git a/composer.lock b/composer.lock index f1e6e78..e37f795 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": "871b5d48a53bf07bdac340934b41325c", + "content-hash": "1a4f0a5fb5a91dc494cb5479886f6001", "packages": [ { "name": "erusev/parsedown", @@ -151,6 +151,67 @@ ], "time": "2020-02-05T20:36:27+00:00" }, + { + "name": "filp/whoops", + "version": "2.7.1", + "source": { + "type": "git", + "url": "https://github.com/filp/whoops.git", + "reference": "fff6f1e4f36be0e0d0b84d66b413d9dcb0c49130" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/filp/whoops/zipball/fff6f1e4f36be0e0d0b84d66b413d9dcb0c49130", + "reference": "fff6f1e4f36be0e0d0b84d66b413d9dcb0c49130", + "shasum": "" + }, + "require": { + "php": "^5.5.9 || ^7.0", + "psr/log": "^1.0.1" + }, + "require-dev": { + "mockery/mockery": "^0.9 || ^1.0", + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0", + "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.6-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" + ], + "time": "2020-01-15T10:00:00+00:00" + }, { "name": "jeremeamia/superclosure", "version": "2.4.0", @@ -1043,6 +1104,53 @@ ], "time": "2018-10-30T17:12:04+00:00" }, + { + "name": "psr/log", + "version": "1.1.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "446d54b4cb6bf489fc9d75f55843658e6f25d801" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/446d54b4cb6bf489fc9d75f55843658e6f25d801", + "reference": "446d54b4cb6bf489fc9d75f55843658e6f25d801", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "Psr/Log/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "time": "2019-11-01T11:05:21+00:00" + }, { "name": "ralouphie/getallheaders", "version": "3.0.3", @@ -3765,53 +3873,6 @@ ], "time": "2019-01-08T18:20:26+00:00" }, - { - "name": "psr/log", - "version": "1.1.2", - "source": { - "type": "git", - "url": "https://github.com/php-fig/log.git", - "reference": "446d54b4cb6bf489fc9d75f55843658e6f25d801" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/446d54b4cb6bf489fc9d75f55843658e6f25d801", - "reference": "446d54b4cb6bf489fc9d75f55843658e6f25d801", - "shasum": "" - }, - "require": { - "php": ">=5.3.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.1.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Log\\": "Psr/Log/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interface for logging libraries", - "homepage": "https://github.com/php-fig/log", - "keywords": [ - "log", - "psr", - "psr-3" - ], - "time": "2019-11-01T11:05:21+00:00" - }, { "name": "psy/psysh", "version": "v0.9.12", diff --git a/tests/Bootstrap/AppManangerTest.php b/tests/Bootstrap/AppManangerTest.php index ed1994a..279e38b 100644 --- a/tests/Bootstrap/AppManangerTest.php +++ b/tests/Bootstrap/AppManangerTest.php @@ -3,7 +3,11 @@ namespace Tests\Bootstrap; use App\Bootstrap\AppManager; +use App\Middlewares; +use App\Providers; +use DI\Container; use Invoker\CallableResolver; +use Middlewares as HttpMiddlewares; use Slim\App; use Tests\TestCase; @@ -16,4 +20,19 @@ class AppManangerTest extends TestCase $this->assertInstanceOf(App::class, $app); } + + public function test_it_registeres_providers(): void + { + $callableResolver = $this->container->get(CallableResolver::class); + + $container = $this->createMock(Container::class); + $container->expects($this->atLeast(4))->method('call')->withConsecutive( + [$callableResolver->resolve(Providers\ConfigProvider::class)], + [$callableResolver->resolve(Providers\FinderProvider::class)], + [$callableResolver->resolve(Providers\TwigProvider::class)], + [$callableResolver->resolve(Providers\WhoopsProvider::class)], + ); + + (new AppManager($container, $callableResolver))(); + } } diff --git a/tests/Middlewares/WhoopsMiddlewareTest.php b/tests/Middlewares/WhoopsMiddlewareTest.php new file mode 100644 index 0000000..be8192c --- /dev/null +++ b/tests/Middlewares/WhoopsMiddlewareTest.php @@ -0,0 +1,73 @@ +createMock(PrettyPageHandler::class); + $pageHandler->expects($this->once())->method('getPageTitle')->willReturn( + 'Test title; please ignore' + ); + $pageHandler->expects($this->once())->method('setPageTitle')->with( + 'Test title; please ignore • Directory Lister' + ); + $pageHandler->expects($this->once())->method('addDataTable')->with( + 'Application Config', $this->config->split('app')->toArray() + ); + + $whoops = $this->createMock(RunInterface::class); + $whoops->expects($this->once())->method('pushHandler')->with( + $pageHandler + ); + + $middleware = new WhoopsMiddleware( + $whoops, $pageHandler, new JsonResponseHandler, $this->config + ); + + $middleware( + $this->createMock(ServerRequestInterface::class), + $this->createMock(RequestHandlerInterface::class) + ); + } + + public function test_it_registers_whoops_with_the_json_handler(): void + { + $pageHandler = $this->createMock(PrettyPageHandler::class); + $pageHandler->expects($this->once())->method('getPageTitle')->willReturn( + 'Test title; please ignore' + ); + $pageHandler->expects($this->once())->method('setPageTitle')->with( + 'Test title; please ignore • Directory Lister' + ); + $pageHandler->expects($this->once())->method('addDataTable')->with( + 'Application Config', $this->config->split('app')->toArray() + ); + + $jsonHandler = new JsonResponseHandler; + + $whoops = $this->createMock(RunInterface::class); + $whoops->expects($this->exactly(2))->method('pushHandler')->withConsecutive( + [$pageHandler], + [$jsonHandler] + ); + + $middleware = new WhoopsMiddleware( + $whoops, $pageHandler, $jsonHandler, $this->config + ); + + $request = $this->createMock(ServerRequestInterface::class); + $request->expects($this->once())->method('getHeaderLine')->willReturn('application/json'); + + $middleware($request, $this->createMock(RequestHandlerInterface::class)); + } +}