diff --git a/composer.json b/composer.json index d14b292..dc998a3 100644 --- a/composer.json +++ b/composer.json @@ -17,7 +17,7 @@ }, "optimize-autoloader": true, "platform": { - "php": "7.4.26" + "php": "7.4" }, "sort-packages": true }, @@ -28,7 +28,7 @@ "ext-json": "*", "ext-openssl": "*", "johnbillion/args": "^1.1", - "psr/container": "^2.0.2" + "psr/container": "^1.0 || ^2.0" }, "require-dev": { "ext-simplexml": "*", diff --git a/src/Plugin/AbstractContainerProvider.php b/src/Plugin/AbstractContainerProvider.php index 5d95391..f0518e5 100644 --- a/src/Plugin/AbstractContainerProvider.php +++ b/src/Plugin/AbstractContainerProvider.php @@ -2,6 +2,8 @@ namespace TheFrosty\WpUtilities\Plugin; +use Psr\Container\ContainerInterface; + /** * Class AbstractContainerProvider * @package TheFrosty\WpUtilities\Plugin @@ -12,13 +14,11 @@ abstract class AbstractContainerProvider implements WpHooksInterface, PluginAwar /** * AbstractContainerProvider constructor. - * @param Container|null $container Set the container, or use `$this->setContainer($container)`. + * @param Container|ContainerInterface|null $container Set the container, or use `$this->setContainer($container)`. */ - public function __construct(?Container $container = null) + public function __construct(?ContainerInterface $container = null) { - if ($container) { - $this->setContainer($container); - } + $this->setContainer($container); } /** diff --git a/src/Plugin/Container.php b/src/Plugin/Container.php index 9474d14..6c3e535 100644 --- a/src/Plugin/Container.php +++ b/src/Plugin/Container.php @@ -10,9 +10,9 @@ /** * Container class. - * Extends Pimple to satisfy the ContainerInterface. + * Extends Pimple to satisfy the ContainerInterface >= v2.0.0. + * @ref https://github.com/php-fig/container/blob/2.0.0/src/ContainerInterface.php * @package TheFrosty\WpUtilities\Plugin - * @SuppressWarnings(PHPMD.ShortVariable) */ class Container extends Pimple implements ContainerInterface { diff --git a/src/Plugin/Container100.php b/src/Plugin/Container100.php new file mode 100644 index 0000000..071dae3 --- /dev/null +++ b/src/Plugin/Container100.php @@ -0,0 +1,47 @@ +offsetGet($id); + } + + /** + * Returns true if the container can return an entry for the given identifier. + * Returns false otherwise. + * + * `has($id)` returning true does not mean that `get($id)` will not throw an exception. + * It does however mean that `get($id)` will not throw a `NotFoundExceptionInterface`. + * + * @param string $id Identifier of the entry to look for. + * @return bool + */ + public function has($id) + { + return $this->offsetExists($id); + } +} diff --git a/src/Plugin/Container110.php b/src/Plugin/Container110.php new file mode 100644 index 0000000..3cf26c9 --- /dev/null +++ b/src/Plugin/Container110.php @@ -0,0 +1,47 @@ +offsetGet($id); + } + + /** + * Returns true if the container can return an entry for the given identifier. + * Returns false otherwise. + * + * `has($id)` returning true does not mean that `get($id)` will not throw an exception. + * It does however mean that `get($id)` will not throw a `NotFoundExceptionInterface`. + * + * @param string $id Identifier of the entry to look for. + * @return bool + */ + public function has(string $id) + { + return $this->offsetExists($id); + } +} diff --git a/src/Plugin/ContainerAwareTrait.php b/src/Plugin/ContainerAwareTrait.php index 6236baa..7c557eb 100644 --- a/src/Plugin/ContainerAwareTrait.php +++ b/src/Plugin/ContainerAwareTrait.php @@ -16,10 +16,10 @@ trait ContainerAwareTrait { /** - * Container. - * @var ContainerInterface + * Container instance. + * @var ContainerInterface|null */ - private ContainerInterface $container; + private ?ContainerInterface $container; /** * Proxy access to container services. @@ -29,7 +29,7 @@ trait ContainerAwareTrait */ public function __get(string $name) { - return $this->container->get($name); + return $this->container && $this->container->get($name); } /** @@ -40,7 +40,7 @@ public function __get(string $name) */ public function __isset(string $name): bool { - return $this->container->has($name); + return $this->container && $this->container->has($name); } /** @@ -53,7 +53,7 @@ public function __isset(string $name): bool */ public function __call(string $method, array $args) { - if ($this->container->has($method)) { + if ($this->container && $this->container->has($method)) { $object = $this->container->get($method); if (\is_callable($object)) { return \call_user_func_array($object, $args); @@ -66,9 +66,9 @@ public function __call(string $method, array $args) /** * Enable access to the DI container by plugin consumers. * - * @return ContainerInterface + * @return ContainerInterface|null */ - public function getContainer(): ContainerInterface + public function getContainer(): ?ContainerInterface { return $this->container; } @@ -76,10 +76,10 @@ public function getContainer(): ContainerInterface /** * Set the container. * - * @param ContainerInterface $container Dependency injection container. + * @param ContainerInterface|null $container Dependency injection container. * @return $this */ - public function setContainer(ContainerInterface $container): self + public function setContainer(?ContainerInterface $container = null): self { $this->container = $container; diff --git a/src/Plugin/PluginFactory.php b/src/Plugin/PluginFactory.php index a10b2c8..efe7b27 100644 --- a/src/Plugin/PluginFactory.php +++ b/src/Plugin/PluginFactory.php @@ -3,6 +3,7 @@ namespace TheFrosty\WpUtilities\Plugin; use Psr\Container\ContainerInterface; +use ReflectionMethod; /** * Class PluginFactory @@ -34,10 +35,9 @@ public static function getInstance(string $slug): Plugin * @param string $slug Plugin slug. * @param string|null $filename Optional. Absolute path to the main plugin file. * This should be passed if the calling file is not the main plugin file. - * @param ContainerInterface|null $container * @return Plugin A Plugin object instance. */ - public static function create(string $slug, ?string $filename = '', ?ContainerInterface $container = null): Plugin + public static function create(string $slug, ?string $filename = ''): Plugin { if (isset(self::$instances[$slug]) && self::$instances[$slug] instanceof Plugin) { return self::$instances[$slug]; @@ -45,7 +45,7 @@ public static function create(string $slug, ?string $filename = '', ?ContainerIn // Use the calling file as the main plugin file. if (empty($filename)) { // @codingStandardsIgnoreStart - $backtrace = \debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT, 1); + $backtrace = \debug_backtrace(\DEBUG_BACKTRACE_PROVIDE_OBJECT, 1); $filename = $backtrace[0]['file']; // @codingStandardsIgnoreEnd } @@ -58,7 +58,7 @@ public static function create(string $slug, ?string $filename = '', ?ContainerIn ->setSlug($slug) ->setUrl(\plugin_dir_url($filename)); - $plugin = self::setContainer($plugin, $container); + $plugin = self::setContainer($plugin); $plugin->setTemplateLoader(new TemplateLoader($plugin)); self::$instances[$slug] = $plugin; @@ -69,15 +69,35 @@ public static function create(string $slug, ?string $filename = '', ?ContainerIn * Set the Pimple\Container if it's available. * * @param Plugin $plugin - * @param ContainerInterface|null $container * @return Plugin */ - private static function setContainer(Plugin $plugin, ?ContainerInterface $container = null): Plugin + private static function setContainer(Plugin $plugin): Plugin { - if (\class_exists('\Pimple\Container') && \interface_exists('\Psr\Container\ContainerInterface')) { - $plugin->setContainer($container ?? new Container()); + if ( + \class_exists('\Pimple\Container') && + \interface_exists('\Psr\Container\ContainerInterface') + ) { + $plugin->setContainer(self::getContainer()); } return $plugin; } + + /** + * Return a "Container" stub for ContainerInterface v1.0.0, v1.1.0 and/or >= v2.0.0. + * @return ContainerInterface + */ + private static function getContainer(): ContainerInterface + { + $reflection = new ReflectionMethod('\Psr\Container\ContainerInterface', 'has'); + if ($reflection->hasReturnType() && $reflection->getReturnType()) { + return new Container(); // ContainerInterface >= 2.0.0 + } + $parameter = $reflection->getParameters()[0] ?? null; + if ($parameter && $parameter->getType() && $parameter->getType()->getName() === 'string') { + return new Container110(); + } + + return new Container100(); + } } diff --git a/tests/unit/Plugin/HooksTraitTest.php b/tests/unit/Plugin/HooksTraitTest.php index 38f53b6..61edc03 100644 --- a/tests/unit/Plugin/HooksTraitTest.php +++ b/tests/unit/Plugin/HooksTraitTest.php @@ -19,7 +19,7 @@ public function testRegisterFilters(): void { $provider = $this->getMockProvider(HookProvider::class); $provider->expects($this->exactly(1)) - ->method('addFilter') + ->method(self::METHOD_ADD_FILTER) ->will($this->returnCallback(function ($hook, $method, $priority, $arg_count) { TestCase::assertSame('theTitle', $hook); TestCase::assertSame(10, $priority); @@ -38,7 +38,7 @@ public function testRegisterActions(): void { $provider = $this->getMockProvider(HookProvider::class); $provider->expects($this->exactly(1)) - ->method('addFilter') + ->method(self::METHOD_ADD_FILTER) ->will($this->returnCallback(function ($hook, $method, $priority, $arg_count) { TestCase::assertSame('template_redirect', $hook); TestCase::assertSame(10, $priority); diff --git a/tests/unit/Plugin/PluginAwareTraitTest.php b/tests/unit/Plugin/PluginAwareTraitTest.php index 708bed4..628997d 100644 --- a/tests/unit/Plugin/PluginAwareTraitTest.php +++ b/tests/unit/Plugin/PluginAwareTraitTest.php @@ -2,6 +2,7 @@ namespace TheFrosty\WpUtilities\Tests\Plugin; +use ReflectionClass; use TheFrosty\WpUtilities\Plugin\Plugin; use TheFrosty\WpUtilities\Plugin\PluginAwareTrait; use TheFrosty\WpUtilities\Tests\Plugin\Framework\TestCase; @@ -15,26 +16,24 @@ class PluginAwareTraitTest extends TestCase { /** - * Test set_plugin() + * Test setPlugin() */ - public function test_set_plugin() + public function testSetPlugin(): void { - $provider = $this->getMockForTrait(PluginAwareTrait::class); + $provider = new class { - try { - $class = new \ReflectionClass($provider); - $property = $class->getProperty('plugin'); - $property->setAccessible(true); - } catch (\ReflectionException $exception) { - $this->assertInstanceOf(\ReflectionException::class, $exception); + use PluginAwareTrait; + }; - return; - } + $class = new ReflectionClass($provider); + $property = $class->getProperty('plugin'); + $property->setAccessible(true); $plugin = new Plugin(); /** PluginAwareTrait @var PluginAwareTrait $provider */ $provider->setPlugin($plugin); $this->assertSame($plugin, $property->getValue($provider)); + $this->assertSame($plugin, $provider->getPlugin()); } } diff --git a/tests/unit/Plugin/PluginRegisterHooksTest.php b/tests/unit/Plugin/PluginRegisterHooksTest.php index 9060cb6..78448ca 100644 --- a/tests/unit/Plugin/PluginRegisterHooksTest.php +++ b/tests/unit/Plugin/PluginRegisterHooksTest.php @@ -2,6 +2,7 @@ namespace TheFrosty\WpUtilities\Tests\Plugin; +use ReflectionClass; use TheFrosty\WpUtilities\Plugin\WpHooksInterface; use TheFrosty\WpUtilities\Plugin\AbstractHookProvider; use TheFrosty\WpUtilities\Plugin\Plugin; @@ -17,12 +18,12 @@ class PluginRegisterHooksTest extends TestCase /** * Test AbstractHookProvider */ - public function test_register_hooks(): void + public function testRegisterHooks(): void { $provider = $this->getMockProviderForAbstractClass(AbstractHookProvider::class); try { - $class = new \ReflectionClass($provider); + $class = new ReflectionClass($provider); $property = $class->getProperty('plugin'); $property->setAccessible(true); } catch (\ReflectionException $exception) {