Skip to content
Draft
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
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
"@check:migrations",
"@test"
],
"test": "vendor/bin/phpunit --bootstrap vendor/autoload.php tests",
"test": "vendor/bin/phpunit --bootstrap vendor/autoload.php tests --display-phpunit-notices",
"start": "php -S localhost:8000 -t public"
}
}
26 changes: 26 additions & 0 deletions src/Controller/NotFoundController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

namespace KittyShare\Controller;

use KittyShare\Controller\BaseController;
use KittyShare\Manager\{ConfigManager, TranslationManager};
use KittyShare\Translations\Language;
use KittyShare\Http\{Request, Response, TemplateResponse};
use Override;

final class NotFoundController extends BaseController
{
#[Override]
public function handle(Request $request): Response
{
return new TemplateResponse(
template: "error/404",
parameters: [
'path' => $request->getUri(),
'baseUrl' => ConfigManager::get()->baseUrl,
'translationManager' => new TranslationManager(Language::EN), // TODO: Do not hardcode
],
statusCode: 404,
);
}
}
18 changes: 9 additions & 9 deletions src/Http/Router.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

namespace KittyShare\Http;

use KittyShare\Controller\NotFoundController;
use KittyShare\Model\Dependencies;
use KittyShare\Manager\ConfigManager;

/**
* The router is responsible for dispatching requests to the correct handler
Expand Down Expand Up @@ -90,14 +90,14 @@ public function dispatch(Method $method, string $path): Response
);
}

return new TemplateResponse(
template: "error/404",
parameters: [
'path' => $path,
'baseUrl' => ConfigManager::get()->baseUrl,
],
statusCode: 404,
);
return new NotFoundController($this->dependencies)
->handle(
new Request(
method: $method,
uri: $path,
parameters: [],
)
);
}

/**
Expand Down
44 changes: 44 additions & 0 deletions src/Manager/TranslationManager.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?php

namespace KittyShare\Manager;

use KittyShare\Translations\Language;
use KittyShare\Translations\TranslationKeys;
use OutOfBoundsException;

/**
* Manages the translation values the application uses
*/
final class TranslationManager
{
/**
* @var array<string, pure-callable(string...): string|string> The key-value store for the translations
*/
private readonly array $translations;

public function __construct(Language $language)
{
/** @var array<string, pure-callable(string...): string|string> $translations */
$translations = require __DIR__ . '/../Translations/' . $language->value . '.php';

$this->translations = $translations;
}

public function get(TranslationKeys $key, string ...$values): string
{
// If the key does not exist, something went wrong... badly.
if (!array_key_exists($key->value, $this->translations)) {
throw new OutOfBoundsException(
sprintf('Translation key "%s" does not exist.', $key->value),
);
}

$translation = $this->translations[$key->value];

if (is_string($translation)) {
return $translation;
}

return $translation(...$values);
}
}
15 changes: 10 additions & 5 deletions src/Template/error/404.php
Original file line number Diff line number Diff line change
@@ -1,18 +1,23 @@
<?php
/** @var string $path */
use KittyShare\Manager\TranslationManager;
use KittyShare\Translations\TranslationKeys;
/**
* @var string $path
* @var TranslationManager $translationManager
*/
?>
<?php
require_once __DIR__ . '/../partials/base.php';

$GLOBALS['__kittyshare_base'] = $baseUrl ?? '';

renderHeader("404 Page not found");
renderHeader($translationManager->get(TranslationKeys::PAGE_404_TITLE));
?>
<main>
<h1 class="title">404 Page not found</h1>
<p>Hey! It seems like the site that you've tried to access does not exist.</p>
<h1 class="title"><?= $translationManager->get(TranslationKeys::PAGE_404_TITLE) ?></h1>
<p><?= $translationManager->get(TranslationKeys::PAGE_404_SUBTITLE) ?></p>

<p class="margin-top-6">Your requested path was &quot;<?= htmlspecialchars($path) ?>&quot;.</p>
<p class="margin-top-6"><?= e($translationManager->get(TranslationKeys::PAGE_404_PATHDESC, $path)) ?></p>
</main>
<?php
renderFooter();
8 changes: 8 additions & 0 deletions src/Translations/Language.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?php

namespace KittyShare\Translations;

enum Language: string
{
case EN = 'en';
}
11 changes: 11 additions & 0 deletions src/Translations/TranslationKeys.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

namespace KittyShare\Translations;

enum TranslationKeys: string
{
case KITTYSHARE_LOGO = 'kittyshare.logo';
case PAGE_404_TITLE = 'page.404.title';
case PAGE_404_SUBTITLE = 'page.404.subtitle';
case PAGE_404_PATHDESC = 'page.404.path';
}
10 changes: 10 additions & 0 deletions src/Translations/en.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

use KittyShare\Translations\TranslationKeys;

return [
TranslationKeys::KITTYSHARE_LOGO->value => 'KittyShare Logo',
TranslationKeys::PAGE_404_TITLE->value => '404 Page not found',
TranslationKeys::PAGE_404_SUBTITLE->value => 'Hey! It seems like the site that you\'ve tried to access does not exist.',
TranslationKeys::PAGE_404_PATHDESC->value => fn(string $path) => "Your requested path was \"$path\".",
];
124 changes: 124 additions & 0 deletions tests/Controller/NotFoundControllerTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
<?php

namespace KittyShare\Tests\Controller;

use KittyShare\Controller\NotFoundController;
use KittyShare\Http\Request;
use KittyShare\Http\TemplateResponse;
use KittyShare\Manager\AuthenticationManager;
use KittyShare\Manager\ConfigManager;
use KittyShare\Manager\TranslationManager;
use KittyShare\Model\Dependencies;
use KittyShare\Repository\SessionRepository;
use KittyShare\Repository\SetupRepository;
use KittyShare\Repository\ShareRepository;
use KittyShare\Repository\UserRepository;
use PHPUnit\Framework\TestCase;
use ReflectionProperty;

final class NotFoundControllerTest extends TestCase
{
private NotFoundController $controller;

protected function setUp(): void
{
$userRepository = $this->createStub(UserRepository::class);
$sessionRepository = $this->createStub(SessionRepository::class);

$dependencies = new Dependencies(
userRepository: $userRepository,
setupRepository: $this->createStub(SetupRepository::class),
sessionRepository: $sessionRepository,
shareRepository: $this->createStub(ShareRepository::class),
authenticationManager: new AuthenticationManager($userRepository, $sessionRepository),
);

$this->controller = new NotFoundController($dependencies);
}

public function testHandleReturnsTemplateResponse(): void
{
$request = $this->createMock(Request::class);
$request->expects(self::once())
->method('getUri')
->willReturn('/missing-page');

$response = $this->controller->handle($request);

self::assertInstanceOf(TemplateResponse::class, $response);
}

public function testHandleReturns404StatusCode(): void
{
$request = $this->createStub(Request::class);
$request->method('getUri')->willReturn('/missing-page');

$response = $this->controller->handle($request);

self::assertSame(404, $this->getResponseProperty($response, 'statusCode'));
}

public function testHandleUses404Template(): void
{
$request = $this->createStub(Request::class);
$request->method('getUri')->willReturn('/missing-page');

$response = $this->controller->handle($request);

self::assertSame('error/404', $this->getResponseProperty($response, 'template'));
}

public function testHandlePassesRequestUriToTemplate(): void
{
$uri = '/some/nonexistent/page?foo=bar';

$request = $this->createMock(Request::class);
$request->expects(self::once())
->method('getUri')
->willReturn($uri);

$response = $this->controller->handle($request);

$parameters = $this->getResponseProperty($response, 'parameters');

self::assertSame($uri, $parameters['path']);
}

public function testHandlePassesConfiguredBaseUrlToTemplate(): void
{
$request = $this->createStub(Request::class);
$request->method('getUri')->willReturn('/missing-page');

$response = $this->controller->handle($request);

$parameters = $this->getResponseProperty($response, 'parameters');

self::assertSame(ConfigManager::get()->baseUrl, $parameters['baseUrl']);
}

public function testHandlePassesTranslationManagerToTemplate(): void
{
$request = $this->createStub(Request::class);
$request->method('getUri')->willReturn('/missing-page');

$response = $this->controller->handle($request);

$parameters = $this->getResponseProperty($response, 'parameters');

self::assertInstanceOf(
TranslationManager::class,
$parameters['translationManager'],
);
}

/**
* Reads a private property from TemplateResponse.
*/
private function getResponseProperty(
TemplateResponse $response,
string $property,
): mixed {
return (new ReflectionProperty(TemplateResponse::class, $property))
->getValue($response);
}
}
6 changes: 4 additions & 2 deletions tests/Http/TemplateResponseTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

use KittyShare\Http\Response;
use KittyShare\Http\TemplateResponse;
use KittyShare\Manager\TranslationManager;
use KittyShare\Translations\Language;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;

Expand Down Expand Up @@ -51,7 +53,7 @@ public function rendersTemplateWithStatusCode(): void
{
$response = new TemplateResponse(
'error/404',
['path' => '/missing', 'baseUrl' => ''],
['path' => '/missing', 'baseUrl' => '', 'translationManager' => new TranslationManager(Language::EN)],
404,
);

Expand All @@ -72,7 +74,7 @@ public function escapesTemplateParameters(): void
{
$response = new TemplateResponse(
'error/404',
['path' => '/a<b>&"c"', 'baseUrl' => ''],
['path' => '/a<b>&"c"', 'baseUrl' => '', 'translationManager' => new TranslationManager(Language::EN)],
404,
);

Expand Down
Loading