Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve message for not found exception interface #6

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ vendor/
.phpunit.result.cache
clover.xml
.vscode/
composer.lock
composer.lock
.idea
58 changes: 35 additions & 23 deletions src/App.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<?php

/**
* SimpleMVC
*
Expand Down Expand Up @@ -38,7 +39,7 @@ class App
private ContainerInterface $container;
private LoggerInterface $logger;
private float $startTime;

/** @var mixed[] */
private array $config;

Expand All @@ -48,7 +49,7 @@ class App
public function __construct(ContainerInterface $container)
{
$this->startTime = microtime(true);
$this->container = $container;
$this->container = $container;

try {
$this->config = $container->get('config');
Expand All @@ -64,14 +65,14 @@ public function __construct(ContainerInterface $container)
}
$routes = $this->config['routing']['routes'];
// Routing initialization
$this->dispatcher = cachedDispatcher(function(RouteCollector $r) use ($routes) {
$this->dispatcher = cachedDispatcher(function (RouteCollector $r) use ($routes) {
foreach ($routes as $route) {
$r->addRoute($route[0], $route[1], $route[2]);
}
}, [
'cacheFile' => $this->config['routing']['cache'] ?? '',
'cacheDisabled' => !isset($this->config['routing']['cache'])
]);
'cacheFile' => $this->config['routing']['cache'] ?? '',
'cacheDisabled' => !isset($this->config['routing']['cache'])
]);

// Logger initialization
try {
Expand All @@ -94,7 +95,7 @@ public function getDispatcher(): Dispatcher
{
return $this->dispatcher;
}

/**
* @return mixed[]
*/
Expand Down Expand Up @@ -122,16 +123,19 @@ public function bootstrap(): void
*/
public function dispatch(ServerRequestInterface $request): ResponseInterface
{
$this->logger->info(sprintf(
"Request: %s %s",
$request->getMethod(),
$request->getUri()->getPath()
));
$this->logger->info(
sprintf(
"Request: %s %s",
$request->getMethod(),
$request->getUri()->getPath()
)
);

$routeInfo = $this->dispatcher->dispatch(
$request->getMethod(),
$request->getMethod(),
$request->getUri()->getPath()
);

$controllerName = null;
switch ($routeInfo[0]) {
case Dispatcher::NOT_FOUND:
Expand Down Expand Up @@ -173,17 +177,25 @@ public function dispatch(ServerRequestInterface $request): ResponseInterface
}
}
} catch (NotFoundExceptionInterface $e) {
throw new ControllerException(sprintf(
'The controller name %s cannot be retrieved from the container',
$name
));
}
throw new ControllerException(
sprintf(
'Cannot get controller from container for route [ %s %s ]: %s',
$request->getMethod(),
$request->getUri()->getPath(),
$e->getMessage()
),
0,
$e
);
}
}

$this->logger->info(sprintf(
"Response: %d",
$response->getStatusCode()
));
$this->logger->info(
sprintf(
"Response: %d",
$response->getStatusCode()
)
);

$this->logger->info(sprintf("Execution time: %.3f sec", microtime(true) - $this->startTime));
$this->logger->info(sprintf("Memory usage: %d bytes", memory_get_usage(true)));
Expand All @@ -200,4 +212,4 @@ public static function buildRequestFromGlobals(): ServerRequestInterface
return (new ServerRequestCreator($factory, $factory, $factory, $factory))
->fromGlobals();
}
}
}
82 changes: 58 additions & 24 deletions tests/AppTest.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<?php

/**
* SimpleMVC
*
Expand All @@ -23,16 +24,18 @@
use Psr\Http\Message\ServerRequestInterface;
use Psr\Log\LoggerInterface;
use Psr\Log\NullLogger;
use RuntimeException;
use SimpleMVC\App;
use SimpleMVC\Controller\BasicAuth;
use SimpleMVC\Controller\Error404;
use SimpleMVC\Exception\ControllerException;
use SimpleMVC\Exception\InvalidConfigException;
use SimpleMVC\Response\HaltResponse;
use SimpleMVC\Test\Asset\TestAttributeController;
use SimpleMVC\Test\Asset\TestController;

class AppTest extends TestCase
{
{
/** @var ContainerInterface|MockInterface|LegacyMockInterface */
private $container;

Expand All @@ -42,15 +45,16 @@ class AppTest extends TestCase
private array $config;

private ServerRequestInterface $request;

public function setUp(): void
{
$this->tmpCacheFile = sys_get_temp_dir() . '/cache.route';
$this->config = [
$this->config = [
'routing' => [
'routes' => []
]
];

$this->container = Mockery::mock(ContainerInterface::class);
$this->container->shouldReceive('get')
->with('config')
Expand All @@ -59,7 +63,10 @@ public function setUp(): void

$this->container->shouldReceive('get')
->with(LoggerInterface::class)
->andThrow(new class extends Exception implements NotFoundExceptionInterface {})
->andThrow(
new class extends Exception implements NotFoundExceptionInterface {
}
)
->byDefault();

$this->container->shouldReceive('get')
Expand Down Expand Up @@ -103,7 +110,8 @@ public function testGetDefaultNullLogger(): void

public function testUseCustomLogger(): void
{
$customLogger = new class extends NullLogger {};
$customLogger = new class extends NullLogger {
};
$this->container->shouldReceive('get')
->with(LoggerInterface::class)
->andReturn($customLogger);
Expand Down Expand Up @@ -146,13 +154,13 @@ public function testBootstrapWithEmptyValue(): void

public function testBootstrapWithClosure(): void
{
$this->config['bootstrap'] = function(ContainerInterface $c) {
$this->config['bootstrap'] = function (ContainerInterface $c) {
$this->assertInstanceOf(ContainerInterface::class, $c);
};
$this->container->shouldReceive('get')
->with('config')
->andReturn($this->config);

$app = new App($this->container);
$app->bootstrap();
}
Expand All @@ -174,15 +182,15 @@ public function testDispatchWithoutRouteReturns404(): void
$response = $app->dispatch($this->request);

$this->assertInstanceOf(ResponseInterface::class, $response);
$this->assertEquals(404, $response->getStatusCode());
$this->assertEquals(404, $response->getStatusCode());
}

public function testDispatchWithRouteAndControllerReturns200(): void
{
$this->config = [
$this->config = [
'routing' => [
'routes' => [
['GET', '/', TestController::class]
'routes' => [
['GET', '/', TestController::class]
]
]
];
Expand All @@ -194,17 +202,17 @@ public function testDispatchWithRouteAndControllerReturns200(): void
$response = $app->dispatch($this->request);

$this->assertInstanceOf(ResponseInterface::class, $response);
$this->assertEquals(200, $response->getStatusCode());
$this->assertEquals(200, $response->getStatusCode());
}

public function testDispatchControllerPipelineWithBasicAuthReturns401(): void
{
$this->config = [
$this->config = [
'routing' => [
'routes' => [
['GET', '/', [BasicAuth::class, TestController::class]]
'routes' => [
['GET', '/', [BasicAuth::class, TestController::class]]
]
],
],
'authentication' => [
'username' => 'test',
'password' => 'password'
Expand All @@ -219,19 +227,22 @@ public function testDispatchControllerPipelineWithBasicAuthReturns401(): void
->andReturn(new BasicAuth($this->container));

$app = new App($this->container);
$this->request = $this->request->withHeader('Authorization', sprintf("Basic %s", base64_encode('test:password')));
$this->request = $this->request->withHeader(
'Authorization',
sprintf("Basic %s", base64_encode('test:password'))
);
$response = $app->dispatch($this->request);
$this->assertEquals(200, $response->getStatusCode());
}

public function testDispatchControllerPipelineWithBasicAuthReturns200(): void
{
$this->config = [
$this->config = [
'routing' => [
'routes' => [
['GET', '/', [BasicAuth::class, TestController::class]]
'routes' => [
['GET', '/', [BasicAuth::class, TestController::class]]
]
],
],
'authentication' => [
'username' => 'test',
'password' => 'password'
Expand All @@ -254,10 +265,10 @@ public function testDispatchControllerPipelineWithBasicAuthReturns200(): void
public function testDispatchControllerPipelineWithAttribute(): void
{
$attributes = ['foo' => 'bar'];
$this->config = [
$this->config = [
'routing' => [
'routes' => [
['GET', '/', [TestAttributeController::class, TestController::class]]
'routes' => [
['GET', '/', [TestAttributeController::class, TestController::class]]
]
]
];
Expand All @@ -283,4 +294,27 @@ public function testBuildRequestFromGlobals(): void
{
$this->assertInstanceOf(ServerRequestInterface::class, App::buildRequestFromGlobals());
}

public function testCannotGetControllerFromContainerException(): void
{
$this->expectException(ControllerException::class);
$this->expectExceptionMessage(
'Cannot get controller from container for route [ GET / ]: This error being throw by a test'
);
$this->expectExceptionCode(0);

$this->container->shouldReceive('get')
->with('config')
->andReturn(['routing' => ['routes' => [['GET', '/', TestController::class]]]]);
$this->container->shouldReceive('get')
->with(TestController::class)
->andThrow(
new class('This error being throw by a test') extends RuntimeException implements
NotFoundExceptionInterface {
}
);

$app = new App($this->container);
$app->dispatch($this->request);
}
}