Skip to content

Commit

Permalink
Added factory support
Browse files Browse the repository at this point in the history
  • Loading branch information
Dropelikeit committed Feb 1, 2023
1 parent 6747907 commit 529147c
Show file tree
Hide file tree
Showing 20 changed files with 701 additions and 57 deletions.
9 changes: 7 additions & 2 deletions src/AppContainer.php
Expand Up @@ -33,9 +33,14 @@ public function get(string $id): mixed
/** @var ClassItem $metadata */
$metadata = $this->classContainer->get($id);

$class = $metadata->getClass();
if ($metadata->hasFactory()) {
$class = $metadata->getFactory();
}

try {
return $this->objectContainer->get($metadata->getClass());
} catch (NotFoundInContainerException $exception) {
return $this->objectContainer->get($class);
} catch (NotFoundInContainerException) {
throw CannotRetrieveException::create($metadata->getClass());
}
}
Expand Down
9 changes: 9 additions & 0 deletions src/Delegator/DelegateInterface.php
@@ -0,0 +1,9 @@
<?php
declare(strict_types=1);

namespace MarcelStrahl\Container\Delegator;

interface DelegateInterface
{
public function delegate(string $class): object;
}
34 changes: 34 additions & 0 deletions src/Delegator/ObjectDelegator.php
@@ -0,0 +1,34 @@
<?php
declare(strict_types=1);

namespace MarcelStrahl\Container\Delegator;

use MarcelStrahl\Container\Factory\FactoryInterface;
use MarcelStrahl\Container\ObjectBuilder\ObjectBuilderFactoryInterface;

use function class_implements;
use function in_array;
use function method_exists;

final class ObjectDelegator implements DelegateInterface
{
public function __construct(private ObjectBuilderFactoryInterface $builderFactory) {}

public function delegate(string $class): object
{
$interfaces = class_implements($class);
$isFactory = in_array(FactoryInterface::class, $interfaces, true);
if (!$isFactory) {
$isFactory = method_exists($class, '__invoke');
}

$type = ObjectBuilderFactoryInterface::BUILDER_REFLECTION;
if ($isFactory) {
$type = ObjectBuilderFactoryInterface::BUILDER_FACTORY;
}

$builder = $this->builderFactory->factorize($type);

return $builder->initialize($class);
}
}
14 changes: 14 additions & 0 deletions src/Exception/ObjectBuilderFactory/UnknownBuilderTypeException.php
@@ -0,0 +1,14 @@
<?php
declare(strict_types=1);

namespace MarcelStrahl\Container\Exception\ObjectBuilderFactory;

use RuntimeException;

final class UnknownBuilderTypeException extends RuntimeException
{
public static function create(string $type): self
{
return new self(sprintf('Unknown builder type detected. Given: "%s"', $type));
}
}
14 changes: 14 additions & 0 deletions src/Factory/FactoryInterface.php
@@ -0,0 +1,14 @@
<?php
declare(strict_types=1);

namespace MarcelStrahl\Container\Factory;

use Psr\Container\ContainerInterface;

/**
* @author Marcel Strahl <info@marcel-strahl.de>
*/
interface FactoryInterface
{
public function factorize(ContainerInterface $container): object;
}
43 changes: 43 additions & 0 deletions src/ObjectBuilder/FactoryBuilder.php
@@ -0,0 +1,43 @@
<?php
declare(strict_types=1);

namespace MarcelStrahl\Container\ObjectBuilder;

use MarcelStrahl\Container\Exception\NotFoundInContainerException;
use MarcelStrahl\Container\Factory\FactoryInterface;
use Psr\Container\ContainerInterface;

use function class_exists;
use function is_callable;
use function method_exists;

/**
* @author Marcel Strahl <info@marcel-strahl.de>
*/
final class FactoryBuilder implements ObjectBuilder
{
public function __construct(private ContainerInterface $container, private ObjectBuilder $reflectionBuilder) {}

public function initialize(callable|string $class): object
{
if (is_callable($class)) {
return $class($this->container);
}

if (!class_exists($class)) {
throw NotFoundInContainerException::create($class);
}

$factory = $this->reflectionBuilder->initialize($class);
if (method_exists($factory, '__invoke')) {

return $factory->__invoke($this->container);
}

if (!$factory instanceof FactoryInterface) {
throw NotFoundInContainerException::create($class);
}

return $factory->factorize($this->container);
}
}
2 changes: 1 addition & 1 deletion src/ObjectBuilder/ObjectBuilder.php
Expand Up @@ -13,5 +13,5 @@ interface ObjectBuilder
* @throws CanNotCreateClassWithNoneClassDependencies
* @throws LogicException
*/
public function initialize(string $class): object;
public function initialize(callable|string $class): object;
}
45 changes: 45 additions & 0 deletions src/ObjectBuilder/ObjectBuilderFactory.php
@@ -0,0 +1,45 @@
<?php
declare(strict_types=1);

namespace MarcelStrahl\Container\ObjectBuilder;

use MarcelStrahl\Container\Exception\ObjectBuilderFactory\UnknownBuilderTypeException;
use Psr\Container\ContainerInterface;
use Webmozart\Assert\Assert;

final class ObjectBuilderFactory implements ObjectBuilderFactoryInterface
{
private ?ContainerInterface $container;

public function __construct()
{
$this->container = null;
}

/**
* @psalm-param self::BUILDER_* $builderType
* @throws UnknownBuilderTypeException
*/
public function factorize(string $builderType): ObjectBuilder
{
$container = $this->container;

Assert::isInstanceOf(
$container,
ContainerInterface::class,
'It is important that you set the container before calling `factorize`.',
);

$reflectionBuilder = new ReflectionBuilder();
return match ($builderType) {
self::BUILDER_REFLECTION => $reflectionBuilder,
self::BUILDER_FACTORY => new FactoryBuilder($container, $reflectionBuilder),
default => throw UnknownBuilderTypeException::create($builderType),
};
}

public function setContainer(ContainerInterface $container): void
{
$this->container = $container;
}
}
18 changes: 18 additions & 0 deletions src/ObjectBuilder/ObjectBuilderFactoryInterface.php
@@ -0,0 +1,18 @@
<?php
declare(strict_types=1);

namespace MarcelStrahl\Container\ObjectBuilder;

use MarcelStrahl\Container\Exception\ObjectBuilderFactory\UnknownBuilderTypeException;

interface ObjectBuilderFactoryInterface
{
public const BUILDER_REFLECTION = 'reflection';
public const BUILDER_FACTORY = 'factory';

/**
* @psalm-param self::BUILDER_* $builderType
* @throws UnknownBuilderTypeException
*/
public function factorize(string $builderType): ObjectBuilder;
}
9 changes: 8 additions & 1 deletion src/ObjectBuilder/ReflectionBuilder.php
Expand Up @@ -11,10 +11,17 @@
use ReflectionParameter;
use Webmozart\Assert\Assert;

use function is_callable;
use function class_exists;

final class ReflectionBuilder implements ObjectBuilder
{
public function initialize(string $class): object
public function initialize(callable|string $class): object
{
if (is_callable($class)) {
throw new LogicException('callables are not yet supported.');
}

try {
$reflectionClass = new ReflectionClass($class);
} catch (ReflectionException $exception) {
Expand Down
5 changes: 3 additions & 2 deletions src/ObjectContainer.php
Expand Up @@ -5,6 +5,7 @@
namespace MarcelStrahl\Container;

use LogicException;
use MarcelStrahl\Container\Delegator\DelegateInterface;
use MarcelStrahl\Container\Dto\ObjectStoreInterface;
use MarcelStrahl\Container\Exception\NotFoundInContainerException;
use MarcelStrahl\Container\ObjectBuilder\ObjectBuilder;
Expand All @@ -14,7 +15,7 @@ final class ObjectContainer implements ContainerInterface
{
public function __construct(
private /* readonly */ ObjectStoreInterface $objectStore,
private /* readonly */ ObjectBuilder $builder,
private /* readonly */ DelegateInterface $delegator,
) {
}

Expand All @@ -26,7 +27,7 @@ public function get(string $id): object
}

try {
$object = $this->builder->initialize($id);
$object = $this->delegator->delegate($id);
} catch (LogicException $exception) {
throw NotFoundInContainerException::create($id, $exception);
}
Expand Down
22 changes: 17 additions & 5 deletions tests/Acceptance/WorkflowTest.php
Expand Up @@ -6,23 +6,26 @@

use MarcelStrahl\Container\AppContainer;
use MarcelStrahl\Container\ClassContainer;
use MarcelStrahl\Container\Delegator\ObjectDelegator;
use MarcelStrahl\Container\Dto\ClassStore;
use MarcelStrahl\Container\Dto\ObjectStore;
use MarcelStrahl\Container\FileLoader\AdapterBuilder;
use MarcelStrahl\Container\FileLoader\PHPArrayAdapter;
use MarcelStrahl\Container\ObjectBuilder\FactoryBuilder;
use MarcelStrahl\Container\ObjectBuilder\ObjectBuilderFactory;
use MarcelStrahl\Container\ObjectBuilder\ReflectionBuilder;
use MarcelStrahl\Container\ObjectContainer;
use MarcelStrahl\Tests\FileLoader\data\PhpArrayLoaderClassDummy;
use MarcelStrahl\Tests\FileLoader\data\PhpArrayLoaderClassDummyWithFactory;
use MarcelStrahl\Tests\FileLoader\data\PhpArrayLoaderClassDummyWithFactory\Factory;
use PhpParser\BuilderFactory;
use PHPUnit\Framework\TestCase;
use Webmozart\Assert\Assert;

/**
* @author Marcel Strahl <info@marcel-strahl.de>
*
* @internal
*
*/
final class WorkflowTest extends TestCase
{
Expand All @@ -33,14 +36,18 @@ public function testCanLoadContainerWithOneServiceFile(): void
$path = sprintf('%s/../FileLoader/php_array_config.php', __DIR__);
Assert::stringNotEmpty($path);

$builderFactory = new ObjectBuilderFactory();
$delegator = new ObjectDelegator($builderFactory);

$classStore = $adapter->loadFileFromPath($path, ClassStore::create());

$classContainer = ClassContainer::create($classStore);
$classContainer->compile();

$objectContainer = new ObjectContainer(ObjectStore::create(), new ReflectionBuilder());
$objectContainer = new ObjectContainer(ObjectStore::create(), $delegator);

$app = AppContainer::initialize($classContainer, $objectContainer);
$builderFactory->setContainer($app);

static::assertTrue($app->has(PhpArrayLoaderClassDummy::class));
$object = $app->get(PhpArrayLoaderClassDummy::class);
Expand All @@ -58,14 +65,19 @@ public function testCanLoadContainerWithMoreThanOneServiceFile(): void
$pathTwo = sprintf('%s/../FileLoader/array_config.php', __DIR__);
Assert::stringNotEmpty($pathTwo);

$builderFactory = new ObjectBuilderFactory();
$delegator = new ObjectDelegator($builderFactory);


$classStore = $adapter->loadFileFromPaths([$pathOne, $pathTwo], ClassStore::create());

$classContainer = ClassContainer::create($classStore);
$classContainer->compile();

$objectContainer = new ObjectContainer(ObjectStore::create(), new ReflectionBuilder());
$objectContainer = new ObjectContainer(ObjectStore::create(), $delegator);

$app = AppContainer::initialize($classContainer, $objectContainer);
$builderFactory->setContainer($app);

static::assertTrue($app->has(PhpArrayLoaderClassDummy::class));
$object = $app->get(PhpArrayLoaderClassDummy::class);
Expand All @@ -75,6 +87,6 @@ public function testCanLoadContainerWithMoreThanOneServiceFile(): void
static::assertTrue($app->has(Factory::class));
$object = $app->get(Factory::class);

static::assertInstanceOf(Factory::class, $object);
static::assertInstanceOf(PhpArrayLoaderClassDummyWithFactory::class, $object);
}
}

0 comments on commit 529147c

Please sign in to comment.