Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add StrictFactory #214

Merged
merged 8 commits into from
Dec 9, 2024
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## 1.2.1 under development

- no changes in this release.
- New #214: Add `StrictFactory` (@vjik)

## 1.2.0 December 03, 2023

Expand Down
23 changes: 23 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,29 @@ turn it off by passing `false` as a third constructor argument:
$factory = new Factory($container, $factoryConfig, false);
```

### Strict factory

`StrictFactory` is similar to base factory, but creating objects for specified definitions only:
vjik marked this conversation as resolved.
Show resolved Hide resolved

```php
$container = new PSR11DependencyInjectionContainer();
$factoryConfig = [
EngineInterface::class => [
'class' => EngineMarkOne::class,
'__construct()' => [
'power' => 42,
],
]
];

$factory = new Factory($factoryConfig, $container);

$engine = $factory->create(EngineInterface::class);

// Throws `NotFoundException`
$factory->create(EngineMarkOne::class);
```

## Documentation

- [Internals](docs/internals.md)
Expand Down
2 changes: 1 addition & 1 deletion src/Factory.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
private FactoryInternalContainer $internalContainer;

/**
* @param ContainerInterface $container Container to use for resolving dependencies.
* @param ContainerInterface|null $container Container to use for resolving dependencies.
* @param array<string, mixed> $definitions Definitions to create objects with.
* @param bool $validate If definitions should be validated when set.
*
Expand All @@ -47,7 +47,7 @@
*/
public function withDefinitions(array $definitions): self
{
$this->validateDefinitions($definitions);

Check warning on line 50 in src/Factory.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.2-ubuntu-latest

Escaped Mutant for Mutator "MethodCallRemoval": --- Original +++ New @@ @@ */ public function withDefinitions(array $definitions) : self { - $this->validateDefinitions($definitions); + $new = clone $this; $new->internalContainer = $this->internalContainer->withDefinitions($definitions); return $new;

$new = clone $this;
$new->internalContainer = $this->internalContainer->withDefinitions($definitions);
Expand Down
72 changes: 72 additions & 0 deletions src/StrictFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
<?php

declare(strict_types=1);

namespace Yiisoft\Factory;

use Psr\Container\ContainerInterface;
use Yiisoft\Definitions\Exception\InvalidConfigException;
use Yiisoft\Definitions\Helpers\DefinitionValidator;

/**
* Strict factory allows creating objects for specified definitions only.
*
* A factory will try to use a PSR-11 compliant container to get dependencies, but will fall back to manual
* instantiation if the container cannot provide a required dependency.
*/
final class StrictFactory
{
private FactoryInternalContainer $internalContainer;

/**
* @param array<string, mixed> $definitions Definitions to create objects with.
* @param ContainerInterface|null $container Container to use for resolving dependencies.
* @param bool $validate If definitions should be validated when set.
*
* @throws InvalidConfigException When validation is enabled and definitions are invalid.
*/
public function __construct(
array $definitions,
?ContainerInterface $container = null,
bool $validate = true,
) {
if ($validate) {
foreach ($definitions as $id => $definition) {
DefinitionValidator::validate($definition, $id);
}
}

$this->internalContainer = new FactoryInternalContainer($container, $definitions);
}

/**
* Creates an object using the definition associated with the provided identifier.
*
* @param string $id The identifier of the object to create.
*
* @throws NotFoundException If no definition is found for the given identifier.
* @throws InvalidConfigException If definition configuration is not valid.
* @return mixed The created object.
*/
public function create(string $id): mixed
{
if (!$this->internalContainer->hasDefinition($id)) {
throw new NotFoundException($id);
}

return $this->internalContainer->create(
$this->internalContainer->getDefinition($id)
);
}

/**
* Checks if the factory has a definition for the specified identifier.
*
* @param string $id The identifier of the definition to check.
* @return bool Whether the factory has a definition for the specified identifier.
*/
public function has(string $id): bool
{
return $this->internalContainer->hasDefinition($id);
}
}
63 changes: 63 additions & 0 deletions tests/StrictFactoryTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<?php

declare(strict_types=1);

namespace Yiisoft\Factory\Tests;

use PHPUnit\Framework\TestCase;
use Yiisoft\Definitions\Exception\InvalidConfigException;
use Yiisoft\Factory\NotFoundException;
use Yiisoft\Factory\StrictFactory;
use Yiisoft\Factory\Tests\Support\Car;
use Yiisoft\Factory\Tests\Support\EngineInterface;
use Yiisoft\Factory\Tests\Support\EngineMarkOne;
use Yiisoft\Test\Support\Container\SimpleContainer;

final class StrictFactoryTest extends TestCase
{
public function testBase(): void
{
$factory = new StrictFactory([
'engine' => EngineMarkOne::class,
]);

$this->assertInstanceOf(EngineMarkOne::class, $factory->create('engine'));
$this->assertTrue($factory->has('engine'));
$this->assertFalse($factory->has(EngineMarkOne::class));

$this->expectException(NotFoundException::class);
$factory->create(EngineMarkOne::class);
}

public function testWithContainer(): void
{
$engine = new EngineMarkOne();
$factory = new StrictFactory(
[
'car' => Car::class,
],
new SimpleContainer([
EngineInterface::class => $engine,
]),
);

$object = $factory->create('car');

$this->assertInstanceOf(Car::class, $object);
$this->assertSame($engine, $object->getEngine());
}

public function testCreateWithInvalidFactoryDefinitionWithValidation(): void
{
$this->expectException(InvalidConfigException::class);
new StrictFactory(['x' => 42]);
}

public function testCreateWithInvalidFactoryDefinitionWithoutValidation(): void
{
$factory = new StrictFactory(['x' => 42], validate: false);

$this->expectException(InvalidConfigException::class);
$factory->create('x');
}
}
Loading