Skip to content

Commit

Permalink
Fix using factory wrapped by anonymous function
Browse files Browse the repository at this point in the history
  • Loading branch information
devanych committed Oct 20, 2020
1 parent c8ae205 commit 20f4434
Show file tree
Hide file tree
Showing 4 changed files with 39 additions and 45 deletions.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,9 @@ final class UserProfileFactory implements \Devanych\Di\FactoryInterface

$container->setMultiple([
UserProfile::class => UserProfileFactory::class,
// Or without autowiring
// UserProfile::class => fn => UserProfileFactory(),
// UserProfile::class => new UserProfileFactory(),
'user_name' => 'Alexander',
'user_age' => 40,
]);
Expand Down
43 changes: 12 additions & 31 deletions src/Container.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,10 @@
use Psr\Container\ContainerInterface;
use ReflectionClass;
use ReflectionException;
use Throwable;

use function array_key_exists;
use function class_exists;
use function gettype;
use function in_array;
use function is_string;
use function sprintf;

Expand Down Expand Up @@ -84,7 +82,7 @@ public function get($id)
return $this->instances[$id];
}

$this->instances[$id] = $this->createInstance($id);
$this->instances[$id] = $this->getNew($id);
return $this->instances[$id];
}

Expand All @@ -95,10 +93,17 @@ public function get($id)
* @return mixed
* @throws NotFoundException If not found definition in the container.
* @throws ContainerException If unable to create instance.
* @psalm-suppress MixedAssignment
*/
public function getNew(string $id)
{
return $this->createInstance($id);
$instance = $this->createInstance($id);

if ($instance instanceof FactoryInterface) {
return $instance->create($this);
}

return $instance;
}

/**
Expand Down Expand Up @@ -161,10 +166,13 @@ private function createInstance(string $id)
/**
* Create object by class name.
*
* If the object has dependencies in the constructor, it tries to create them too.
*
* @param string $className
* @return object
* @throws ContainerException If unable to create object.
* @psalm-suppress ArgumentTypeCoercion
* @psalm-suppress MixedAssignment
*/
private function createObject(string $className): object
{
Expand All @@ -174,33 +182,6 @@ private function createObject(string $className): object
throw new ContainerException(sprintf('Unable to create object `%s`.', $className), 0, $e);
}

if (in_array(FactoryInterface::class, $reflection->getInterfaceNames(), true)) {
try {
/** @var FactoryInterface $factory */
$factory = $this->getObjectFromReflection($reflection);
return $factory->create($this);
} catch (ContainerException $e) {
throw $e;
} catch (Throwable $e) {
throw new ContainerException(sprintf('Unable to create object `%s`.', $className), 0, $e);
}
}

return $this->getObjectFromReflection($reflection);
}

/**
* Create object from reflection.
*
* If the object has dependencies in the constructor, it tries to create them too.
*
* @param ReflectionClass $reflection
* @return object
* @throws ContainerException If unable to create object.
* @psalm-suppress MixedAssignment
*/
private function getObjectFromReflection(ReflectionClass $reflection): object
{
if (($constructor = $reflection->getConstructor()) === null) {
return $reflection->newInstance();
}
Expand Down
21 changes: 9 additions & 12 deletions src/FactoryInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,32 +16,29 @@ interface FactoryInterface
* Example of use:
*
* ```php
* use Devanych\Di\Container;
* use Devanych\Di\FactoryInterface;
* use Psr\Container\ContainerInterface;
*
* // Example of an ApplicationFactory test class:
* final class ApplicationFactory implements \Devanych\Di\FactoryInterface
* final class ApplicationFactory implements FactoryInterface
* {
* public ?string $environment;
*
* public function __construct(string $environment = null)
* {
* $this->environment = $environment;
* }
*
* public function create(ContainerInterface $container): Application
* {
* return new Application(
* $container->get(Router::class),
* $container->get(EmitterInterface::class),
* $this->environment ?? $container->get('environment'),
* $container->get('environment'),
* );
* }
* }
*
* // Example of setting dependencies:
* $container = new \Devanych\Di\Container([
* $container = new Container([
* 'environment' => 'development',
* Application::class => ApplicationFactory::class,
* Router::class => RouterFactory::class,
* EmitterInterface::class => EmitterFactory::class,
* Router::class => fn() => new RouterFactory(),
* EmitterInterface::class => new EmitterFactory(),
* ]);
*
* // Creating an Application instance:
Expand Down
17 changes: 15 additions & 2 deletions tests/ContainerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -135,10 +135,23 @@ public function testGetSameObject(): void
$this->assertSame($instance1, $instance2);
}

public function testGetSameObjectFromFactory(): void
public function factoryDataProvider(): array
{
return [
'factory-class-name' => [DummyFactory::class],
'factory-callable' => [fn() => new DummyFactory()],
'factory-object' => [new DummyFactory()],
];
}

/**
* @dataProvider factoryDataProvider
* @param $factory
*/
public function testGetSameObjectFromFactory($factory): void
{
$container = new Container();
$container->set(DummyData::class, DummyFactory::class);
$container->set(DummyData::class, $factory);

$this->assertNotNull($instance1 = $container->get(DummyData::class));
$this->assertNotNull($instance2 = $container->get(DummyData::class));
Expand Down

0 comments on commit 20f4434

Please sign in to comment.