Skip to content

Commit

Permalink
#7 Added possibility to add abstract factories of type class-string
Browse files Browse the repository at this point in the history
  • Loading branch information
BlackBonjour committed Oct 23, 2024
1 parent 5696964 commit 258c6c4
Show file tree
Hide file tree
Showing 3 changed files with 70 additions and 6 deletions.
46 changes: 40 additions & 6 deletions src/ServiceManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
*/
class ServiceManager implements ArrayAccess, ContainerInterface
{
/** @var array<AbstractFactoryInterface> */
/** @var array<AbstractFactoryInterface|class-string> */
private array $abstractFactories;

/** @var array<string, FactoryInterface|callable|class-string> */
Expand All @@ -33,6 +33,9 @@ class ServiceManager implements ArrayAccess, ContainerInterface
/** @var array<class-string, class-string> */
private array $invokables;

/** @var array<string, AbstractFactoryInterface> */
private array $resolvedAbstractFactories = [];

/** @var array<string, FactoryInterface|callable> */
private array $resolvedFactories = [];

Expand All @@ -45,7 +48,7 @@ class ServiceManager implements ArrayAccess, ContainerInterface
/**
* @param array<string, mixed> $services
* @param array<string, FactoryInterface|callable|class-string> $factories
* @param array<AbstractFactoryInterface> $abstractFactories
* @param array<AbstractFactoryInterface|class-string> $abstractFactories
* @param array<string|int, class-string> $invokables
*/
public function __construct(
Expand Down Expand Up @@ -73,7 +76,8 @@ public function __construct(
// Validate abstract factories
foreach ($abstractFactories as $abstractFactory) {
assert(
$abstractFactory instanceof AbstractFactoryInterface,
$abstractFactory instanceof AbstractFactoryInterface
|| (is_string($abstractFactory) && class_exists($abstractFactory)),
sprintf('Abstract factories must implement %s!', AbstractFactoryInterface::class),
);
}
Expand All @@ -93,11 +97,21 @@ public function __construct(
$this->services = $services;
}

public function addAbstractFactory(AbstractFactoryInterface $abstractFactory): void
/**
* @throws ContainerException
*/
public function addAbstractFactory(AbstractFactoryInterface|string $abstractFactory): void
{
if (is_string($abstractFactory) && class_exists($abstractFactory) === false) {
throw new ContainerException(sprintf('Abstract factory "%s does not exist!"', $abstractFactory));
}

$this->abstractFactories[] = $abstractFactory;
}

/**
* @throws ContainerException
*/
public function addFactory(string $id, FactoryInterface|callable|string $factory): void
{
if (is_string($factory) && class_exists($factory) === false) {
Expand All @@ -107,6 +121,9 @@ public function addFactory(string $id, FactoryInterface|callable|string $factory
$this->factories[$id] = $factory;
}

/**
* @throws ContainerException
*/
public function addInvokable(string $id): void
{
if (class_exists($id) === false) {
Expand Down Expand Up @@ -208,11 +225,28 @@ public function removeService(string $id): void
);
}

/**
* @throws ContainerException
*/
private function getAbstractFactory(string $id): ?AbstractFactoryInterface
{
foreach ($this->abstractFactories as $abstractFactory) {
if ($abstractFactory->canCreate($this, $id)) {
return $abstractFactory;
if ($abstractFactory instanceof AbstractFactoryInterface) {
$factory = $abstractFactory;
} elseif (isset($this->resolvedAbstractFactories[$abstractFactory])) {
$factory = $this->resolvedAbstractFactories[$abstractFactory];
} else {
$factory = new $abstractFactory();

if ($factory instanceof AbstractFactoryInterface) {
$this->resolvedAbstractFactories[$abstractFactory] = $factory;
} else {
throw new ContainerException(sprintf('Abstract factory "%s" is invalid!', $abstractFactory));
}
}

if ($factory->canCreate($this, $id)) {
return $factory;
}
}

Expand Down
13 changes: 13 additions & 0 deletions test/Asset/FooBarFactoryWithoutInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

declare(strict_types=1);

namespace BlackBonjourTest\ServiceManager\Asset;

final class FooBarFactoryWithoutInterface
{
public function __invoke(): FooBar
{
return new FooBar('foo', 'bar');
}
}
17 changes: 17 additions & 0 deletions test/ServiceManagerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
use BlackBonjourTest\ServiceManager\Asset\FooBar;
use BlackBonjourTest\ServiceManager\Asset\FooBarFactory;
use BlackBonjourTest\ServiceManager\Asset\FooBarFactoryWithOptions;
use BlackBonjourTest\ServiceManager\Asset\FooBarFactoryWithoutInterface;
use PHPUnit\Framework\TestCase;
use stdClass;
use Throwable;
Expand All @@ -29,6 +30,14 @@ public function testAddAbstractFactory(): void
self::assertInstanceOf(FooBar::class, $manager[FooBar::class]);
}

public function testAddAbstractFactoryWithClassString(): void
{
$manager = new ServiceManager();
$manager->addAbstractFactory(DynamicFactory::class);

self::assertInstanceOf(FooBar::class, $manager[FooBar::class]);
}

public function testAddFactory(): void
{
$manager = new ServiceManager();
Expand All @@ -37,6 +46,14 @@ public function testAddFactory(): void
self::assertInstanceOf(FooBar::class, $manager[FooBar::class]);
}

public function testAddFactoryWithClassStringAndWithoutInterface(): void
{
$manager = new ServiceManager();
$manager->addFactory(FooBar::class, FooBarFactoryWithoutInterface::class);

self::assertInstanceOf(FooBar::class, $manager[FooBar::class]);
}

public function testAddInvokable(): void
{
$manager = new ServiceManager(invokables: [ClassWithoutDependencies::class]);
Expand Down

0 comments on commit 258c6c4

Please sign in to comment.