diff --git a/.env.example b/.env.example
index 62a5eb1..b58e560 100644
--- a/.env.example
+++ b/.env.example
@@ -6,9 +6,8 @@ APP_VERSION=1.0.0
# Environment
# -----------
-# Development and debug environment.
+# Development environment.
APP_ENV=development
-APP_DEBUG=true
# URL
# ---
@@ -39,4 +38,4 @@ FALLBACK_LOCALE=en
# LOG_CHANNEL: name usually used in Monolog, see file config/container.php, at the ErrorHandlerMiddleware definition.
# LOG_LEVEL: the minimum logging level at which the handler will be triggered, MUST be one of Level::class constant from Monolog.
LOG_CHANNEL=app
-LOG_LEVEL=Debug
\ No newline at end of file
+LOG_LEVEL=Debug
diff --git a/.gitignore b/.gitignore
index 731f1ce..e2445b8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -10,6 +10,7 @@ composer.phar
### Cache ###
/storage/cache/views/*.php
/storage/cache/logs/*.log
+/storage/database.sqlite
# Commit your application's lock file https://getcomposer.org/doc/01-basic-usage.md#commit-your-composer-lock-file-to-version-control
# You may choose to ignore a library lock file http://getcomposer.org/doc/02-libraries.md#lock-file
diff --git a/README.md b/README.md
index 245b299..7eee3f3 100644
--- a/README.md
+++ b/README.md
@@ -12,7 +12,7 @@
Sometimes, you don't need an overkill solution like [Laravel](https://laravel.com/) or [Symfony](https://symfony.com/).
-Borsch is a simple and efficient [PSR-15](https://www.php-fig.org/psr/psr-15/) micro framework made to kick start your
+Borsch is a simple, real fast and efficient [PSR-15](https://www.php-fig.org/psr/psr-15/) micro framework made to kick-start your
web app or API development by using the tools you prefer, and provides minimal structure and facilities to ease your
development.
@@ -22,8 +22,7 @@ It natively features :
* [Router](https://github.com/borschphp/borsch-router)
* [Request Handlers and Middlewares](https://github.com/borschphp/borsch-requesthandler)
* [Environment Variables](https://github.com/vlucas/phpdotenv)
-* [Error Handling](https://github.com/borschphp/borsch-skeleton/blob/master/src/Middleware/ErrorHandlerMiddleware.php)
-* [Listeners](https://github.com/borschphp/borsch-skeleton/blob/master/src/Listener/MonologListener.php)
+* [Error Handling](https://github.com/borschphp/borsch-middlewares/blob/main/src/Middleware/ErrorHandlerMiddleware.php)
Can be enriched with :
@@ -41,7 +40,7 @@ Via [composer](https://getcomposer.org/) :
## Web servers
-Instructions below will start a server on http://0.0.0.0:8080.
+Instructions below will start a server on http://0.0.0.0:8080 (or https://localhost if you use FrankenPHP).
### PHP Built-in web server
@@ -74,6 +73,8 @@ docker run \
-v $PWD:/app \
-p 80:8080 -p 443:443 -p 443:443/udp \
dunglas/frankenphp
+# or use the shortcut
+composer franken
```
## Documentation
@@ -89,4 +90,4 @@ Do not hesitate to check [Mezzio](https://docs.mezzio.dev/mezzio/) and [Laravel]
## License
-The package is licensed under the MIT license. See [License File](https://github.com/borschphp/borsch-skeleton/blob/master/LICENSE.md) for more information.
\ No newline at end of file
+The package is licensed under the MIT license. See [License File](https://github.com/borschphp/borsch-skeleton/blob/master/LICENSE.md) for more information.
diff --git a/Skeletorfile.php b/Skeletorfile.php
index e7b8b91..e5b3fbe 100644
--- a/Skeletorfile.php
+++ b/Skeletorfile.php
@@ -14,27 +14,26 @@
if ($installation_type === 'MINIMAL') {
$skeletor->spin('Removing front handlers', function () use ($skeletor) {
- $skeletor->removeFile('src/Handler/HomeHandler.php');
- $skeletor->pregReplaceInFile(
- '/\s\$app->[\s\S].+/',
- '',
- 'config/routes.php'
- );
-
+ $skeletor->removeFile('src/Application/Handler/HomeHandler.php');
return true;
}, 'Removed front handlers', 'Unable to completely remove front handlers');
-
+ /* // For now, it only removes the handlers, not the templates (because used in other container definitions).
$skeletor->spin('Removing template files and configuration', function () use ($skeletor) {
- $skeletor->removeFile('config/containers/template.container.php');
$skeletor->pregReplaceInFile(
- '/\n[\s\S].+template[\s\S].+;/',
+ '/\n[\s\S].+TemplateRendererInterface[\s\S].+;/',
+ '',
+ 'config/container.php'
+ );
+ $skeletor->pregReplaceInFile(
+ '/\$engine->render(\'500.tpl\')/',
'',
'config/container.php'
);
$skeletor->removeFile('storage/views/404.tpl');
$skeletor->removeFile('storage/views/500.tpl');
$skeletor->removeFile('storage/views/home.tpl');
+ $skeletor->removeFile('storage/views/layout.tpl');
// There is an issue with `Skeletor::removeDirectory(string $filename);` because it internally uses `rmdir`
// which has a parameter named `$path` and not `$filename`.
// Because of the use of `get_defined_vars()`, `rmdir` receives a parameter named `$filename` instead of
@@ -56,6 +55,7 @@
return true;
}, 'Removing Latte template engine from composer.json', 'Unable to completely remove Latte template engine from composer.json');
+ */
$skeletor->spin('Creating environment file', function () use ($skeletor, $app_name) {
if (!$skeletor->exists('.env')) {
diff --git a/chef b/chef
deleted file mode 100755
index 3a4e4a7..0000000
--- a/chef
+++ /dev/null
@@ -1,9 +0,0 @@
-#!/usr/bin/php
-run();
diff --git a/composer.json b/composer.json
index 97a9773..45e754c 100644
--- a/composer.json
+++ b/composer.json
@@ -24,16 +24,16 @@
"ext-libxml": "*",
"ext-pdo": "*",
"ext-simplexml": "*",
- "borschphp/application": "^2",
- "borschphp/middlewares": "^1",
+ "borschphp/middlewares": "^2",
"borschphp/latte": "^1",
- "borschphp/chef": "^1",
- "laminas/laminas-diactoros": "^3",
+ "borschphp/requesthandler": "^2",
+ "borschphp/container": "^2",
+ "borschphp/router": "^3.1",
+ "borschphp/psr7": "^1.0",
+ "laminas/laminas-db": "^2.20",
"monolog/monolog": "^2 || ^3",
"vlucas/phpdotenv": "^v5.1",
- "league/container": "^4",
"zircote/swagger-php": "^5.0",
- "laminas/laminas-db": "^2.20",
"debuss-a/problem-details": "^1.0"
},
"require-dev": {
@@ -43,7 +43,9 @@
},
"autoload": {
"psr-4": {
- "App\\": "src/"
+ "Application\\": "src/Application/",
+ "Domain\\": "src/Domain/",
+ "Infrastructure\\": "src/Infrastructure/"
},
"files": [
"bootstrap/defines.inc.php",
@@ -53,7 +55,7 @@
},
"autoload-dev": {
"psr-4": {
- "AppTest\\": "tests/"
+ "Tests\\": "tests/"
}
},
"config": {
@@ -62,7 +64,6 @@
}
},
"scripts": {
- "pre-install-cmd": "App\\Package\\Installer::install",
"post-create-project-cmd": [
"NiftyCo\\Skeletor\\Runner::execute"
],
diff --git a/config/api.php b/config/api.php
deleted file mode 100644
index aee602e..0000000
--- a/config/api.php
+++ /dev/null
@@ -1,24 +0,0 @@
-group('/api', function (Application $app) {
- if (!isProduction()) {
- // Redoc (/swagger also available)
- $app->get('/openapi[.{format:json|yml|yaml}]', OpenApiHandler::class, 'openapi');
- $app->get('/{redoc:redoc|swagger}', RedocHandler::class);
- }
-
- $app->any('/albums[/{id:\d+}]', AlbumHandler::class, 'albums');
- $app->any('/artists[/{id:\d+}]', ArtistHandler::class, 'artists');
-
- // Health checks
- $app->get('/healthcheck', HealthCheckHandler::class, 'healthcheck');
- });
-};
diff --git a/config/container.php b/config/container.php
index 72d5d84..315e2c5 100644
--- a/config/container.php
+++ b/config/container.php
@@ -1,16 +1,16 @@
setCacheByDefault(true);
-$container->defaultToShared();
-$container->delegate(new ReflectionContainer(true));
-
-(require_once __DIR__.'/containers/app.container.php')($container);
-(require_once __DIR__.'/containers/logs.container.php')($container);
-(require_once __DIR__.'/containers/pipeline.container.php')($container);
-(require_once __DIR__.'/containers/template.container.php')($container);
-(require_once __DIR__.'/containers/database.container.php')($container);
+(require_once __DIR__ . '/containers/container.handler.php')($container);
+(require_once __DIR__ . '/containers/container.middlewares.php')($container);
+(require_once __DIR__ . '/containers/container.http.php')($container);
+(require_once __DIR__ . '/containers/container.routes.php')($container);
+(require_once __DIR__ . '/containers/container.logs.php')($container);
+(require_once __DIR__ . '/containers/container.views.php')($container);
+(require_once __DIR__ . '/containers/container.database.php')($container);
return $container;
diff --git a/config/containers/app.container.php b/config/containers/app.container.php
deleted file mode 100644
index 2d315f4..0000000
--- a/config/containers/app.container.php
+++ /dev/null
@@ -1,56 +0,0 @@
-addServiceProvider(new class extends AbstractServiceProvider {
-
- public function provides(string $id): bool
- {
- return in_array($id, [
- ApplicationInterface::class,
- RouterInterface::class,
- RequestHandlerInterface::class,
- ServerRequestInterface::class
- ]);
- }
-
- public function register(): void
- {
- $this
- ->getContainer()
- ->add(ApplicationInterface::class, Application::class)
- ->addArgument(RequestHandlerInterface::class)
- ->addArgument(RouterInterface::class)
- ->addArgument($this->getContainer());
-
- $this
- ->getContainer()
- ->add(RouterInterface::class, function () {
- $router = new FastRouteRouter();
- if (isProduction()) {
- $router->setCacheFile(cache_path('routes.cache.php'));
- }
-
- return $router;
- });
-
- $this
- ->getContainer()
- ->add(RequestHandlerInterface::class, RequestHandler::class);
-
- $this
- ->getContainer()
- ->add(ServerRequestInterface::class, fn() => ServerRequestFactory::fromGlobals())
- ->setShared(false);
- }
- });
-};
diff --git a/config/containers/container.database.php b/config/containers/container.database.php
new file mode 100644
index 0000000..ad6e044
--- /dev/null
+++ b/config/containers/container.database.php
@@ -0,0 +1,23 @@
+set(AdapterInterface::class, Adapter::class)
+ ->addParameter([
+ 'driver' => 'Pdo_Sqlite',
+ 'dsn' => 'sqlite:'.storage_path('database.sqlite')
+ ]);
+
+};
diff --git a/config/containers/container.handler.php b/config/containers/container.handler.php
new file mode 100644
index 0000000..1d72387
--- /dev/null
+++ b/config/containers/container.handler.php
@@ -0,0 +1,76 @@
+set(
+ RequestHandlerRunnerInterface::class,
+ static function (
+ RequestHandlerInterface $handler,
+ ServerRequestInterface $request,
+ TemplateRendererInterface $renderer,
+ ResponseFactoryInterface $factory
+ ) {
+ return new RequestHandlerRunner(
+ $handler,
+ new Emitter(),
+ static fn() => $request,
+ static function() use ($renderer, $factory) {
+ $response = $factory->createResponse(500);
+ $response->getBody()->write($renderer->render('500.tpl'));
+
+ return $response;
+ }
+ );
+ });
+
+ /*
+ * The RequestHandler is responsible for handling the request and returning a response.
+ *
+ * It is composed of several middlewares that will be executed in the order they are added (FIFO).
+ * Predefined middlewares are included to handle common tasks, feel free to add your own.
+ */
+ $container->set(RequestHandlerInterface::class, static function (ContainerInterface $container) {
+ return (new RequestHandler())
+ ->middleware($container->get(ErrorHandlerMiddleware::class))
+ ->middleware($container->get(ProblemDetailsMiddleware::class))
+ ->middleware($container->get(TrailingSlashMiddleware::class))
+ ->middleware($container->get(ContentLengthMiddleware::class))
+ ->middleware($container->get(RouteMiddleware::class))
+ ->middleware($container->get(ImplicitHeadMiddleware::class))
+ ->middleware($container->get(ImplicitOptionsMiddleware::class))
+ ->middleware($container->get(MethodNotAllowedMiddleware::class))
+ ->middleware($container->get(BodyParserMiddleware::class))
+ ->middleware($container->get(UploadedFilesParserMiddleware::class))
+ ->middleware($container->get(DispatchMiddleware::class))
+ ->middleware($container->get(NotFoundHandlerMiddleware::class));
+ });
+
+};
diff --git a/config/containers/container.http.php b/config/containers/container.http.php
new file mode 100644
index 0000000..9147664
--- /dev/null
+++ b/config/containers/container.http.php
@@ -0,0 +1,33 @@
+set(ServerRequestInterface::class, static function () {
+ $scheme = isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? 'https' : 'http';
+ $host = $_SERVER['HTTP_HOST'] ?? $_SERVER['SERVER_NAME'];
+ $uri = "$scheme://$host" . ($_SERVER['REQUEST_URI'] ?? '');
+
+ return (new ServerRequestFactory())->createServerRequest($_SERVER['REQUEST_METHOD'], $uri, $_SERVER);
+ })->cache(false);
+
+ /*
+ * Necessary factories for PSR-7, used in middlewares and controllers.
+ */
+ $container->set(UploadedFileFactoryInterface::class, UploadedFileFactory::class);
+ $container->set(StreamFactoryInterface::class, StreamFactory::class);
+ $container->set(ResponseFactoryInterface::class, ResponseFactory::class);
+
+};
diff --git a/config/containers/container.logs.php b/config/containers/container.logs.php
new file mode 100644
index 0000000..5a4a910
--- /dev/null
+++ b/config/containers/container.logs.php
@@ -0,0 +1,35 @@
+set(LoggerInterface::class, Logger::class);
+
+ /*
+ * A simple PSR-3 compliant logger instance.
+ *
+ * Logs are written to the `storage/logs/app.log` file in the application root by default.
+ * The log level and channel can be configured via environment variables.
+ */
+ $container->set(Logger::class, static function (): Logger {
+ $name = env('APP_NAME', 'App');
+
+ $handlers = [
+ new StreamHandler(
+ logs_path(env('LOG_CHANNEL', 'app').'.log'),
+ Level::fromName(env('LOG_LEVEL', 'Debug'))
+ )
+ ];
+
+ $processors = [new PsrLogMessageProcessor(removeUsedContextFields: true)];
+ $datetime_zone = new DateTimeZone(env('TIMEZONE', 'UTC'));
+
+ return new Logger($name, $handlers, $processors, $datetime_zone);
+ });
+
+};
diff --git a/config/containers/container.middlewares.php b/config/containers/container.middlewares.php
new file mode 100644
index 0000000..4132306
--- /dev/null
+++ b/config/containers/container.middlewares.php
@@ -0,0 +1,63 @@
+set(ErrorHandlerMiddleware::class, static fn(TemplateRendererInterface $renderer) => new ErrorHandlerMiddleware(
+ static function (Throwable $throwable, ServerRequestInterface $request) use ($renderer): ResponseInterface {
+ if (str_starts_with($request->getUri()->getPath(), '/api')) {
+ return new JsonResponse(new ProblemDetails(
+ type: '://problem/internal-server-error',
+ title: 'Internal server error.',
+ status: 500,
+ detail: $throwable->getMessage()
+ ), 500);
+ }
+
+ return new HtmlResponse(
+ $renderer->render('500.tpl'),
+ 500
+ );
+ }
+ ));
+
+ /*
+ * The NotFoundHandlerMiddleware is responsible for handling 404 Not Found errors.
+ *
+ * If the request is for an API endpoint, it returns a JSON response with a ProblemDetails object.
+ * Otherwise, it returns an HTML response with a 404 error page.
+ */
+ $container->set(NotFoundHandlerMiddleware::class, static function (TemplateRendererInterface $renderer) {
+ return new NotFoundHandlerMiddleware(static function (ServerRequestInterface $request) use ($renderer): ResponseInterface {
+ if (str_starts_with($request->getUri()->getPath(), '/api')) {
+ throw new ProblemDetailsException(new ProblemDetails(
+ type: '://problem/not-found',
+ title: 'Not found.',
+ status: 404,
+ detail: "The requested uri ({$request->getUri()->getPath()}) could not be found."
+ ));
+ }
+
+ return new HtmlResponse(
+ $renderer->render('404.tpl'),
+ 404
+ );
+ });
+ });
+
+};
diff --git a/config/containers/container.routes.php b/config/containers/container.routes.php
new file mode 100644
index 0000000..f052a98
--- /dev/null
+++ b/config/containers/container.routes.php
@@ -0,0 +1,55 @@
+set(AttributeRouteLoader::class, static function (ContainerInterface $container) {
+ $loader = new AttributeRouteLoader(
+ [__ROOT_DIR__ . '/src/Application'],
+ $container,
+ cache_path('loader.routes.cache.php'),
+ !isProduction()
+ );
+
+ return $loader->load();
+ });
+
+ /*
+ * Register the router service.
+ *
+ * In production, it uses a cached version of the routes for performance.
+ * In development, it loads routes directly from the source files.
+ */
+ $container->set(RouterInterface::class, static function (AttributeRouteLoader $loader) {
+ $routes = $loader->getRoutes();
+
+ if (isProduction()) {
+ return new FastRouteRouter(
+ array_combine(
+ array_map(fn(RouteInterface $route) => $route->getName(), $routes),
+ $routes
+ ),
+ cache_path('router.routes.cache.php')
+ );
+ }
+
+ $router = new FastRouteRouter();
+ foreach ($routes as $route) {
+ $router->addRoute($route);
+ }
+
+ return $router;
+ });
+
+};
diff --git a/config/containers/container.views.php b/config/containers/container.views.php
new file mode 100644
index 0000000..fb69c29
--- /dev/null
+++ b/config/containers/container.views.php
@@ -0,0 +1,19 @@
+set(
+ TemplateRendererInterface::class,
+ fn() => new LatteRenderer(storage_path('views'), cache_path('views'), !isProduction())
+ );
+
+};
diff --git a/config/containers/database.container.php b/config/containers/database.container.php
deleted file mode 100644
index 0d47b31..0000000
--- a/config/containers/database.container.php
+++ /dev/null
@@ -1,13 +0,0 @@
-add(AdapterInterface::class, Adapter::class)
- ->addArgument([
- 'driver' => 'Pdo_Sqlite',
- 'dsn' => 'sqlite:'.storage_path('database.sqlite')
- ]);
-};
diff --git a/config/containers/logs.container.php b/config/containers/logs.container.php
deleted file mode 100644
index ef85ec8..0000000
--- a/config/containers/logs.container.php
+++ /dev/null
@@ -1,22 +0,0 @@
-add(Logger::class, function (): Logger {
- $name = env('APP_NAME', 'App');
-
- $handlers = [
- new StreamHandler(
- logs_path(env('LOG_CHANNEL', 'app').'.log'),
- Level::fromName(env('LOG_LEVEL', 'Debug'))
- )
- ];
-
- $processors = [new PsrLogMessageProcessor(removeUsedContextFields: true)];
- $datetime_zone = new DateTimeZone(env('TIMEZONE', 'UTC'));
-
- return new Logger($name, $handlers, $processors, $datetime_zone);
- });
-};
diff --git a/config/containers/pipeline.container.php b/config/containers/pipeline.container.php
deleted file mode 100644
index e63886e..0000000
--- a/config/containers/pipeline.container.php
+++ /dev/null
@@ -1,74 +0,0 @@
-addServiceProvider(new class extends AbstractServiceProvider {
-
- public function provides(string $id): bool
- {
- return in_array($id, [
- FormatterInterface::class,
- ErrorHandlerMiddleware::class,
- NotFoundHandlerMiddleware::class,
- ProblemDetailsMiddleware::class,
- ]);
- }
-
- public function register(): void
- {
- $this
- ->getContainer()
- ->add(FormatterInterface::class, fn(): FormatterInterface => new class implements FormatterInterface {
-
- public function format(ResponseInterface $response, Throwable $throwable, RequestInterface $request): ResponseInterface
- {
- $formatter = str_starts_with($request->getUri()->getPath(), '/api') ?
- new JsonFormatter() :
- new HtmlFormatter(isProduction());
-
- return $formatter->format($response, $throwable, $request);
- }
- });
-
- $this
- ->getContainer()
- ->add(ErrorHandlerMiddleware::class)
- ->addArgument($this->getContainer()->get(FormatterInterface::class))
- ->addArgument([$this->getContainer()->get(MonologListener::class)]);
-
- $this
- ->getContainer()
- ->add(NotFoundHandlerMiddleware::class)
- ->addArgument(static function (ServerRequestInterface $request): ResponseInterface {
- if (str_starts_with($request->getUri()->getPath(), '/api')) {
- throw new ProblemDetailsException(new ProblemDetails(
- type: '://problem/not-found',
- title: 'Not found.',
- status: 404,
- detail: "The requested uri ({$request->getUri()->getPath()}) could not be find."
- ));
- }
-
- return new HtmlResponse(
- '
404 Not Found
',
- 404
- );
- });
-
- $this
- ->getContainer()
- ->add(ProblemDetailsMiddleware::class)
- ->addArgument(new ResponseFactory());
- }
- });
-};
diff --git a/config/containers/template.container.php b/config/containers/template.container.php
deleted file mode 100644
index 725c196..0000000
--- a/config/containers/template.container.php
+++ /dev/null
@@ -1,17 +0,0 @@
-add(
- TemplateRendererInterface::class,
- fn() => new LatteRenderer(
- storage_path('views'),
- cache_path('views'),
- !isProduction()
- )
- );
-};
diff --git a/config/pipeline.php b/config/pipeline.php
deleted file mode 100644
index 9dcf45d..0000000
--- a/config/pipeline.php
+++ /dev/null
@@ -1,60 +0,0 @@
-pipe(ErrorHandlerMiddleware::class);
-
- // This middleware will handle exceptions coming from API calls throwing a ProblemDetailsException.
- $app->pipe(ProblemDetailsMiddleware::class);
-
- // Pipe more middleware here that you want to execute on every request.
- $app->pipe(TrailingSlashMiddleware::class);
- $app->pipe(ContentLengthMiddleware::class);
-
- // Register the routing middleware in the pipeline.
- // It will add the Borsch\Router\RouteResult request attribute.
- $app->pipe(RouteMiddleware::class);
-
- // The following handle routing failures for common conditions:
- // - HEAD request but no routes answer that method
- // - OPTIONS request but no routes answer that method
- // - method not allowed
- // Order here matters, the MethodNotAllowedMiddleware should be placed after the Implicit*Middleware.
- $app->pipe(ImplicitHeadMiddleware::class);
- $app->pipe(ImplicitOptionsMiddleware::class);
- $app->pipe(MethodNotAllowedMiddleware::class);
-
- // Middleware can be attached to specific paths, allowing you to mix and match
- // applications under a common domain.
- $app->pipe('/api', [
- BodyParserMiddleware::class,
- UploadedFilesParserMiddleware::class
- ]);
-
- // This will take care of generating the response of your matched route.
- $app->pipe(DispatchMiddleware::class);
-
- // If no Response is returned by any middleware, then send a 404 Not Found response.
- // You can provide other fallback middleware to execute.
- $app->pipe(NotFoundHandlerMiddleware::class);
-};
diff --git a/config/routes.php b/config/routes.php
deleted file mode 100644
index 5e1f33c..0000000
--- a/config/routes.php
+++ /dev/null
@@ -1,12 +0,0 @@
-get('/', HomeHandler::class, 'home');
-};
diff --git a/phpstan.neon b/phpstan.neon
index 35fe330..7243a57 100644
--- a/phpstan.neon
+++ b/phpstan.neon
@@ -1,3 +1,3 @@
parameters:
- level: 6
+ level: 0
treatPhpDocTypesAsCertain: false
\ No newline at end of file
diff --git a/phpunit.xml b/phpunit.xml
index 7d0904f..0c12bb9 100644
--- a/phpunit.xml
+++ b/phpunit.xml
@@ -11,7 +11,6 @@
- ./app
./src
diff --git a/public/index.php b/public/index.php
index 5a386eb..8f4cab1 100644
--- a/public/index.php
+++ b/public/index.php
@@ -2,9 +2,8 @@
require_once __DIR__.'/../vendor/autoload.php';
-use Borsch\Application\ApplicationInterface;
+use Borsch\RequestHandler\RequestHandlerRunnerInterface;
use Psr\Container\ContainerInterface;
-use Psr\Http\Message\ServerRequestInterface;
(static function () {
// Warning, see: https://www.php.net/manual/en/timezones.others.php
@@ -14,13 +13,6 @@
/** @var ContainerInterface $container */
$container = (require_once __DIR__.'/../config/container.php');
- $app = $container->get(ApplicationInterface::class);
-
- (require_once __DIR__.'/../config/pipeline.php')($app);
- (require_once __DIR__.'/../config/routes.php')($app);
- (require_once __DIR__.'/../config/api.php')($app);
-
- $request = $container->get(ServerRequestInterface::class);
-
- $app->run($request);
+ $runner = $container->get(RequestHandlerRunnerInterface::class);
+ $runner->run();
})();
diff --git a/public/worker.php b/public/worker.php
index 67f88d8..2abd606 100644
--- a/public/worker.php
+++ b/public/worker.php
@@ -11,9 +11,8 @@
require_once __DIR__ . '/../vendor/autoload.php';
-use Borsch\Application\ApplicationInterface;
+use Borsch\RequestHandler\RequestHandlerRunnerInterface;
use Psr\Container\ContainerInterface;
-use Psr\Http\Message\ServerRequestInterface;
// Warning, see: https://www.php.net/manual/en/timezones.others.php
// do not use any of the timezones listed here (besides UTC)
@@ -22,15 +21,9 @@
/** @var ContainerInterface $container */
$container = (require_once __DIR__ . '/../config/container.php');
-$app = $container->get(ApplicationInterface::class);
-
-(require_once __DIR__.'/../config/pipeline.php')($app);
-(require_once __DIR__.'/../config/routes.php')($app);
-(require_once __DIR__.'/../config/api.php')($app);
-
-$handler = static function () use ($app, $container) {
- $request = $container->get(ServerRequestInterface::class);
- $app->run($request);
+$handler = static function () use ($container) {
+ $runner = $container->get(RequestHandlerRunnerInterface::class);
+ $runner->run();
};
$max_requests_number = filter_input(INPUT_SERVER, 'MAX_REQUESTS', FILTER_SANITIZE_NUMBER_INT) ?: 25;
diff --git a/src/Handler/AlbumHandler.php b/src/Application/Handler/AlbumHandler.php
similarity index 88%
rename from src/Handler/AlbumHandler.php
rename to src/Application/Handler/AlbumHandler.php
index 0269ff3..8ba59b2 100644
--- a/src/Handler/AlbumHandler.php
+++ b/src/Application/Handler/AlbumHandler.php
@@ -1,17 +1,16 @@
getAttribute('id');
@@ -33,6 +40,9 @@ public function handle(ServerRequestInterface $request): ResponseInterface
};
}
+ /**
+ * @throws ProblemDetailsException
+ */
#[OA\Get(
path: '/albums',
description: 'Get all albums',
@@ -89,7 +99,10 @@ private function getAlbums(?int $id = null): ResponseInterface
);
}
- /** @param array{"title": string, "artist_id": int} $body */
+ /**
+ * @param array{"title": string, "artist_id": int} $body
+ * @throws ProblemDetailsException
+ */
#[OA\Post(
path: '/albums',
description: 'Create a new album (there is no check on the `artist_id` existence)',
@@ -121,7 +134,10 @@ private function createAlbum(array $body): ResponseInterface
return new JsonResponse($new_album, 201);
}
- /** @param array{title?: string, artist_id?: int} $body */
+ /**
+ * @param array{title?: string, artist_id?: int} $body
+ * @throws ProblemDetailsException
+ */
#[OA\Put(
path: '/albums/{id}',
description: 'Update an album by ID',
@@ -164,6 +180,9 @@ private function updateAlbum(int $id, array $body): ResponseInterface
return new JsonResponse($updated_album);
}
+ /**
+ * @throws ProblemDetailsException
+ */
#[OA\Delete(
path: '/albums/{id}',
description: 'Delete an album by ID',
diff --git a/src/Handler/ArtistHandler.php b/src/Application/Handler/ArtistHandler.php
similarity index 88%
rename from src/Handler/ArtistHandler.php
rename to src/Application/Handler/ArtistHandler.php
index 66b9039..ccfbb70 100644
--- a/src/Handler/ArtistHandler.php
+++ b/src/Application/Handler/ArtistHandler.php
@@ -1,17 +1,16 @@
getAttribute('id');
@@ -33,6 +40,9 @@ public function handle(ServerRequestInterface $request): ResponseInterface
};
}
+ /**
+ * @throws ProblemDetailsException
+ */
#[OA\Get(
path: '/artists',
description: 'Get all artists',
@@ -89,7 +99,10 @@ private function getArtists(?int $id = null): ResponseInterface
);
}
- /** @param array{name: string} $body */
+ /**
+ * @param array{name: string} $body
+ * @throws ProblemDetailsException
+ */
#[OA\Post(
path: '/artists',
description: 'Create a new artist',
@@ -118,7 +131,10 @@ private function createArtist(array $body): ResponseInterface
return new JsonResponse($new_artist, 201);
}
- /** @param array{name: string} $body */
+ /**
+ * @param array{name: string} $body
+ * @throws ProblemDetailsException
+ */
#[OA\Put(
path: '/artists/{id}',
description: 'Update an artist',
@@ -158,6 +174,9 @@ private function updateArtist(int $id, array $body): ResponseInterface
return new JsonResponse($updated_artist);
}
+ /**
+ * @throws ProblemDetailsException
+ */
#[OA\Delete(
path: '/artists/{id}',
description: 'Delete an artist',
diff --git a/src/Handler/HomeHandler.php b/src/Application/Handler/HomeHandler.php
similarity index 51%
rename from src/Handler/HomeHandler.php
rename to src/Application/Handler/HomeHandler.php
index 8a0d98d..f8866f6 100644
--- a/src/Handler/HomeHandler.php
+++ b/src/Application/Handler/HomeHandler.php
@@ -1,32 +1,22 @@
engine->assign([
diff --git a/src/Handler/OpenApiHandler.php b/src/Application/Handler/OpenApiHandler.php
similarity index 66%
rename from src/Handler/OpenApiHandler.php
rename to src/Application/Handler/OpenApiHandler.php
index 32df909..4c391e2 100644
--- a/src/Handler/OpenApiHandler.php
+++ b/src/Application/Handler/OpenApiHandler.php
@@ -1,10 +1,12 @@
getAttribute('format', 'yaml');
- $openapi = Generator::scan([__ROOT_DIR__.'/src']);
+ $openapi = (new Generator())->generate([
+ __ROOT_DIR__.'/src/Application/Handler',
+ __ROOT_DIR__.'/src/Domain',
+ ]);
$definition = match ($format) {
'json' => $openapi->toJson(),
default => $openapi->toYaml(),
@@ -30,9 +37,9 @@ public function handle(ServerRequestInterface $request): ResponseInterface
$stream_factory = new StreamFactory();
return new Response(
- $stream_factory->createStream($definition),
200,
- ['Content-Type' => 'text/'.$format]
+ $stream_factory->createStream($definition),
+ ['Content-Type' => ['text/'.$format]]
);
}
}
diff --git a/src/Handler/RedocHandler.php b/src/Application/Handler/RedocHandler.php
similarity index 78%
rename from src/Handler/RedocHandler.php
rename to src/Application/Handler/RedocHandler.php
index be3ac67..1385ab1 100644
--- a/src/Handler/RedocHandler.php
+++ b/src/Application/Handler/RedocHandler.php
@@ -1,12 +1,15 @@
router->generateUri('openapi', ['format' => 'json']);
diff --git a/src/Service/AlbumService.php b/src/Domain/AlbumService.php
similarity index 78%
rename from src/Service/AlbumService.php
rename to src/Domain/AlbumService.php
index f3bc17d..3f23269 100644
--- a/src/Service/AlbumService.php
+++ b/src/Domain/AlbumService.php
@@ -1,13 +1,11 @@
AlbumMapper::toAlbum($album),
- $this->repository->all()
- );
+ return $this->repository->all();
}
+ /**
+ * @throws ProblemDetailsException
+ */
public function find(int $id): ?Album
{
$album = $this->repository->find($id);
+
if ($album === null) {
$this->logger->error('Album with ID #{id} not found', ['{id}' => $id]);
@@ -44,10 +43,24 @@ public function find(int $id): ?Album
));
}
- return AlbumMapper::toAlbum($album);
+ if (!$album instanceof Album) {
+ $this->logger->error('Album with ID #{id} is not an instance of Album', ['{id}' => $id]);
+
+ throw new ProblemDetailsException(new ProblemDetails(
+ type: '://problem/invalid-type',
+ title: 'Invalid album type.',
+ status: 500,
+ detail: "The album with ID {$id} is not a valid Album instance."
+ ));
+ }
+
+ return $album;
}
- /** @param array{title: string, artist_id: int} $data */
+ /**
+ * @param array{title: string, artist_id: int} $data
+ * @throws ProblemDetailsException
+ */
public function create(array $data): Album
{
if (!isset($data['title'], $data['artist_id'])) {
@@ -76,7 +89,10 @@ public function create(array $data): Album
return $this->find($id);
}
- /** @param array{title?: string, artist_id?: int} $data */
+ /**
+ * @param array{title?: string, artist_id?: int} $data
+ * @throws ProblemDetailsException
+ */
public function update(int $id, array $data): Album
{
if (!isset($data['title']) && !isset($data['artist_id'])) {
@@ -112,6 +128,9 @@ public function update(int $id, array $data): Album
return $this->find($id);
}
+ /**
+ * @throws ProblemDetailsException
+ */
public function delete(int $id): bool
{
// Making sure it exists
diff --git a/src/Service/ArtistService.php b/src/Domain/ArtistService.php
similarity index 77%
rename from src/Service/ArtistService.php
rename to src/Domain/ArtistService.php
index c6871ae..d65e5a4 100644
--- a/src/Service/ArtistService.php
+++ b/src/Domain/ArtistService.php
@@ -1,15 +1,11 @@
ArtistMapper::toArtist($artist),
- $this->repository->all()
- );
+ return $this->repository->all();
}
+ /**
+ * @throws ProblemDetailsException
+ */
public function find(int $id): ?Artist
{
$artist = $this->repository->find($id);
+
if ($artist === null) {
$this->logger->error('Artist with ID #{id} not found', ['{id}' => $id]);
@@ -46,10 +43,24 @@ public function find(int $id): ?Artist
));
}
- return ArtistMapper::toArtist($artist);
+ if (!$artist instanceof Artist) {
+ $this->logger->error('Artist with ID #{id} is not an instance of Artist', ['{id}' => $id]);
+
+ throw new ProblemDetailsException(new ProblemDetails(
+ type: '://problem/invalid-artist',
+ title: 'Invalid artist data.',
+ status: 500,
+ detail: "The artist with ID {$id} is not a valid Artist instance."
+ ));
+ }
+
+ return $artist;
}
- /** @param array{name: string} $data */
+ /**
+ * @param array{name: string} $data
+ * @throws ProblemDetailsException
+ */
public function create(array $data): Artist
{
if (!isset($data['name'])) {
@@ -78,7 +89,10 @@ public function create(array $data): Artist
return $this->find($id);
}
- /** @param array{name: string} $data */
+ /**
+ * @param array{name: string} $data
+ * @throws ProblemDetailsException
+ */
public function update(int $id, array $data): Artist
{
if (!isset($data['name'])) {
@@ -109,6 +123,9 @@ public function update(int $id, array $data): Artist
return $this->find($id);
}
+ /**
+ * @throws ProblemDetailsException
+ */
public function delete(int $id): bool
{
// Making sure it exists
diff --git a/src/Model/Album.php b/src/Domain/Model/Album.php
similarity index 91%
rename from src/Model/Album.php
rename to src/Domain/Model/Album.php
index daa5fe8..2c77860 100644
--- a/src/Model/Album.php
+++ b/src/Domain/Model/Album.php
@@ -1,6 +1,6 @@
> */
+ /** @return array */
public function all(): array;
/** @return array|null */
- public function find(int $id): ?array;
+ public function find(int $id): ?Model;
/** @param array{Title?: string, ArtistId?: int, Name?: string} $data */
public function create(array $data): int;
diff --git a/src/Handler/HealthCheckHandler.php b/src/Handler/HealthCheckHandler.php
deleted file mode 100644
index 6ba3e04..0000000
--- a/src/Handler/HealthCheckHandler.php
+++ /dev/null
@@ -1,24 +0,0 @@
-table_gateway = new TableGateway($this->getTable(), $adapter);
+ $this->mapper = $mapper;
$this->logger = $logger->withName(__CLASS__);
}
@@ -26,15 +32,20 @@ abstract protected function getTable(): string;
public function all(): array
{
- return iterator_to_array($this->table_gateway->select());
+ return array_map(
+ fn (iterable $row): Model => $this->mapper->map($row),
+ iterator_to_array($this->table_gateway->select())
+ );
}
- public function find(int $id): ?array
+ public function find(int $id): ?Model
{
/** @var ResultSet $results */
- $results = $this->table_gateway->select([static::ROW_IDENTIFIER => $id]);
+ $results = $this->table_gateway->select([static::ROW_IDENTIFIER => $id])->current();
- return (array)$results->current();
+ return $results === null
+ ? null
+ : $this->mapper->map((array)$results);
}
public function create(array $data): int
diff --git a/src/Infrastructure/AlbumRepository.php b/src/Infrastructure/AlbumRepository.php
new file mode 100644
index 0000000..57e154b
--- /dev/null
+++ b/src/Infrastructure/AlbumRepository.php
@@ -0,0 +1,23 @@
+ $object */
- public static function toAlbum(iterable $object): Album
+ public function map(iterable $object): Album
{
$album = new Album();
$album->id = $object[AlbumRepository::ROW_IDENTIFIER] ?? null;
diff --git a/src/Repository/Mapper/ArtistMapper.php b/src/Infrastructure/Mapper/ArtistMapper.php
similarity index 56%
rename from src/Repository/Mapper/ArtistMapper.php
rename to src/Infrastructure/Mapper/ArtistMapper.php
index 13627f8..ff83b97 100644
--- a/src/Repository/Mapper/ArtistMapper.php
+++ b/src/Infrastructure/Mapper/ArtistMapper.php
@@ -1,15 +1,15 @@
$object */
- public static function toArtist(iterable $object): Artist
+ public function map(iterable $object): Artist
{
$artist = new Artist();
$artist->id = $object[ArtistRepository::ROW_IDENTIFIER] ?? null;
diff --git a/src/Infrastructure/Mapper/MapperInterface.php b/src/Infrastructure/Mapper/MapperInterface.php
new file mode 100644
index 0000000..90beddb
--- /dev/null
+++ b/src/Infrastructure/Mapper/MapperInterface.php
@@ -0,0 +1,11 @@
+handleErrorException($throwable, $request);
- return;
- }
-
- $this->logger->critical($this->formatLog($throwable, $request));
- }
-
- /**
- * @param ErrorException $exception
- * @param ServerRequestInterface $request
- */
- protected function handleErrorException(ErrorException $exception, ServerRequestInterface $request): void
- {
- $log = $this->formatLog($exception, $request);
- $severity = $exception->getSeverity();
-
- if ($this->isError($severity)) {
- $this->logger->error($log);
- } elseif ($this->isWarning($severity)) {
- $this->logger->warning($log);
- } elseif ($this->isNotice($severity)) {
- $this->logger->notice($log);
- } elseif ($this->isInformation($severity)) {
- $this->logger->info($log);
- } else {
- $this->logger->debug($log);
- }
- }
-
- /**
- * @param Throwable $throwable
- * @param ServerRequestInterface $request
- * @return string
- */
- protected function formatLog(Throwable $throwable, ServerRequestInterface $request): string
- {
- return sprintf(
- '%s %s => %s Stacktrace: %s',
- $request->getMethod(),
- (string)$request->getUri(),
- $throwable->getMessage(),
- $throwable->getTraceAsString()
- );
- }
-
- /**
- * @param int $code
- * @return bool
- */
- protected function isError(int $code): bool
- {
- return in_array($code, [
- E_ERROR,
- E_RECOVERABLE_ERROR,
- E_CORE_ERROR,
- E_COMPILE_ERROR,
- E_USER_ERROR,
- E_PARSE
- ]);
- }
-
- /**
- * @param int $code
- * @return bool
- */
- protected function isWarning(int $code): bool
- {
- return in_array($code, [
- E_WARNING,
- E_USER_WARNING,
- E_CORE_WARNING,
- E_COMPILE_WARNING
- ]);
- }
-
- /**
- * @param int $code
- * @return bool
- */
- protected function isNotice(int $code): bool
- {
- return in_array($code, [
- E_NOTICE,
- E_USER_NOTICE
- ]);
- }
-
- /**
- * @param int $code
- * @return bool
- */
- protected function isInformation(int $code): bool
- {
- // If PHP version is 8.4 then do not include E_STRICT because it is deprecated (throws exception)
- if (version_compare(PHP_VERSION, '8.4', '>=')) {
- return in_array($code, [
- E_DEPRECATED,
- E_USER_DEPRECATED
- ]);
- }
-
- return in_array($code, [
- E_STRICT,
- E_DEPRECATED,
- E_USER_DEPRECATED
- ]);
- }
-}
diff --git a/src/Repository/AlbumRepository.php b/src/Repository/AlbumRepository.php
deleted file mode 100644
index d4aac8d..0000000
--- a/src/Repository/AlbumRepository.php
+++ /dev/null
@@ -1,14 +0,0 @@
-
-
-
-
-
-
- Borsch-Skeleton - Not Found
-
-
-
-
-
+{layout 'layout.tpl'}
+
+{block title}Not found{/block}
+
+{block content}
-
Nothing here...
+
Nothing here...
Go home
-
-
-
\ No newline at end of file
+{/block}
diff --git a/storage/views/500.tpl b/storage/views/500.tpl
index 7319366..88172ce 100644
--- a/storage/views/500.tpl
+++ b/storage/views/500.tpl
@@ -1,26 +1,16 @@
-Not Found
-
-
-
-
-
- Borsch-Skeleton - Error
-
-
-
-
-
+{layout 'layout.tpl'}
+
+{block title}Whoops !{/block}
+
+{block content}
-
Whoops !
-
An unexpected error occured, be assured we've been informed and working hard to fix it.
+
Whoops !
+
An unexpected error occurred, be assured we've been informed and working hard to fix it.
Go home
-
-
-
\ No newline at end of file
+{/block}
diff --git a/storage/views/home.tpl b/storage/views/home.tpl
index 97937b3..89769ce 100644
--- a/storage/views/home.tpl
+++ b/storage/views/home.tpl
@@ -1,97 +1,88 @@
-
-
-
-
-
-
- Borsch-Skeleton - Home
-
-
-
-
-
-
-

-
Fuel Your Code with Flavor
-
+{layout 'layout.tpl'}
+
+{block title}Home{/block}
-
-
+{block content}
+
+

+
Fuel Your Code with Flavor
+
+
+
+
-
-
Easy-to-Follow Documentation
-
Get started quickly with clear, well-structured documentation covering everything you need to build and scale.
-
-
-
-
+
+
Easy-to-Follow Documentation
+
Get started quickly with clear, well-structured documentation covering everything you need to build and scale.
+
+
+
+
-
-
API-First with OpenAPI & Redoc
-
- Effortlessly document and explore your APIs with built-in OpenAPI support and a sleek ReDoc interface.
-
-
-
-
-
+
+
API-First with OpenAPI & Redoc
+
+ Effortlessly document and explore your APIs with built-in OpenAPI support and a sleek ReDoc interface.
+
+
+
+
+
-
-
Lightweight & Fast
-
Designed for high performance with minimal overhead, ensuring ultra-fast response times.
-
-
-
+
+
Lightweight & Fast
+
Designed for high performance with minimal overhead, ensuring ultra-fast response times.
+
+
+
-
+
-
-
Dependency Injection
-
Built-in DI container for managing dependencies efficiently.
-
-
-
+
+
Dependency Injection
+
Built-in DI container for managing dependencies efficiently.
+
+
+
-
-
Middleware Support
-
Customize request handling with PSR-15 middlewares.
-
-
-
+
+
Middleware Support
+
Customize request handling with PSR-15 middlewares.
+
+
+
-
-
Error Handling & Debugging
-
Clear error messages for a smooth development experience.
-
-
+
+
Error Handling & Debugging
+
Clear error messages for a smooth development experience.
-
-
\ No newline at end of file
+
+{/block}
diff --git a/storage/views/layout.tpl b/storage/views/layout.tpl
new file mode 100644
index 0000000..492ba83
--- /dev/null
+++ b/storage/views/layout.tpl
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
Borsch-Skeleton - {block title}{/block}
+
+
+
+
+
+ {block content}{/block}
+
+
+
diff --git a/tests/Feature/ExampleTest.php b/tests/Feature/ExampleTest.php
new file mode 100644
index 0000000..e99d96d
--- /dev/null
+++ b/tests/Feature/ExampleTest.php
@@ -0,0 +1,7 @@
+toBeTrue();
+});
diff --git a/tests/Mock/TestHandler.php b/tests/Mock/TestHandler.php
deleted file mode 100644
index d8772a4..0000000
--- a/tests/Mock/TestHandler.php
+++ /dev/null
@@ -1,35 +0,0 @@
-getUri()->getPath();
-
- if ($path == '/to/exception') {
- throw new Exception('Pest for testz!');
- } elseif (in_array($path, ['/to/post/and/check/json', '/to/post/and/check/urlencoded', '/to/post/and/check/xml'])) {
- return new JsonResponse($request->getParsedBody());
- } elseif ($path == '/to/route/result') {
- return new JsonResponse([
- 'route_result_is_success' => $request->getAttribute(RouteResultInterface::class)?->isSuccess() ?? false
- ]);
- }
-
- return new TextResponse(__METHOD__);
- }
-}
diff --git a/tests/Pest.php b/tests/Pest.php
index 879562e..b239048 100644
--- a/tests/Pest.php
+++ b/tests/Pest.php
@@ -1,30 +1,5 @@
beforeEach(function () {
- $this->log_file = __DIR__.'/app.log';
- $this->logger = new Logger('Borsch');
- $this->logger->pushHandler(new StreamHandler($this->log_file));
-
- $this->listener = new MonologListener($this->logger);
-
- $this->server_request = (new ServerRequestFactory())->createServerRequest(
- 'GET',
- 'https://example.com/to/dispatch'
- );
- })
- ->afterEach(function () {
- if (file_exists($this->log_file)) {
- @unlink($this->log_file);
- }
- })
- ->in(
- 'Unit/Listener/MonologListenerTest.php',
- 'Unit/Middleware/ErrorHandlerMiddlewareTest.php'
- );
-
-uses()
- ->beforeEach(function () {
- $this->log_file = __DIR__.'/app.log';
+pest()->extend(Tests\TestCase::class)->in('Feature');
- $container = new Container();
- $container->set(RouteMiddleware::class);
- $container->set(DispatchMiddleware::class);
- $container->set(NotFoundHandlerMiddleware::class);
- $container->set(FastRouteRouter::class);
- $container->set(RouterInterface::class, FastRouteRouter::class)->cache(true);
- $container->set(
- Logger::class,
- fn() => (new Logger(env('APP_NAME', 'App')))
- ->pushHandler(new StreamHandler(__DIR__.'/app.log'))
- );
- $container
- ->set(ErrorHandlerMiddleware::class)
- ->addMethod('addListener', [$container->get(MonologListener::class)]);
- $container
- ->set(TemplateRendererInterface::class, LatteEngine::class);
-
- $this->container = $container;
- $this->app = new class(new RequestHandler(), $container->get(RouterInterface::class), $container) extends BorschApp {
- public function runAndGetResponse(ServerRequestInterface $server_request): ResponseInterface
- {
- return $this->request_handler->handle($server_request);
- }
- public function getContainer(): Container
- {
- return $this->container;
- }
- };
-
- // Middlewares pipeline
- $this->app->pipe(ErrorHandlerMiddleware::class);
- $this->app->pipe(TrailingSlashMiddleware::class);
- $this->app->pipe(ContentLengthMiddleware::class);
- $this->app->pipe('/to/post/and/check', BodyParserMiddleware::class);
- $this->app->pipe(RouteMiddleware::class);
- $this->app->pipe(ImplicitHeadMiddleware::class);
- $this->app->pipe(ImplicitOptionsMiddleware::class);
- $this->app->pipe(MethodNotAllowedMiddleware::class);
- $this->app->pipe(DispatchMiddleware::class);
- $this->app->pipe(NotFoundHandlerMiddleware::class);
+/*
+|--------------------------------------------------------------------------
+| Expectations
+|--------------------------------------------------------------------------
+|
+| When you're writing tests, you often need to check that values meet certain conditions. The
+| "expect()" function gives you access to a set of "expectations" methods that you can use
+| to assert different things. Of course, you may extend the Expectation API at any time.
+|
+*/
- // Routes
- $this->app->get('/to/dispatch', TestHandler::class);
- $this->app->get('/to/exception', TestHandler::class);
- $this->app->post('/to/post/and/check/json', TestHandler::class);
- $this->app->post('/to/post/and/check/urlencoded', TestHandler::class);
- $this->app->post('/to/post/and/check/xml', TestHandler::class);
- $this->app->post('/to/head/without/post', TestHandler::class);
- $this->app->head('/to/head', TestHandler::class);
- $this->app->options('/to/options', TestHandler::class);
- $this->app->get('/to/route/result', TestHandler::class);
+expect()->extend('toBeOne', function () {
+ return $this->toBe(1);
+});
- // Server Requests
- $factory = new ServerRequestFactory();
- $this->server_request = $factory->createServerRequest('GET', 'https://example.com/to/dispatch');
- $this->server_request_not_found = $factory->createServerRequest('GET', 'https://example.com/to/not/dispatch');
- $this->server_request_to_exception = $factory->createServerRequest('GET', 'https://example.com/to/exception');
- })
- ->in('Unit/Middleware');
+/*
+|--------------------------------------------------------------------------
+| Functions
+|--------------------------------------------------------------------------
+|
+| While Pest is very powerful out-of-the-box, you may have some testing code specific to your
+| project that you don't want to repeat in every file. Here you can also expose helpers as
+| global functions to help you to reduce the number of lines of code in your test files.
+|
+*/
-uses()
- ->beforeEach(function () {
- $_ENV['TEST1'] = 'true';
- $_ENV['TEST2'] = 'yes';
- $_ENV['TEST3'] = 'false';
- $_ENV['TEST4'] = 'no';
- $_ENV['TEST5'] = 'empty';
- $_ENV['TEST6'] = 'null';
- $_ENV['TEST7'] = 'a value ';
- })
- ->in('Unit/Bootstrap/HelpersTest.php');
+function something()
+{
+ // ..
+}
diff --git a/tests/TestCase.php b/tests/TestCase.php
index fdb4604..cfb05b6 100644
--- a/tests/TestCase.php
+++ b/tests/TestCase.php
@@ -1,6 +1,6 @@
toBeTrue()
- ->and(env('TEST2'))->toBeTrue();
-});
-
-test('env can deal with FALSE', function() {
- expect(env('TEST3'))->toBeFalse()
- ->and(env('TEST4'))->toBeFalse();
-});
-
-test('env can deal with EMPTY', function() {
- expect(env('TEST5'))->toBe('');
-});
-
-test('env can deal with NULL', function() {
- expect(env('TEST6'))->toBe(null);
-});
-
-test('env can deal with DEFAULT', function() {
- expect(env('TEST7'))->toBe('a value');
-});
diff --git a/tests/Unit/ExampleTest.php b/tests/Unit/ExampleTest.php
new file mode 100644
index 0000000..d92d58a
--- /dev/null
+++ b/tests/Unit/ExampleTest.php
@@ -0,0 +1,7 @@
+toBeTrue();
+});
diff --git a/tests/Unit/Listener/MonologListenerTest.php b/tests/Unit/Listener/MonologListenerTest.php
deleted file mode 100644
index 035ed49..0000000
--- a/tests/Unit/Listener/MonologListenerTest.php
+++ /dev/null
@@ -1,139 +0,0 @@
-listener)(new InvalidArgumentException('Not Found', 404), $this->server_request);
- expect($this->log_file)->toBeFile()
- ->and(file_get_contents($this->log_file))->toContain(
- 'Borsch.CRITICAL: GET https://example.com/to/dispatch => Not Found'
- );
-});
-
-it('can handle error exception with severity E_ERROR', function () {
- ($this->listener)(new ErrorException('Not Found', 404, E_ERROR), $this->server_request);
- expect($this->log_file)->toBeFile()
- ->and(file_get_contents($this->log_file))->toContain(
- 'Borsch.ERROR: GET https://example.com/to/dispatch => Not Found'
- );
-});
-
-it('can handle error exception with severity E_RECOVERABLE_ERROR', function () {
- ($this->listener)(new ErrorException('Not Found', 404, E_RECOVERABLE_ERROR), $this->server_request);
- expect($this->log_file)->toBeFile()
- ->and(file_get_contents($this->log_file))->toContain(
- 'Borsch.ERROR: GET https://example.com/to/dispatch => Not Found'
- );
-});
-
-it('can handle error exception with severity E_CORE_ERROR', function () {
- ($this->listener)(new ErrorException('Not Found', 404, E_RECOVERABLE_ERROR), $this->server_request);
- expect($this->log_file)->toBeFile()
- ->and(file_get_contents($this->log_file))->toContain(
- 'Borsch.ERROR: GET https://example.com/to/dispatch => Not Found'
- );
-});
-
-it('can handle error exception with severity E_COMPILE_ERROR', function () {
- ($this->listener)(new ErrorException('Not Found', 404, E_RECOVERABLE_ERROR), $this->server_request);
- expect($this->log_file)->toBeFile()
- ->and(file_get_contents($this->log_file))->toContain(
- 'Borsch.ERROR: GET https://example.com/to/dispatch => Not Found'
- );
-});
-
-it('can handle error exception with severity E_USER_ERROR', function () {
- ($this->listener)(new ErrorException('Not Found', 404, E_USER_ERROR), $this->server_request);
- expect($this->log_file)->toBeFile()
- ->and(file_get_contents($this->log_file))->toContain(
- 'Borsch.ERROR: GET https://example.com/to/dispatch => Not Found'
- );
-});
-
-it('can handle error exception with severity E_PARSE', function () {
- ($this->listener)(new ErrorException('Not Found', 404, E_PARSE), $this->server_request);
- expect($this->log_file)->toBeFile()
- ->and(file_get_contents($this->log_file))->toContain(
- 'Borsch.ERROR: GET https://example.com/to/dispatch => Not Found'
- );
-});
-
-it('can handle error exception with severity E_WARNING', function () {
- ($this->listener)(new ErrorException('Not Found', 404, E_WARNING), $this->server_request);
- expect($this->log_file)->toBeFile()
- ->and(file_get_contents($this->log_file))->toContain(
- 'Borsch.WARNING: GET https://example.com/to/dispatch => Not Found'
- );
-});
-
-it('can handle error exception with severity E_USER_WARNING', function () {
- ($this->listener)(new ErrorException('Not Found', 404, E_USER_WARNING), $this->server_request);
- expect($this->log_file)->toBeFile()
- ->and(file_get_contents($this->log_file))->toContain(
- 'Borsch.WARNING: GET https://example.com/to/dispatch => Not Found'
- );
-});
-
-it('can handle error exception with severity E_CORE_WARNING', function () {
- ($this->listener)(new ErrorException('Not Found', 404, E_CORE_WARNING), $this->server_request);
- expect($this->log_file)->toBeFile()
- ->and(file_get_contents($this->log_file))->toContain(
- 'Borsch.WARNING: GET https://example.com/to/dispatch => Not Found'
- );
-});
-
-it('can handle error exception with severity E_COMPILE_WARNING', function () {
- ($this->listener)(new ErrorException('Not Found', 404, E_COMPILE_WARNING), $this->server_request);
- expect($this->log_file)->toBeFile()
- ->and(file_get_contents($this->log_file))->toContain(
- 'Borsch.WARNING: GET https://example.com/to/dispatch => Not Found'
- );
-});
-
-it('can handle error exception with severity E_NOTICE', function () {
- ($this->listener)(new ErrorException('Not Found', 404, E_NOTICE), $this->server_request);
- expect($this->log_file)->toBeFile()
- ->and(file_get_contents($this->log_file))->toContain(
- 'Borsch.NOTICE: GET https://example.com/to/dispatch => Not Found'
- );
-});
-
-it('can handle error exception with severity E_USER_NOTICE', function () {
- ($this->listener)(new ErrorException('Not Found', 404, E_USER_NOTICE), $this->server_request);
- expect($this->log_file)->toBeFile()
- ->and(file_get_contents($this->log_file))->toContain(
- 'Borsch.NOTICE: GET https://example.com/to/dispatch => Not Found'
- );
-});
-
-if (version_compare(PHP_VERSION, '8.4', '<')) {
- it('can handle error exception with severity E_STRICT', function () {
- ($this->listener)(new ErrorException('Not Found', 404, E_STRICT), $this->server_request);
- expect($this->log_file)->toBeFile()
- ->and(file_get_contents($this->log_file))->toContain(
- 'Borsch.INFO: GET https://example.com/to/dispatch => Not Found'
- );
- });
-}
-
-it('can handle error exception with severity E_DEPRECATED', function () {
- ($this->listener)(new ErrorException('Not Found', 404, E_DEPRECATED), $this->server_request);
- expect($this->log_file)->toBeFile()
- ->and(file_get_contents($this->log_file))->toContain(
- 'Borsch.INFO: GET https://example.com/to/dispatch => Not Found'
- );
-});
-
-it('can handle error exception with severity E_USER_DEPRECATED', function () {
- ($this->listener)(new ErrorException('Not Found', 404, E_USER_DEPRECATED), $this->server_request);
- expect($this->log_file)->toBeFile()
- ->and(file_get_contents($this->log_file))->toContain(
- 'Borsch.INFO: GET https://example.com/to/dispatch => Not Found'
- );
-});
-
-it('can handle error exception with default DEBUG severity', function () {
- ($this->listener)(new ErrorException('Not Found', 404, 9999999), $this->server_request);
- expect($this->log_file)->toBeFile()
- ->and(file_get_contents($this->log_file))->toContain(
- 'Borsch.DEBUG: GET https://example.com/to/dispatch => Not Found'
- );
-});