refactor: introduce DI container (#4238)

* refactor: introduce DI container

* add bin/test
This commit is contained in:
Dag 2024-08-29 22:48:59 +02:00 committed by GitHub
parent e010fd4d52
commit 58544cd61a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 231 additions and 89 deletions

View File

@ -14,9 +14,10 @@ class ConnectivityAction implements ActionInterface
{
private BridgeFactory $bridgeFactory;
public function __construct()
{
$this->bridgeFactory = new BridgeFactory();
public function __construct(
BridgeFactory $bridgeFactory
) {
$this->bridgeFactory = $bridgeFactory;
}
public function __invoke(Request $request): Response

View File

@ -2,6 +2,14 @@
class DetectAction implements ActionInterface
{
private BridgeFactory $bridgeFactory;
public function __construct(
BridgeFactory $bridgeFactory
) {
$this->bridgeFactory = $bridgeFactory;
}
public function __invoke(Request $request): Response
{
$url = $request->get('url');
@ -14,14 +22,12 @@ class DetectAction implements ActionInterface
return new Response(render(__DIR__ . '/../templates/error.html.php', ['message' => 'You must specify a format']));
}
$bridgeFactory = new BridgeFactory();
foreach ($bridgeFactory->getBridgeClassNames() as $bridgeClassName) {
if (!$bridgeFactory->isEnabled($bridgeClassName)) {
foreach ($this->bridgeFactory->getBridgeClassNames() as $bridgeClassName) {
if (!$this->bridgeFactory->isEnabled($bridgeClassName)) {
continue;
}
$bridge = $bridgeFactory->create($bridgeClassName);
$bridge = $this->bridgeFactory->create($bridgeClassName);
$bridgeParams = $bridge->detectParameters($url);

View File

@ -4,11 +4,16 @@ class DisplayAction implements ActionInterface
{
private CacheInterface $cache;
private Logger $logger;
private BridgeFactory $bridgeFactory;
public function __construct()
{
$this->cache = RssBridge::getCache();
$this->logger = RssBridge::getLogger();
public function __construct(
CacheInterface $cache,
Logger $logger,
BridgeFactory $bridgeFactory
) {
$this->cache = $cache;
$this->logger = $logger;
$this->bridgeFactory = $bridgeFactory;
}
public function __invoke(Request $request): Response
@ -39,8 +44,7 @@ class DisplayAction implements ActionInterface
if (!$bridgeName) {
return new Response(render(__DIR__ . '/../templates/error.html.php', ['message' => 'Missing bridge parameter']), 400);
}
$bridgeFactory = new BridgeFactory();
$bridgeClassName = $bridgeFactory->createBridgeClassName($bridgeName);
$bridgeClassName = $this->bridgeFactory->createBridgeClassName($bridgeName);
if (!$bridgeClassName) {
return new Response(render(__DIR__ . '/../templates/error.html.php', ['message' => 'Bridge not found']), 404);
}
@ -48,7 +52,7 @@ class DisplayAction implements ActionInterface
if (!$format) {
return new Response(render(__DIR__ . '/../templates/error.html.php', ['message' => 'You must specify a format']), 400);
}
if (!$bridgeFactory->isEnabled($bridgeClassName)) {
if (!$this->bridgeFactory->isEnabled($bridgeClassName)) {
return new Response(render(__DIR__ . '/../templates/error.html.php', ['message' => 'This bridge is not whitelisted']), 400);
}
@ -62,7 +66,7 @@ class DisplayAction implements ActionInterface
define('NOPROXY', true);
}
$bridge = $bridgeFactory->create($bridgeClassName);
$bridge = $this->bridgeFactory->create($bridgeClassName);
$response = $this->createResponse($request, $bridge, $format);

View File

@ -7,6 +7,14 @@
*/
class FindfeedAction implements ActionInterface
{
private BridgeFactory $bridgeFactory;
public function __construct(
BridgeFactory $bridgeFactory
) {
$this->bridgeFactory = $bridgeFactory;
}
public function __invoke(Request $request): Response
{
$url = $request->get('url');
@ -19,15 +27,13 @@ class FindfeedAction implements ActionInterface
return new Response('You must specify a format', 400);
}
$bridgeFactory = new BridgeFactory();
$results = [];
foreach ($bridgeFactory->getBridgeClassNames() as $bridgeClassName) {
if (!$bridgeFactory->isEnabled($bridgeClassName)) {
foreach ($this->bridgeFactory->getBridgeClassNames() as $bridgeClassName) {
if (!$this->bridgeFactory->isEnabled($bridgeClassName)) {
continue;
}
$bridge = $bridgeFactory->create($bridgeClassName);
$bridge = $this->bridgeFactory->create($bridgeClassName);
$bridgeParams = $bridge->detectParameters($url);

View File

@ -2,6 +2,14 @@
final class FrontpageAction implements ActionInterface
{
private BridgeFactory $bridgeFactory;
public function __construct(
BridgeFactory $bridgeFactory
) {
$this->bridgeFactory = $bridgeFactory;
}
public function __invoke(Request $request): Response
{
$token = $request->attribute('token');
@ -9,10 +17,9 @@ final class FrontpageAction implements ActionInterface
$messages = [];
$activeBridges = 0;
$bridgeFactory = new BridgeFactory();
$bridgeClassNames = $bridgeFactory->getBridgeClassNames();
$bridgeClassNames = $this->bridgeFactory->getBridgeClassNames();
foreach ($bridgeFactory->getMissingEnabledBridges() as $missingEnabledBridge) {
foreach ($this->bridgeFactory->getMissingEnabledBridges() as $missingEnabledBridge) {
$messages[] = [
'body' => sprintf('Warning : Bridge "%s" not found', $missingEnabledBridge),
'level' => 'warning'
@ -21,8 +28,8 @@ final class FrontpageAction implements ActionInterface
$body = '';
foreach ($bridgeClassNames as $bridgeClassName) {
if ($bridgeFactory->isEnabled($bridgeClassName)) {
$body .= BridgeCard::render($bridgeClassName, $token);
if ($this->bridgeFactory->isEnabled($bridgeClassName)) {
$body .= BridgeCard::render($this->bridgeFactory, $bridgeClassName, $token);
$activeBridges++;
}
}

View File

@ -2,19 +2,25 @@
class ListAction implements ActionInterface
{
private BridgeFactory $bridgeFactory;
public function __construct(
BridgeFactory $bridgeFactory
) {
$this->bridgeFactory = $bridgeFactory;
}
public function __invoke(Request $request): Response
{
$list = new \stdClass();
$list->bridges = [];
$list->total = 0;
$bridgeFactory = new BridgeFactory();
foreach ($bridgeFactory->getBridgeClassNames() as $bridgeClassName) {
$bridge = $bridgeFactory->create($bridgeClassName);
foreach ($this->bridgeFactory->getBridgeClassNames() as $bridgeClassName) {
$bridge = $this->bridgeFactory->create($bridgeClassName);
$list->bridges[$bridgeClassName] = [
'status' => $bridgeFactory->isEnabled($bridgeClassName) ? 'active' : 'inactive',
'status' => $this->bridgeFactory->isEnabled($bridgeClassName) ? 'active' : 'inactive',
'uri' => $bridge->getURI(),
'donationUri' => $bridge->getDonationURI(),
'name' => $bridge->getName(),

View File

@ -17,11 +17,8 @@ if (file_exists(__DIR__ . '/../config.ini.php')) {
}
Configuration::loadConfiguration($config, getenv());
$logger = new SimpleLogger('rssbridge');
$container = require __DIR__ . '/../lib/dependencies.php';
$logger->addHandler(new StreamHandler('php://stderr', Logger::INFO));
$cacheFactory = new CacheFactory($logger);
$cache = $cacheFactory->create();
$cache = $container['cache'];
$cache->clear();

View File

@ -17,11 +17,8 @@ if (file_exists(__DIR__ . '/../config.ini.php')) {
}
Configuration::loadConfiguration($config, getenv());
$logger = new SimpleLogger('rssbridge');
$container = require __DIR__ . '/../lib/dependencies.php';
$logger->addHandler(new StreamHandler('php://stderr', Logger::INFO));
$cacheFactory = new CacheFactory($logger);
$cache = $cacheFactory->create();
$cache = $container['cache'];
$cache->prune();

29
bin/test Executable file
View File

@ -0,0 +1,29 @@
#!/usr/bin/env php
<?php
/**
* Add log records to all three levels (for testing purposes)
*/
require __DIR__ . '/../lib/bootstrap.php';
$config = [];
if (file_exists(__DIR__ . '/../config.ini.php')) {
$config = parse_ini_file(__DIR__ . '/../config.ini.php', true, INI_SCANNER_TYPED);
if (!$config) {
http_response_code(500);
exit("Error parsing config.ini.php\n");
}
}
Configuration::loadConfiguration($config, getenv());
$container = require __DIR__ . '/../lib/dependencies.php';
/** @var Logger $logger */
$logger = $container['logger'];
$logger->debug('This is a test debug message');
$logger->info('This is a test info message');
$logger->error('This is a test error message');

View File

@ -99,7 +99,7 @@ EOD;
$user = $data->user;
if ($user->videos === null) {
// twitch regularly does this for unknown reasons
//$this->logger->info('Twitch returned empty set of videos', ['data' => $data]);
$this->debug->info('Twitch returned empty set of videos', ['data' => $data]);
return;
}

View File

@ -17,7 +17,9 @@ if (file_exists(__DIR__ . '/config.ini.php')) {
}
Configuration::loadConfiguration($config, getenv());
$logger = new SimpleLogger('rssbridge');
$container = require __DIR__ . '/lib/dependencies.php';
$logger = $container['logger'];
set_exception_handler(function (\Throwable $e) use ($logger) {
$response = new Response(render(__DIR__ . '/templates/exception.html.php', ['e' => $e]), 500);
@ -60,23 +62,6 @@ register_shutdown_function(function () use ($logger) {
}
});
$cacheFactory = new CacheFactory($logger);
// Uncomment this for info logging to fs
// $logger->addHandler(new StreamHandler('/tmp/rss-bridge.txt', Logger::INFO));
// Uncomment this for debug logging to fs
// $logger->addHandler(new StreamHandler('/tmp/rss-bridge-debug.txt', Logger::DEBUG));
if (Debug::isEnabled()) {
$logger->addHandler(new ErrorLogHandler(Logger::DEBUG));
$cache = $cacheFactory->create('array');
} else {
$logger->addHandler(new ErrorLogHandler(Logger::INFO));
$cache = $cacheFactory->create();
}
$httpClient = new CurlHttpClient();
date_default_timezone_set(Configuration::getConfig('system', 'timezone'));
$argv = $argv ?? null;
@ -88,7 +73,7 @@ if ($argv) {
}
try {
$rssBridge = new RssBridge($logger, $cache, $httpClient);
$rssBridge = new RssBridge($container);
$response = $rssBridge->main($request);
$response->send();
} catch (\Throwable $e) {

View File

@ -2,10 +2,11 @@
final class BridgeCard
{
public static function render(string $bridgeClassName, ?string $token): string
{
$bridgeFactory = new BridgeFactory();
public static function render(
BridgeFactory $bridgeFactory,
string $bridgeClassName,
?string $token
): string {
$bridge = $bridgeFactory->create($bridgeClassName);
$uri = $bridge->getURI();

View File

@ -8,10 +8,12 @@ final class BridgeFactory
private array $enabledBridges = [];
private array $missingEnabledBridges = [];
public function __construct()
{
$this->cache = RssBridge::getCache();
$this->logger = RssBridge::getLogger();
public function __construct(
CacheInterface $cache,
Logger $logger
) {
$this->cache = $cache;
$this->logger = $logger;
// Create all possible bridge class names from fs
foreach (scandir(__DIR__ . '/../bridges/') as $file) {

View File

@ -14,10 +14,6 @@ class CacheFactory
public function create(string $name = null): CacheInterface
{
$name ??= Configuration::getConfig('cache', 'type');
if (!$name) {
throw new \Exception('No cache type configured');
}
$cacheNames = [];
foreach (scandir(PATH_LIB_CACHES) as $file) {
if (preg_match('/^([^.]+)Cache\.php$/U', $file, $m)) {

33
lib/Container.php Normal file
View File

@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
class Container implements \ArrayAccess
{
private array $values = [];
private array $resolved = [];
public function offsetSet($offset, $value): void
{
$this->values[$offset] = $value;
}
#[ReturnTypeWillChange] public function offsetGet($offset)
{
if (!isset($this->values[$offset])) {
throw new \Exception(sprintf('Unknown container key: "%s"', $offset));
}
if (!isset($this->resolved[$offset])) {
$this->resolved[$offset] = $this->values[$offset]($this);
}
return $this->resolved[$offset];
}
#[ReturnTypeWillChange] public function offsetExists($offset)
{
}
public function offsetUnset($offset): void
{
}
}

View File

@ -2,18 +2,12 @@
final class RssBridge
{
private static Logger $logger;
private static CacheInterface $cache;
private static HttpClient $httpClient;
private static Container $container;
public function __construct(
Logger $logger,
CacheInterface $cache,
HttpClient $httpClient
Container $container
) {
self::$logger = $logger;
self::$cache = $cache;
self::$httpClient = $httpClient;
self::$container = $container;
}
public function main(Request $request): Response
@ -83,10 +77,9 @@ final class RssBridge
return new Response(render(__DIR__ . '/../templates/error.html.php', ['message' => 'Invalid action']), 400);
}
$className = '\\' . $actionName;
$actionObject = new $className();
$controller = self::$container[$actionName];
$response = $actionObject($request);
$response = $controller($request);
return $response;
}
@ -94,16 +87,16 @@ final class RssBridge
public static function getLogger(): Logger
{
// null logger is only for the tests not to fail
return self::$logger ?? new NullLogger();
return self::$container['logger'] ?? new NullLogger();
}
public static function getCache(): CacheInterface
{
return self::$cache;
return self::$container['cache'];
}
public static function getHttpClient(): HttpClient
{
return self::$httpClient;
return self::$container['http_client'];
}
}

78
lib/dependencies.php Normal file
View File

@ -0,0 +1,78 @@
<?php
declare(strict_types=1);
$container = new Container();
$container[ConnectivityAction::class] = function ($c) {
return new ConnectivityAction($c['bridge_factory']);
};
$container[DetectAction::class] = function ($c) {
return new DetectAction($c['bridge_factory']);
};
$container[DisplayAction::class] = function ($c) {
return new DisplayAction($c['cache'], $c['logger'], $c['bridge_factory']);
};
$container[FindfeedAction::class] = function ($c) {
return new FindfeedAction($c['bridge_factory']);
};
$container[FrontpageAction::class] = function ($c) {
return new FrontpageAction($c['bridge_factory']);
};
$container[HealthAction::class] = function () {
return new HealthAction();
};
$container[ListAction::class] = function ($c) {
return new ListAction($c['bridge_factory']);
};
$container['bridge_factory'] = function ($c) {
return new BridgeFactory($c['cache'], $c['logger']);
};
$container['http_client'] = function () {
return new CurlHttpClient();
};
$container['cache_factory'] = function ($c) {
return new CacheFactory($c['logger']);
};
$container['logger'] = function () {
$logger = new SimpleLogger('rssbridge');
if (Debug::isEnabled()) {
$logger->addHandler(new ErrorLogHandler(Logger::DEBUG));
} else {
$logger->addHandler(new ErrorLogHandler(Logger::INFO));
}
// Uncomment this for info logging to fs
// $logger->addHandler(new StreamHandler('/tmp/rss-bridge.txt', Logger::INFO));
// Uncomment this for debug logging to fs
//$logger->addHandler(new StreamHandler('/tmp/rss-bridge-debug.txt', Logger::DEBUG));
return $logger;
};
$container['cache'] = function ($c) {
/** @var CacheFactory $cacheFactory */
$cacheFactory = $c['cache_factory'];
$type = Configuration::getConfig('cache', 'type');
if (!$type) {
throw new \Exception('No cache type configured');
}
if (Debug::isEnabled()) {
$cache = $cacheFactory->create('array');
} else {
$cache = $cacheFactory->create($type);
}
return $cache;
};
return $container;

View File

@ -175,8 +175,9 @@ final class ErrorLogHandler
$context = Json::encode($record['context']);
}
}
// Intentionally omitting newline
$text = sprintf(
"[%s] %s.%s %s %s\n",
'[%s] %s.%s %s %s',
$record['created_at']->format('Y-m-d H:i:s'),
$record['name'],
$record['level_name'],