Skip to content

Commit

Permalink
add support for external containers
Browse files Browse the repository at this point in the history
  • Loading branch information
klimov-paul committed Jul 28, 2023
1 parent 4d655cb commit cec90ce
Show file tree
Hide file tree
Showing 5 changed files with 331 additions and 0 deletions.
34 changes: 34 additions & 0 deletions src/external/ContainerBasedInjector.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

namespace yii1tech\di\external;

use Psr\Container\ContainerInterface;
use yii1tech\di\InjectorContract;

/**
* ContainerBasedInjector forwards dependency injection resolution to the container.
*
* It expects passing container to implement methods `make()` and `call()`, since it is widely used notation.
* This injection can be used with some external (3rd party) containers.
*
* @author Paul Klimov <[email protected]>
* @since 1.0
*/
class ContainerBasedInjector implements InjectorContract
{
/**
* {@inheritdoc}
*/
public function invoke(ContainerInterface $container, callable $callable, array $arguments = [])
{
return $container->call($callable, $arguments);
}

/**
* {@inheritdoc}
*/
public function make(ContainerInterface $container, string $class, array $arguments = [])
{
return $container->make($class, $arguments);
}
}
132 changes: 132 additions & 0 deletions src/external/ContainerProxy.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
<?php

namespace yii1tech\di\external;

use Psr\Container\ContainerInterface;

/**
* ContainerProxy wraps another container and forwards all method calls to it.
*
* This class can be used to resolve inconsistencies in particular container implementation.
* Usually to fix behavior of `has()` method.
*
* For example:
*
* ```php
* class PhpDiContainerProxy extends ContainerProxy
* {
* public function has(string $id): bool
* {
* return in_array($id, $this->container->getKnownEntryNames(), true);
* }
* }
* ```
*
* @author Paul Klimov <[email protected]>
* @since 1.0
*/
class ContainerProxy implements ContainerInterface
{
/**
* @var \Psr\Container\ContainerInterface wrapped container instance.
*/
protected $container;

/**
* Constructor.
*
* @param \Psr\Container\ContainerInterface $container container instance to be wrapped.
*/
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}

/**
* {@inheritdoc}
*/
public function get(string $id)
{
return $this->container->get($id);
}

/**
* {@inheritdoc}
*/
public function has(string $id): bool
{
return $this->container->has($id);
}

/**
* Calls the named method which is not a class method.
* Do not call this method. This is a PHP magic method that forwards calls to the wrapped container.
*
* @param string $name the method name.
* @param array $parameters method parameters.
* @return mixed the method return value.
*/
public function __call($name, $parameters)
{
return call_user_func_array([$this->container, $name], $parameters);
}

/**
* Returns a property value.
* Do not call this method. This is a PHP magic method that forwards property resolution to the wrapped container.
*
* @param string $name the property name.
* @return mixed the property value.
*/
public function __get($name)
{
return $this->container->$name;
}

/**
* Sets a property value.
* Do not call this method. This is a PHP magic method that forwards property resolution to the wrapped container.
*
* @param string $name the property name.
* @param mixed $value the property value.
*/
public function __set($name, $value)
{
$this->container->$name = $value;
}

/**
* Checks if a property value is null.
* Do not call this method. This is a PHP magic method that forwards property resolution to the wrapped container.
*
* @param string $name the property name.
* @return bool whether property it `null`.
*/
public function __isset($name)
{
return isset($this->container->$name);
}

/**
* Sets a component property to be null.
* Do not call this method. This is a PHP magic method that forwards property resolution to the wrapped container.
*
* @param string $name the property name.
*/
public function __unset($name)
{
unset($this->container->$name);
}

/**
* Creates new self instance.
* This method can be useful when writing chain methods calls.
*
* @param mixed ...$args constructor arguments.
* @return static new self instance.
*/
public static function new(...$args): self
{
return new static(...$args);
}
}
88 changes: 88 additions & 0 deletions tests/external/ContainerBasedInjectorTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
<?php

namespace yii1tech\di\test\external;

use CDbConnection;
use CDummyCache;
use ICache;
use yii1tech\di\external\ContainerBasedInjector;
use yii1tech\di\test\support\DummyWithDependency;
use yii1tech\di\test\support\SelfInjectingContainer;
use yii1tech\di\test\TestCase;

class ContainerBasedInjectorTest extends TestCase
{
public function testMake(): void
{
$container = new SelfInjectingContainer();
$container->instance(CDbConnection::class, new CDbConnection());
$container->instance(ICache::class, new CDummyCache());

$injector = new ContainerBasedInjector();

/** @var DummyWithDependency $object */
$object = $injector->make($container, DummyWithDependency::class);

$this->assertTrue($object instanceof DummyWithDependency);
$this->assertSame($container->get(CDbConnection::class), $object->constructorArgs[0]);
$this->assertSame($container->get(ICache::class), $object->constructorArgs[1]);
$this->assertSame(null, $object->constructorArgs[2]);
$this->assertSame('tail', $object->constructorArgs[3]);
}

/**
* @depends testMake
*/
public function testMakeWithArguments(): void
{
$container = new SelfInjectingContainer();
$container->instance(CDbConnection::class, new CDbConnection());

$injector = new ContainerBasedInjector();

$cache = new CDummyCache();
$tail = 'explicit-set-tail';

/** @var DummyWithDependency $object */
$object = $injector->make($container, DummyWithDependency::class, ['cache' => $cache, 'tail' => $tail]);

$this->assertTrue($object instanceof DummyWithDependency);
$this->assertSame($cache, $object->constructorArgs[1]);
$this->assertSame($tail, $object->constructorArgs[3]);
}

public function testInvoke(): void
{
$container = new SelfInjectingContainer();
$container->instance(CDbConnection::class, new CDbConnection());
$container->instance(ICache::class, new CDummyCache());

$injector = new ContainerBasedInjector();

$result = $injector->invoke($container, [DummyWithDependency::class, 'returnArguments']);

$this->assertSame($container->get(CDbConnection::class), $result[0]);
$this->assertSame($container->get(ICache::class), $result[1]);
$this->assertSame(null, $result[2]);
$this->assertSame('tail', $result[3]);
}

/**
* @depends testInvoke
*/
public function testInvokeWithArguments(): void
{
$container = new SelfInjectingContainer();
$container->instance(CDbConnection::class, new CDbConnection());

$injector = new ContainerBasedInjector();

$cache = new CDummyCache();
$tail = 'explicit-set-tail';

$result = $injector->invoke($container, [DummyWithDependency::class, 'returnArguments'], ['cache' => $cache, 'tail' => $tail]);

$this->assertSame($cache, $result[1]);
$this->assertSame($tail, $result[3]);
}
}
49 changes: 49 additions & 0 deletions tests/external/ContainerProxyTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?php

namespace yii1tech\di\test\external;

use ArrayObject;
use yii1tech\di\Container;
use yii1tech\di\external\ContainerProxy;
use yii1tech\di\test\TestCase;

class ContainerProxyTest extends TestCase
{
public function testHas(): void
{
$container = new Container();

$container->instance(ArrayObject::class, new ArrayObject());

$proxy = new ContainerProxy($container);

$this->assertTrue($proxy->has(ArrayObject::class));
$this->assertFalse($proxy->has('unexistint-id'));
}

public function testGet(): void
{
$container = new Container();

$object = new ArrayObject();
$container->instance(ArrayObject::class, $object);

$proxy = new ContainerProxy($container);

$this->assertSame($object, $proxy->get(ArrayObject::class));
}

/**
* @depends testHas
*/
public function testForwardCall(): void
{
$container = new Container();

$proxy = new ContainerProxy($container);

$proxy->instance(ArrayObject::class, new ArrayObject());

$this->assertTrue($proxy->has(ArrayObject::class));
}
}
28 changes: 28 additions & 0 deletions tests/support/SelfInjectingContainer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

namespace yii1tech\di\test\support;

use yii1tech\di\Container;
use yii1tech\di\external\ContainerProxy;
use yii1tech\di\Injector;

/**
* @method instance(string $id, mixed $object)
*/
class SelfInjectingContainer extends ContainerProxy
{
public function __construct()
{
parent::__construct(new Container());
}

public function call(callable $callable, array $arguments = [])
{
return (new Injector())->invoke($this, $callable, $arguments);
}

public function make(string $class, array $arguments = [])
{
return (new Injector())->make($this, $class, $arguments);
}
}

0 comments on commit cec90ce

Please sign in to comment.