Skip to content
Merged
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
10 changes: 4 additions & 6 deletions src/App.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,8 @@ public function __construct(...$middleware)
$needsErrorHandlerNext = false;
foreach ($middleware as $handler) {
// load AccessLogHandler and ErrorHandler instance from last Container
if ($handler === AccessLogHandler::class) {
$handler = $container->getAccessLogHandler();
} elseif ($handler === ErrorHandler::class) {
$handler = $container->getErrorHandler();
if ($handler === AccessLogHandler::class || $handler === ErrorHandler::class) {
$handler = $container->getObject($handler);
}

// ensure AccessLogHandler is always followed by ErrorHandler
Expand Down Expand Up @@ -99,12 +97,12 @@ public function __construct(...$middleware)

// add default ErrorHandler as first handler unless it is already added explicitly
if ($needsErrorHandler instanceof Container) {
\array_unshift($handlers, $needsErrorHandler->getErrorHandler());
\array_unshift($handlers, $needsErrorHandler->getObject(ErrorHandler::class));
}

// only log for built-in webserver and PHP development webserver by default, others have their own access log
if ($needsAccessLog instanceof Container) {
$handler = $needsAccessLog->getAccessLogHandler();
$handler = $needsAccessLog->getObject(AccessLogHandler::class);
if (!$handler->isDevNull()) {
\array_unshift($handlers, $handler);
}
Expand Down
46 changes: 20 additions & 26 deletions src/Container.php
Original file line number Diff line number Diff line change
Expand Up @@ -135,45 +135,39 @@ public function getEnv(string $name): ?string
}

/**
* [Internal] Get an object of given class from container
*
* @template T of object
* @param class-string<T> $class
* @return object returns an instance of given $class or throws if it can not be instantiated
* @phpstan-return T
* @throws \TypeError if container config or factory returns an unexpected type
* @throws \Error if object of type $class can not be loaded
* @throws \Throwable if container factory function throws unexpected exception
* @internal
*/
public function getAccessLogHandler(): AccessLogHandler
public function getObject(string $class) /*: object (PHP 7.2+) */
{
if ($this->container instanceof ContainerInterface) {
if ($this->container->has(AccessLogHandler::class)) {
// @phpstan-ignore-next-line method return type will ensure correct type or throw `TypeError`
return $this->container->get(AccessLogHandler::class);
} else {
return new AccessLogHandler();
if ($this->container instanceof ContainerInterface && $this->container->has($class)) {
$value = $this->container->get($class);
if (!$value instanceof $class) {
throw new \TypeError(
'Return value of ' . \explode("\0", \get_class($this->container))[0] . '::get() for ' . $class . ' must be of type ' . $class . ', ' . $this->gettype($value) . ' returned'
);
}
return $value;
} elseif ($this->container instanceof ContainerInterface) {
return new $class();
}
return $this->loadObject(AccessLogHandler::class);
}

/**
* @throws \TypeError if container config or factory returns an unexpected type
* @throws \Throwable if container factory function throws unexpected exception
* @internal
*/
public function getErrorHandler(): ErrorHandler
{
if ($this->container instanceof ContainerInterface) {
if ($this->container->has(ErrorHandler::class)) {
// @phpstan-ignore-next-line method return type will ensure correct type or throw `TypeError`
return $this->container->get(ErrorHandler::class);
} else {
return new ErrorHandler();
}
}
return $this->loadObject(ErrorHandler::class);
return $this->loadObject($class);
}

/**
* @template T of object
* @param class-string<T> $name
* @return T
* @return object returns an instance of given class $name or throws if it can not be instantiated
* @phpstan-return T
* @throws \TypeError if container config or factory returns an unexpected type
* @throws \Error if object of type $name can not be loaded
* @throws \Throwable if container factory function throws unexpected exception
Expand Down
86 changes: 59 additions & 27 deletions tests/AppTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,10 @@ public function testConstructWithContainerAssignsDefaultHandlersAndContainerForR
$errorHandler = new ErrorHandler();

$container = $this->createMock(Container::class);
$container->expects($this->once())->method('getAccessLogHandler')->willReturn($accessLogHandler);
$container->expects($this->once())->method('getErrorHandler')->willReturn($errorHandler);
$container->expects($this->exactly(2))->method('getObject')->willReturnMap([
[AccessLogHandler::class, $accessLogHandler],
[ErrorHandler::class, $errorHandler],
]);

assert($container instanceof Container);
$app = new App($container);
Expand Down Expand Up @@ -105,8 +107,15 @@ public function testConstructWithContainerAndMiddlewareClassNameAssignsCallableF
{
$middleware = function (ServerRequestInterface $request, callable $next) { };

$accessLogHandler = new AccessLogHandler();
$errorHandler = new ErrorHandler();

$container = $this->createMock(Container::class);
$container->expects($this->once())->method('callable')->with('stdClass')->willReturn($middleware);
$container->expects($this->exactly(2))->method('getObject')->willReturnMap([
[AccessLogHandler::class, $accessLogHandler],
[ErrorHandler::class, $errorHandler],
]);

assert($container instanceof Container);
$app = new App($container, \stdClass::class);
Expand All @@ -126,8 +135,8 @@ public function testConstructWithContainerAndMiddlewareClassNameAssignsCallableF
assert(is_array($handlers));

$this->assertCount(4, $handlers);
$this->assertInstanceOf(AccessLogHandler::class, $handlers[0]);
$this->assertInstanceOf(ErrorHandler::class, $handlers[1]);
$this->assertSame($accessLogHandler, $handlers[0]);
$this->assertSame($errorHandler, $handlers[1]);
$this->assertSame($middleware, $handlers[2]);
$this->assertInstanceOf(RouteHandler::class, $handlers[3]);

Expand Down Expand Up @@ -217,10 +226,14 @@ public function testConstructWithContainerAndErrorHandlerAssignsErrorHandlerAfte

public function testConstructWithContainerAndErrorHandlerClassAssignsErrorHandlerFromContainerAfterDefaultAccessLogHandler(): void
{
$accessLogHandler = new AccessLogHandler();
$errorHandler = new ErrorHandler();

$container = $this->createMock(Container::class);
$container->expects($this->once())->method('getErrorHandler')->willReturn($errorHandler);
$container->expects($this->exactly(2))->method('getObject')->willReturnMap([
[AccessLogHandler::class, $accessLogHandler],
[ErrorHandler::class, $errorHandler],
]);

assert($container instanceof Container);
$app = new App($container, ErrorHandler::class);
Expand All @@ -240,20 +253,24 @@ public function testConstructWithContainerAndErrorHandlerClassAssignsErrorHandle
assert(is_array($handlers));

$this->assertCount(3, $handlers);
$this->assertInstanceOf(AccessLogHandler::class, $handlers[0]);
$this->assertSame($accessLogHandler, $handlers[0]);
$this->assertSame($errorHandler, $handlers[1]);
$this->assertInstanceOf(RouteHandler::class, $handlers[2]);
}

public function testConstructWithMultipleContainersAndErrorHandlerClassAssignsErrorHandlerFromLastContainerBeforeErrorHandlerAfterDefaultAccessLogHandler(): void
{
$accessLogHandler = new AccessLogHandler();
$errorHandler = new ErrorHandler();

$unused = $this->createMock(Container::class);
$unused->expects($this->never())->method('getErrorHandler');
$unused->expects($this->never())->method('getObject');

$container = $this->createMock(Container::class);
$container->expects($this->once())->method('getErrorHandler')->willReturn($errorHandler);
$container->expects($this->exactly(2))->method('getObject')->willReturnMap([
[AccessLogHandler::class, $accessLogHandler],
[ErrorHandler::class, $errorHandler],
]);

assert($unused instanceof Container);
assert($container instanceof Container);
Expand All @@ -274,21 +291,25 @@ public function testConstructWithMultipleContainersAndErrorHandlerClassAssignsEr
assert(is_array($handlers));

$this->assertCount(3, $handlers);
$this->assertInstanceOf(AccessLogHandler::class, $handlers[0]);
$this->assertSame($accessLogHandler, $handlers[0]);
$this->assertSame($errorHandler, $handlers[1]);
$this->assertInstanceOf(RouteHandler::class, $handlers[2]);
}

public function testConstructWithMultipleContainersAndMiddlewareAssignsErrorHandlerFromLastContainerBeforeMiddlewareAfterDefaultAccessLogHandler(): void
{
$middleware = function (ServerRequestInterface $request, callable $next) { };
$accessLogHandler = new AccessLogHandler();
$errorHandler = new ErrorHandler();

$unused = $this->createMock(Container::class);
$unused->expects($this->never())->method('getErrorHandler');
$unused->expects($this->never())->method('getObject');

$container = $this->createMock(Container::class);
$container->expects($this->once())->method('getErrorHandler')->willReturn($errorHandler);
$container->expects($this->exactly(2))->method('getObject')->willReturnMap([
[AccessLogHandler::class, $accessLogHandler],
[ErrorHandler::class, $errorHandler],
]);

assert($unused instanceof Container);
assert($container instanceof Container);
Expand All @@ -309,7 +330,7 @@ public function testConstructWithMultipleContainersAndMiddlewareAssignsErrorHand
assert(is_array($handlers));

$this->assertCount(4, $handlers);
$this->assertInstanceOf(AccessLogHandler::class, $handlers[0]);
$this->assertSame($accessLogHandler, $handlers[0]);
$this->assertSame($errorHandler, $handlers[1]);
$this->assertSame($middleware, $handlers[2]);
$this->assertInstanceOf(RouteHandler::class, $handlers[3]);
Expand Down Expand Up @@ -350,15 +371,19 @@ public function testConstructWithMultipleContainersAndMiddlewareAndErrorHandlerC
$middleware = function (ServerRequestInterface $request, callable $next) { };

$unused = $this->createMock(Container::class);
$unused->expects($this->never())->method('getErrorHandler');
$unused->expects($this->never())->method('getObject');

$accessLogHandler = new AccessLogHandler();
$errorHandler1 = new ErrorHandler();
$container1 = $this->createMock(Container::class);
$container1->expects($this->once())->method('getErrorHandler')->willReturn($errorHandler1);
$container1->expects($this->exactly(2))->method('getObject')->willReturnMap([
[AccessLogHandler::class, $accessLogHandler],
[ErrorHandler::class, $errorHandler1],
]);

$errorHandler2 = new ErrorHandler();
$container2 = $this->createMock(Container::class);
$container2->expects($this->once())->method('getErrorHandler')->willReturn($errorHandler2);
$container2->expects($this->exactly(1))->method('getObject')->with(ErrorHandler::class)->willReturn($errorHandler2);

assert($unused instanceof Container);
assert($container1 instanceof Container);
Expand All @@ -380,7 +405,7 @@ public function testConstructWithMultipleContainersAndMiddlewareAndErrorHandlerC
assert(is_array($handlers));

$this->assertCount(5, $handlers);
$this->assertInstanceOf(AccessLogHandler::class, $handlers[0]);
$this->assertSame($accessLogHandler, $handlers[0]);
$this->assertSame($errorHandler1, $handlers[1]);
$this->assertSame($middleware, $handlers[2]);
$this->assertSame($errorHandler2, $handlers[3]);
Expand Down Expand Up @@ -473,8 +498,10 @@ public function testConstructWithContainerAndAccessLogHandlerClassAndErrorHandle
$errorHandler = new ErrorHandler();

$container = $this->createMock(Container::class);
$container->expects($this->once())->method('getAccessLogHandler')->willReturn($accessLogHandler);
$container->expects($this->once())->method('getErrorHandler')->willReturn($errorHandler);
$container->expects($this->exactly(2))->method('getObject')->willReturnMap([
[AccessLogHandler::class, $accessLogHandler],
[ErrorHandler::class, $errorHandler],
]);

assert($container instanceof Container);
$app = new App($container, AccessLogHandler::class, ErrorHandler::class);
Expand Down Expand Up @@ -507,8 +534,10 @@ public function testConstructWithContainerAndAccessLogHandlerClassAndErrorHandle
$errorHandler = new ErrorHandler();

$container = $this->createMock(Container::class);
$container->expects($this->once())->method('getAccessLogHandler')->willReturn($accessLogHandler);
$container->expects($this->once())->method('getErrorHandler')->willReturn($errorHandler);
$container->expects($this->exactly(2))->method('getObject')->willReturnMap([
[AccessLogHandler::class, $accessLogHandler],
[ErrorHandler::class, $errorHandler],
]);

assert($container instanceof Container);
$app = new App($container, AccessLogHandler::class, ErrorHandler::class);
Expand Down Expand Up @@ -569,11 +598,13 @@ public function testConstructWithMultipleContainersAndAccessLogHandlerClassAndEr
$errorHandler = new ErrorHandler();

$unused = $this->createMock(Container::class);
$unused->expects($this->never())->method('getErrorHandler');
$unused->expects($this->never())->method('getObject');

$container = $this->createMock(Container::class);
$container->expects($this->once())->method('getAccessLogHandler')->willReturn($accessLogHandler);
$container->expects($this->once())->method('getErrorHandler')->willReturn($errorHandler);
$container->expects($this->exactly(2))->method('getObject')->willReturnMap([
[AccessLogHandler::class, $accessLogHandler],
[ErrorHandler::class, $errorHandler],
]);

assert($unused instanceof Container);
assert($container instanceof Container);
Expand Down Expand Up @@ -608,12 +639,13 @@ public function testConstructWithMultipleContainersAndMiddlewareAssignsDefaultHa
$errorHandler = new ErrorHandler();

$unused = $this->createMock(Container::class);
$unused->expects($this->never())->method('getAccessLogHandler');
$unused->expects($this->never())->method('getErrorHandler');
$unused->expects($this->never())->method('getObject');

$container = $this->createMock(Container::class);
$container->expects($this->once())->method('getAccessLogHandler')->willReturn($accessLogHandler);
$container->expects($this->once())->method('getErrorHandler')->willReturn($errorHandler);
$container->expects($this->exactly(2))->method('getObject')->willReturnMap([
[AccessLogHandler::class, $accessLogHandler],
[ErrorHandler::class, $errorHandler],
]);

assert($unused instanceof Container);
assert($container instanceof Container);
Expand Down
Loading