refactor: introduce DI container

This commit is contained in:
Dag 2024-08-29 22:24:29 +02:00
parent e010fd4d52
commit 7754173b80
17 changed files with 202 additions and 89 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -99,7 +99,7 @@ EOD;
$user = $data->user; $user = $data->user;
if ($user->videos === null) { if ($user->videos === null) {
// twitch regularly does this for unknown reasons // 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; return;
} }

View File

@ -17,7 +17,9 @@ if (file_exists(__DIR__ . '/config.ini.php')) {
} }
Configuration::loadConfiguration($config, getenv()); 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) { set_exception_handler(function (\Throwable $e) use ($logger) {
$response = new Response(render(__DIR__ . '/templates/exception.html.php', ['e' => $e]), 500); $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')); date_default_timezone_set(Configuration::getConfig('system', 'timezone'));
$argv = $argv ?? null; $argv = $argv ?? null;
@ -88,7 +73,7 @@ if ($argv) {
} }
try { try {
$rssBridge = new RssBridge($logger, $cache, $httpClient); $rssBridge = new RssBridge($container);
$response = $rssBridge->main($request); $response = $rssBridge->main($request);
$response->send(); $response->send();
} catch (\Throwable $e) { } catch (\Throwable $e) {

View File

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

View File

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

View File

@ -14,10 +14,6 @@ class CacheFactory
public function create(string $name = null): CacheInterface public function create(string $name = null): CacheInterface
{ {
$name ??= Configuration::getConfig('cache', 'type');
if (!$name) {
throw new \Exception('No cache type configured');
}
$cacheNames = []; $cacheNames = [];
foreach (scandir(PATH_LIB_CACHES) as $file) { foreach (scandir(PATH_LIB_CACHES) as $file) {
if (preg_match('/^([^.]+)Cache\.php$/U', $file, $m)) { 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 final class RssBridge
{ {
private static Logger $logger; private static Container $container;
private static CacheInterface $cache;
private static HttpClient $httpClient;
public function __construct( public function __construct(
Logger $logger, Container $container
CacheInterface $cache,
HttpClient $httpClient
) { ) {
self::$logger = $logger; self::$container = $container;
self::$cache = $cache;
self::$httpClient = $httpClient;
} }
public function main(Request $request): Response 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); return new Response(render(__DIR__ . '/../templates/error.html.php', ['message' => 'Invalid action']), 400);
} }
$className = '\\' . $actionName; $controller = self::$container[$actionName];
$actionObject = new $className();
$response = $actionObject($request); $response = $controller($request);
return $response; return $response;
} }
@ -94,16 +87,16 @@ final class RssBridge
public static function getLogger(): Logger public static function getLogger(): Logger
{ {
// null logger is only for the tests not to fail // 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 public static function getCache(): CacheInterface
{ {
return self::$cache; return self::$container['cache'];
} }
public static function getHttpClient(): HttpClient 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']); $context = Json::encode($record['context']);
} }
} }
// Intentionally omitting newline
$text = sprintf( $text = sprintf(
"[%s] %s.%s %s %s\n", '[%s] %s.%s %s %s',
$record['created_at']->format('Y-m-d H:i:s'), $record['created_at']->format('Y-m-d H:i:s'),
$record['name'], $record['name'],
$record['level_name'], $record['level_name'],