Provide dependencies as PSR-11 container instead of arrays

This commit is contained in:
Art4 2025-01-08 13:30:50 +00:00
parent 1f25fe9bf5
commit bc8d0d5fae
9 changed files with 54 additions and 62 deletions

View file

@ -9,20 +9,22 @@ declare(strict_types=1);
namespace Friendica\Addon\Event;
use Psr\Container\ContainerInterface;
/**
* Start an addon.
*/
final class AddonStartEvent
{
private array $dependencies;
private ContainerInterface $container;
public function __construct(array $dependencies)
public function __construct(ContainerInterface $container)
{
$this->dependencies = $dependencies;
$this->container = $container;
}
public function getDependencies(): array
public function getContainer(): ContainerInterface
{
return $this->dependencies;
return $this->container;
}
}

View file

@ -38,6 +38,7 @@ use Friendica\Protocol\ATProtocol\DID;
use Friendica\Security\Authentication;
use Friendica\Security\ExAuth;
use Friendica\Security\OpenWebAuth;
use Friendica\Service\Addon\AddonContainer;
use Friendica\Service\Addon\AddonManager;
use Friendica\Util\BasePath;
use Friendica\Util\DateTimeFormat;
@ -208,17 +209,18 @@ class App
// At this place we should be careful because addons can change the container
// Maybe we should create a new container especially for the addons
foreach ($$this->addonManager->getProvidedDependencyRules() as $name => $rule) {
foreach ($this->addonManager->getProvidedDependencyRules() as $name => $rule) {
$this->container->addRule($name, $rule);
}
$dependencies = [];
$containers = [];
foreach ($this->addonManager->getAllRequiredDependencies() as $dependency) {
$dependencies[$dependency] = $this->container->create($dependency);
foreach ($this->addonManager->getRequiredDependencies() as $addonId => $dependencies) {
// @TODO At this point we can filter or restrict the dependencies of addons
$containers[$addonId] = AddonContainer::fromContainer($this->container, $dependencies);
}
$this->addonManager->initAddons($dependencies);
$this->addonManager->initAddons($containers);
}
private function registerEventDispatcher(): void

View file

@ -9,6 +9,8 @@ declare(strict_types=1);
namespace Friendica\Service\Addon;
use Psr\Container\ContainerInterface;
/**
* Interface to communicate with an addon.
*/
@ -22,7 +24,7 @@ interface Addon
public function getProvidedDependencyRules(): array;
public function initAddon(array $dependencies): void;
public function initAddon(ContainerInterface $container): void;
public function installAddon(): void;

View file

@ -9,6 +9,8 @@ declare(strict_types=1);
namespace Friendica\Service\Addon;
use Psr\Container\ContainerInterface;
/**
* Manager for all addons.
*/
@ -29,18 +31,6 @@ final class AddonManager
$this->addons = $this->addonFactory->getAddons($addonNames);
}
public function getAllRequiredDependencies(): array
{
$dependencies = [];
foreach ($this->addons as $addon) {
// @TODO Here we can filter or deny dependencies from addons
$dependencies = array_merge($dependencies, $addon->getRequiredDependencies());
}
return array_unique($dependencies);
}
public function getRequiredDependencies(): array
{
$dependencies = [];
@ -76,21 +66,19 @@ final class AddonManager
return $events;
}
public function initAddons(array $dependencies): void
/**
* @param ContainerInterface[] $containers
*/
public function initAddons(array $containers): void
{
foreach ($this->addons as $addon) {
$required = $addon->getRequiredDependencies();
$addonDependencies = [];
$container = $containers[$addon->getId()] ?? null;
foreach ($required as $dependency) {
if (!array_key_exists($dependency, $dependencies)) {
throw new \RuntimeException(sprintf('Dependency "%s" required by addon "%s" not found.', $dependency, $addon));
}
$addonDependencies[$dependency] = $dependencies[$dependency];
if ($container === null) {
throw new \RuntimeException(sprintf('Container for addon "%s" is missing.', $addon->getId()));
}
$addon->initAddon($addonDependencies);
$addon->initAddon($container);
}
}
}

View file

@ -13,6 +13,7 @@ use Friendica\Addon\AddonBootstrap;
use Friendica\Addon\DependencyProvider;
use Friendica\Addon\Event\AddonStartEvent;
use Friendica\Addon\InstallableAddon;
use Psr\Container\ContainerInterface;
/**
* Proxy object for an addon.
@ -61,7 +62,7 @@ final class AddonProxy implements Addon
return [];
}
public function initAddon(array $dependencies): void
public function initAddon(ContainerInterface $container): void
{
if ($this->isInit) {
return;
@ -69,7 +70,7 @@ final class AddonProxy implements Addon
$this->isInit = true;
$event = new AddonStartEvent($dependencies);
$event = new AddonStartEvent($container);
$this->bootstrap->initAddon($event);
}

View file

@ -9,6 +9,8 @@ declare(strict_types=1);
namespace Friendica\Service\Addon;
use Psr\Container\ContainerInterface;
/**
* Proxy object for a legacy addon.
*/
@ -52,7 +54,7 @@ final class LegacyAddonProxy implements Addon
return [];
}
public function initAddon(array $dependencies): void
public function initAddon(ContainerInterface $container): void
{
if ($this->isInit) {
return;

View file

@ -16,6 +16,7 @@ use Friendica\Addon\InstallableAddon;
use Friendica\Service\Addon\Addon;
use Friendica\Service\Addon\AddonProxy;
use PHPUnit\Framework\TestCase;
use Psr\Container\ContainerInterface;
use Psr\Log\LoggerInterface;
/**
@ -97,7 +98,7 @@ class AddonProxyTest extends TestCase
$addon = new AddonProxy('id', $bootstrap);
$addon->initAddon([]);
$addon->initAddon($this->createStub(ContainerInterface::class));
}
public function testInitAddonMultipleTimesWillCallBootstrapOnce(): void
@ -109,26 +110,22 @@ class AddonProxyTest extends TestCase
$addon = new AddonProxy('id', $bootstrap);
$addon->initAddon([]);
$addon->initAddon([]);
$addon->initAddon($this->createStub(ContainerInterface::class));
$addon->initAddon($this->createStub(ContainerInterface::class));
}
public function testInitAddonCallsBootstrapWithDependencies(): void
{
$container = $this->createStub(ContainerInterface::class);
$bootstrap = $this->createMock(AddonBootstrap::class);
$bootstrap->expects($this->once())->method('initAddon')->willReturnCallback(function (AddonStartEvent $event) {
$dependencies = $event->getDependencies();
$this->assertArrayHasKey(LoggerInterface::class, $dependencies);
$this->assertInstanceOf(LoggerInterface::class, $dependencies[LoggerInterface::class]);
$bootstrap->expects($this->once())->method('initAddon')->willReturnCallback(function (AddonStartEvent $event) use ($container) {
$this->assertSame($container, $event->getContainer());
});
$addon = new AddonProxy('id', $bootstrap);
$addon->initAddon(
[LoggerInterface::class => $this->createStub(LoggerInterface::class)]
);
$addon->initAddon($container);
}
public function testInstallAddonCallsBootstrap(): void
@ -138,7 +135,7 @@ class AddonProxyTest extends TestCase
$addon = new AddonProxy('id', $bootstrap);
$addon->initAddon([]);
$addon->initAddon($this->createStub(ContainerInterface::class));
$addon->installAddon();
}
@ -149,7 +146,7 @@ class AddonProxyTest extends TestCase
$addon = new AddonProxy('id', $bootstrap);
$addon->initAddon([]);
$addon->initAddon($this->createStub(ContainerInterface::class));
$addon->uninstallAddon();
}
}

View file

@ -13,6 +13,7 @@ use Friendica\Service\Addon\Addon;
use Friendica\Service\Addon\LegacyAddonProxy;
use org\bovigo\vfs\vfsStream;
use PHPUnit\Framework\TestCase;
use Psr\Container\ContainerInterface;
class LegacyAddonProxyTest extends TestCase
{
@ -85,7 +86,7 @@ class LegacyAddonProxyTest extends TestCase
$addon = new LegacyAddonProxy('helloaddon', $root->url());
try {
$addon->initAddon([]);
$addon->initAddon($this->createStub(ContainerInterface::class));
} catch (\Throwable $th) {
$this->assertSame(
'Addon loaded',
@ -105,7 +106,7 @@ class LegacyAddonProxyTest extends TestCase
$addon = new LegacyAddonProxy('helloaddon', $root->url());
try {
$addon->initAddon([]);
$addon->initAddon($this->createStub(ContainerInterface::class));
} catch (\Exception $th) {
$this->assertSame(
'Addon loaded',
@ -113,8 +114,8 @@ class LegacyAddonProxyTest extends TestCase
);
}
$addon->initAddon([]);
$addon->initAddon([]);
$addon->initAddon($this->createStub(ContainerInterface::class));
$addon->initAddon($this->createStub(ContainerInterface::class));
}
public function testInstallAddonWillCallInstallFunction(): void
@ -127,7 +128,7 @@ class LegacyAddonProxyTest extends TestCase
$addon = new LegacyAddonProxy('helloaddon', $root->url());
$addon->initAddon([]);
$addon->initAddon($this->createStub(ContainerInterface::class));
try {
$addon->installAddon();
} catch (\Exception $th) {
@ -148,7 +149,7 @@ class LegacyAddonProxyTest extends TestCase
$addon = new LegacyAddonProxy('helloaddon', $root->url());
$addon->initAddon([]);
$addon->initAddon($this->createStub(ContainerInterface::class));
try {
$addon->uninstallAddon();
} catch (\Exception $th) {

View file

@ -32,7 +32,7 @@ class HelloAddon implements AddonBootstrap, DependencyProvider, InstallableAddon
*
* The array should contain FQCN of the required services.
*
* The dependencies will be passed to the initAddon() method via AddonStartEvent::getDependencies().
* The dependencies will be passed as a PSR-11 Container to the initAddon() method via AddonStartEvent::getContainer().
*/
public function getRequiredDependencies(): array
{
@ -87,12 +87,9 @@ class HelloAddon implements AddonBootstrap, DependencyProvider, InstallableAddon
public function initAddon(AddonStartEvent $event): void
{
// $dependencies containts an array of services defined in getRequiredDependencies().
// The keys are the FQCN of the services.
// The values are the instances of the services.
$dependencies = $event->getDependencies();
$container = $event->getContainer();
$this->logger = $dependencies[LoggerInterface::class];
$this->logger = $container->get(LoggerInterface::class);
$this->logger->info('Hello from HelloAddon');
}