Skip to content

Commit

Permalink
Rework PluginContainer to fix singleton instance problems with multip…
Browse files Browse the repository at this point in the history
…le plugins
  • Loading branch information
thojou committed Feb 28, 2024
1 parent 8b1e111 commit 24b06ff
Show file tree
Hide file tree
Showing 7 changed files with 279 additions and 41 deletions.
29 changes: 29 additions & 0 deletions src/DI/PluginContainerException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php

declare(strict_types=1);

/*
* This file is part of the ilias-plugin-utils Library for ILIAS.
*
* (c) Thomas Joußen <[email protected]>
*
* This source file is subject to the GPL-3.0 license that is bundled
* with this source code in the file LICENSE.
*/

namespace Thojou\Ilias\Plugin\Utils\DI;

use Exception;
use Psr\Container\ContainerExceptionInterface;

/**
* PluginContainerException
*
* This class represents an exception that is thrown when a container error occurs.
*
* @author Thomas Joußen <[email protected]>
* @see https://www.php-fig.org/psr/psr-11/#32-psrcontainercontainerexceptioninterface
*/
class PluginContainerException extends \RuntimeException implements ContainerExceptionInterface
{
}
51 changes: 51 additions & 0 deletions src/DI/PluginContainerInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?php

declare(strict_types=1);

/*
* This file is part of the ilias-plugin-utils Library for ILIAS.
*
* (c) Thomas Joußen <[email protected]>
*
* This source file is subject to the GPL-3.0 license that is bundled
* with this source code in the file LICENSE.
*/

namespace Thojou\Ilias\Plugin\Utils\DI;

use ILIAS\DI\Container;
use Psr\Container\ContainerInterface;

/**
* PluginContainerInterface
*
* This interface represents a container for managing services and dependencies related to an ILIAS plugin.
*
* @author Thomas Joußen <[email protected]>
*/
interface PluginContainerInterface
{
/**
* Register a service in the container.
*
* @param string $id The key to access the service.
* @param callable $factory The function to register the service.
*
* @return self
*/
public function register(string $id, callable $factory): self;

/**
* Get the core DI container.
*
* @return Container The ILIAS DI container.
*/
public function core(): Container;

/**
* Get the plugin DI container.
*
* @return ContainerInterface The Plugin DI container.
*/
public function plugin(): ContainerInterface;
}
28 changes: 28 additions & 0 deletions src/DI/PluginContainerNotFoundException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

declare(strict_types=1);

/*
* This file is part of the ilias-plugin-utils Library for ILIAS.
*
* (c) Thomas Joußen <[email protected]>
*
* This source file is subject to the GPL-3.0 license that is bundled
* with this source code in the file LICENSE.
*/

namespace Thojou\Ilias\Plugin\Utils\DI;

use Psr\Container\NotFoundExceptionInterface;

/**
* PluginContainerNotFoundException
*
* This class represents an exception that is thrown when the entry was not found in the container.
*
* @author Thomas Joußen <[email protected]>
* @see https://www.php-fig.org/psr/psr-11/#33-psrcontainernotfoundexceptioninterface
*/
class PluginContainerNotFoundException extends \Exception implements NotFoundExceptionInterface
{
}
61 changes: 31 additions & 30 deletions src/DI/PluginContainer.php → src/DI/PluginContainerTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,30 +14,41 @@
namespace Thojou\Ilias\Plugin\Utils\DI;

use ILIAS\DI\Container;

use Psr\Container\ContainerInterface;
use RuntimeException;

use function is_object;

/**
* PluginContainer
* PluginContainerTrait
*
* This class represents a container for managing services and dependencies related to an ILIAS plugin.
* This trait represents a container for managing services and dependencies related to an ILIAS plugin.
* The PluginContainerTrait provides the implementation of the PluginContainerInterface.
*
* @author Thomas Joußen <[email protected]>
*/
class PluginContainer
trait PluginContainerTrait
{
/**
* @var PluginContainer|null A singleton instance of the PluginContainer class.
* @var PluginContainerInterface|null A singleton instance of the PluginContainer class.
*/
private static ?PluginContainer $instance = null;
private static ?PluginContainerInterface $instance = null;

/**
* @var Container The DI (Dependency Injection) container.
*/
private Container $dic;

/**
* @var ContainerInterface The core DI container.
*/
private ContainerInterface $core;

/**
* @var ContainerInterface The plugin DI container.
*/
private ContainerInterface $plugin;

/**
* @var string The ID of the associated plugin.
*/
Expand All @@ -49,20 +60,20 @@ class PluginContainer
* @param Container $dic The DI container to be used.
* @param string $pluginId The ID of the associated plugin.
*
* @return self
* @return PluginContainerInterface
*/
public static function init(Container $dic, string $pluginId): self
public static function init(Container $dic, string $pluginId): PluginContainerInterface
{
return self::$instance = new self($dic, $pluginId);
}

/**
* Get the singleton instance of PluginContainer.
*
* @return self
* @return PluginContainerInterface
* @throws RuntimeException If the PluginContainer is not initialized.
*/
public static function get(): self
public static function get(): PluginContainerInterface
{
if (!self::$instance) {
throw new RuntimeException('PluginContainer not initialized');
Expand All @@ -80,50 +91,40 @@ private function __construct(Container $dic, string $pluginId)
{
$this->pluginId = $pluginId;
$this->dic = $dic;
$this->plugin = new PluginContainerWrapper($dic, $pluginId);
}

/**
* Get the core DI container.
*
* @return Container The DI container.
* @return Container The ILIAS DI container.
*/
public function core(): Container
{
return $this->dic;
}

/**
* Get a service from the container.
*
* @template T of object
*
* @param class-string<T> $key The key to access the service.
* Get the plugin DI container.
*
* @return T The requested service.
* @throws RuntimeException If the service is not found.
* @return ContainerInterface The Plugin DI container.
*/
public function getService(string $key): object
public function plugin(): ContainerInterface
{
$service = $this->dic[$this->pluginId . '.' . $key];

if (!is_object($service)) {
throw new RuntimeException("Service $key not found for plugin $this->pluginId");
}

return $service; //@phpstan-ignore-line
return $this->plugin;
}

/**
* Register a service in the container.
*
* @param string $key The key to access the service.
* @param callable $registerFunction The function to register the service.
* @param string $id The key to access the service.
* @param callable $factory The function to register the service.
*
* @return self
*/
public function register(string $key, callable $registerFunction): self
public function register(string $id, callable $factory): self
{
$this->dic[$this->pluginId . '.' . $key] = $registerFunction;
$this->dic[$this->pluginId . '.' . $id] = $factory;

return $this;
}
Expand Down
108 changes: 108 additions & 0 deletions src/DI/PluginContainerWrapper.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
<?php

declare(strict_types=1);

/*
* This file is part of the ilias-plugin-utils Library for ILIAS.
*
* (c) Thomas Joußen <[email protected]>
*
* This source file is subject to the GPL-3.0 license that is bundled
* with this source code in the file LICENSE.
*/

namespace Thojou\Ilias\Plugin\Utils\DI;

use Exception;
use ILIAS\DI\Container;
use Psr\Container\ContainerInterface;

/**
* PluginContainerWrapper
*
* This class represents a wrapper for the ILIAS container to make it compatible with the PSR-11 container interface.
*
* @author Thomas Joußen <[email protected]>
*/
class PluginContainerWrapper implements ContainerInterface
{
/**
* @var Container The ILIAS container.
*/
private Container $container;

/**
* @var string|null The key prefix for the container.
*/
private ?string $keyPrefix;

/**
* PluginContainerWrapper constructor.
*
* @param Container $container The ILIAS container.
* @param string|null $keyPrefix The key prefix for the container.
*/
public function __construct(
Container $container,
?string $keyPrefix = null
) {
$this->container = $container;
$this->keyPrefix = $keyPrefix;
}

/**
* Get a service from the container.
*
* @param class-string<T> $id The id to access the service.
*
* @template T of mixed
*
* @return T The requested service.
* @throws PluginContainerException
* @throws PluginContainerNotFoundException
*/
public function get(string $id)
{
$id = $this->resolveId($id);

if(!$this->has($id)) {
throw new PluginContainerNotFoundException("Service with id '$id' not found in PluginContainer.");
}

try {
return $this->container[$id];
} catch (Exception $e) { // @phpstan-ignore-line
throw new PluginContainerException("Error while getting service with id '$id' from PluginContainer.", 0, $e);
}
}

/**
* Check if a service is registered in the container.
*
* @param string $id The id to access the service.
*
* @return bool True if the service is registered, false otherwise.
*/
public function has(string $id): bool
{
$id = $this->resolveId($id);

return isset($this->container[$id]);
}

/**
* Resolve the id with the key prefix.
*
* @param string $id
*
* @return string
*/
private function resolveId(string $id): string
{
if ($this->keyPrefix === null) {
return $id;
}

return sprintf("%s.%s", $this->keyPrefix, $id);
}
}
11 changes: 11 additions & 0 deletions tests/DI/Fixtures/TestPluginContainer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

namespace Thojou\Ilias\Plugin\Utils\Tests\DI\Fixtures;

use Thojou\Ilias\Plugin\Utils\DI\PluginContainerInterface;
use Thojou\Ilias\Plugin\Utils\DI\PluginContainerTrait;

class TestPluginContainer implements PluginContainerInterface
{
use PluginContainerTrait;
}
Loading

0 comments on commit 24b06ff

Please sign in to comment.