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

Parameter name binding #87

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
## 3.2.1 under development

- Bug #86: Fix crash when intersection types are used (@vjik)
- Enh #87: Support parameter name bindings (@xepozz)

## 3.2.0 February 12, 2023

Expand Down
34 changes: 33 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ at the moment of obtaining a service instance or creating an object.

#### `ArrayDefinition`

Array definition allows describing a service or an object declaratively:
`ArrayDefinition` describes a class declarative:
xepozz marked this conversation as resolved.
Show resolved Hide resolved

```php
use \Yiisoft\Definitions\ArrayDefinition;
Expand Down Expand Up @@ -197,6 +197,38 @@ ContentNegotiator::class => [
],
```

### Name binding

Name binding is a way to bind a name to a definition. It is used to resolve a definition not by its class name but by a name.

Set a definitions with a specific name. It may be typed or untyped reference like:
1. `'$serviceName' => $definition`
2. `Service::class . ' $serviceName' => $definition`

```php
return [
'$fileCache' => FileCache::class, // implements CacheInterface
'$redisCache' => RedisCache::class, // implements CacheInterface
CacheInterface::class . ' $memCache' => MemCache::class, // also implements CacheInterface
]
```

So now you can resolve a definition by its name:

```php
class MyService
{
public function __construct(
CacheInterface $memCache, // typed reference
$fileCache, // untyped reference
CacheInterface $redisCache, // typed reference to untyped definition
) {
// ...
}

}
```

### Definition storage

Definition storage could be used to hold and obtain definitions and check if a certain definition could be instantiated.
Expand Down
32 changes: 28 additions & 4 deletions src/DefinitionStorage.php
Original file line number Diff line number Diff line change
Expand Up @@ -96,12 +96,36 @@
*
* @throws CircularReferenceException
*/
private function isResolvable(string $id, array $building): bool
private function isResolvable(string $id, array $building, ?string $parameterName = null): bool
{
if (isset($this->definitions[$id])) {
return true;
}

if (
Copy link
Member

@vjik vjik Mar 3, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume that feature can degrade performance. Suggest to do it optional or need check performance.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@xepozz would you mind running a comparison benchmark for it?

$parameterName !== null
&& (
isset($this->definitions[$typedParameterName = $id . ' $' . $parameterName])

Check warning on line 108 in src/DefinitionStorage.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.3-ubuntu-latest

Escaped Mutant for Mutator "Concat": --- Original +++ New @@ @@ if (isset($this->definitions[$id])) { return true; } - if ($parameterName !== null && (isset($this->definitions[$typedParameterName = $id . ' $' . $parameterName]) || isset($this->definitions[$typedParameterName = '$' . $parameterName])) && !empty($buildingClass = array_key_last($building)) && class_exists($buildingClass)) { + if ($parameterName !== null && (isset($this->definitions[$typedParameterName = ' $' . $id . $parameterName]) || isset($this->definitions[$typedParameterName = '$' . $parameterName])) && !empty($buildingClass = array_key_last($building)) && class_exists($buildingClass)) { $definition = $this->definitions[$buildingClass] ?? null; $temporaryDefinition = ArrayDefinition::fromConfig([ArrayDefinition::CLASS_NAME => $buildingClass, ArrayDefinition::CONSTRUCTOR => [$parameterName => Reference::to($this->definitions[$typedParameterName])]]); if ($definition instanceof ArrayDefinition) {

Check warning on line 108 in src/DefinitionStorage.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.3-ubuntu-latest

Escaped Mutant for Mutator "ConcatOperandRemoval": --- Original +++ New @@ @@ if (isset($this->definitions[$id])) { return true; } - if ($parameterName !== null && (isset($this->definitions[$typedParameterName = $id . ' $' . $parameterName]) || isset($this->definitions[$typedParameterName = '$' . $parameterName])) && !empty($buildingClass = array_key_last($building)) && class_exists($buildingClass)) { + if ($parameterName !== null && (isset($this->definitions[$typedParameterName = ' $' . $parameterName]) || isset($this->definitions[$typedParameterName = '$' . $parameterName])) && !empty($buildingClass = array_key_last($building)) && class_exists($buildingClass)) { $definition = $this->definitions[$buildingClass] ?? null; $temporaryDefinition = ArrayDefinition::fromConfig([ArrayDefinition::CLASS_NAME => $buildingClass, ArrayDefinition::CONSTRUCTOR => [$parameterName => Reference::to($this->definitions[$typedParameterName])]]); if ($definition instanceof ArrayDefinition) {

Check warning on line 108 in src/DefinitionStorage.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.3-ubuntu-latest

Escaped Mutant for Mutator "ConcatOperandRemoval": --- Original +++ New @@ @@ if (isset($this->definitions[$id])) { return true; } - if ($parameterName !== null && (isset($this->definitions[$typedParameterName = $id . ' $' . $parameterName]) || isset($this->definitions[$typedParameterName = '$' . $parameterName])) && !empty($buildingClass = array_key_last($building)) && class_exists($buildingClass)) { + if ($parameterName !== null && (isset($this->definitions[$typedParameterName = $id . $parameterName]) || isset($this->definitions[$typedParameterName = '$' . $parameterName])) && !empty($buildingClass = array_key_last($building)) && class_exists($buildingClass)) { $definition = $this->definitions[$buildingClass] ?? null; $temporaryDefinition = ArrayDefinition::fromConfig([ArrayDefinition::CLASS_NAME => $buildingClass, ArrayDefinition::CONSTRUCTOR => [$parameterName => Reference::to($this->definitions[$typedParameterName])]]); if ($definition instanceof ArrayDefinition) {

Check warning on line 108 in src/DefinitionStorage.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.3-ubuntu-latest

Escaped Mutant for Mutator "Concat": --- Original +++ New @@ @@ if (isset($this->definitions[$id])) { return true; } - if ($parameterName !== null && (isset($this->definitions[$typedParameterName = $id . ' $' . $parameterName]) || isset($this->definitions[$typedParameterName = '$' . $parameterName])) && !empty($buildingClass = array_key_last($building)) && class_exists($buildingClass)) { + if ($parameterName !== null && (isset($this->definitions[$typedParameterName = $id . $parameterName . ' $']) || isset($this->definitions[$typedParameterName = '$' . $parameterName])) && !empty($buildingClass = array_key_last($building)) && class_exists($buildingClass)) { $definition = $this->definitions[$buildingClass] ?? null; $temporaryDefinition = ArrayDefinition::fromConfig([ArrayDefinition::CLASS_NAME => $buildingClass, ArrayDefinition::CONSTRUCTOR => [$parameterName => Reference::to($this->definitions[$typedParameterName])]]); if ($definition instanceof ArrayDefinition) {

Check warning on line 108 in src/DefinitionStorage.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.3-ubuntu-latest

Escaped Mutant for Mutator "ConcatOperandRemoval": --- Original +++ New @@ @@ if (isset($this->definitions[$id])) { return true; } - if ($parameterName !== null && (isset($this->definitions[$typedParameterName = $id . ' $' . $parameterName]) || isset($this->definitions[$typedParameterName = '$' . $parameterName])) && !empty($buildingClass = array_key_last($building)) && class_exists($buildingClass)) { + if ($parameterName !== null && (isset($this->definitions[$typedParameterName = $id . ' $']) || isset($this->definitions[$typedParameterName = '$' . $parameterName])) && !empty($buildingClass = array_key_last($building)) && class_exists($buildingClass)) { $definition = $this->definitions[$buildingClass] ?? null; $temporaryDefinition = ArrayDefinition::fromConfig([ArrayDefinition::CLASS_NAME => $buildingClass, ArrayDefinition::CONSTRUCTOR => [$parameterName => Reference::to($this->definitions[$typedParameterName])]]); if ($definition instanceof ArrayDefinition) {

Check warning on line 108 in src/DefinitionStorage.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.3-ubuntu-latest

Escaped Mutant for Mutator "LogicalOr": --- Original +++ New @@ @@ if (isset($this->definitions[$id])) { return true; } - if ($parameterName !== null && (isset($this->definitions[$typedParameterName = $id . ' $' . $parameterName]) || isset($this->definitions[$typedParameterName = '$' . $parameterName])) && !empty($buildingClass = array_key_last($building)) && class_exists($buildingClass)) { + if ($parameterName !== null && (isset($this->definitions[$typedParameterName = $id . ' $' . $parameterName]) && isset($this->definitions[$typedParameterName = '$' . $parameterName])) && !empty($buildingClass = array_key_last($building)) && class_exists($buildingClass)) { $definition = $this->definitions[$buildingClass] ?? null; $temporaryDefinition = ArrayDefinition::fromConfig([ArrayDefinition::CLASS_NAME => $buildingClass, ArrayDefinition::CONSTRUCTOR => [$parameterName => Reference::to($this->definitions[$typedParameterName])]]); if ($definition instanceof ArrayDefinition) {
|| isset($this->definitions[$typedParameterName = '$' . $parameterName])

Check warning on line 109 in src/DefinitionStorage.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.3-ubuntu-latest

Escaped Mutant for Mutator "Concat": --- Original +++ New @@ @@ if (isset($this->definitions[$id])) { return true; } - if ($parameterName !== null && (isset($this->definitions[$typedParameterName = $id . ' $' . $parameterName]) || isset($this->definitions[$typedParameterName = '$' . $parameterName])) && !empty($buildingClass = array_key_last($building)) && class_exists($buildingClass)) { + if ($parameterName !== null && (isset($this->definitions[$typedParameterName = $id . ' $' . $parameterName]) || isset($this->definitions[$typedParameterName = $parameterName . '$'])) && !empty($buildingClass = array_key_last($building)) && class_exists($buildingClass)) { $definition = $this->definitions[$buildingClass] ?? null; $temporaryDefinition = ArrayDefinition::fromConfig([ArrayDefinition::CLASS_NAME => $buildingClass, ArrayDefinition::CONSTRUCTOR => [$parameterName => Reference::to($this->definitions[$typedParameterName])]]); if ($definition instanceof ArrayDefinition) {

Check warning on line 109 in src/DefinitionStorage.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.3-ubuntu-latest

Escaped Mutant for Mutator "ConcatOperandRemoval": --- Original +++ New @@ @@ if (isset($this->definitions[$id])) { return true; } - if ($parameterName !== null && (isset($this->definitions[$typedParameterName = $id . ' $' . $parameterName]) || isset($this->definitions[$typedParameterName = '$' . $parameterName])) && !empty($buildingClass = array_key_last($building)) && class_exists($buildingClass)) { + if ($parameterName !== null && (isset($this->definitions[$typedParameterName = $id . ' $' . $parameterName]) || isset($this->definitions[$typedParameterName = $parameterName])) && !empty($buildingClass = array_key_last($building)) && class_exists($buildingClass)) { $definition = $this->definitions[$buildingClass] ?? null; $temporaryDefinition = ArrayDefinition::fromConfig([ArrayDefinition::CLASS_NAME => $buildingClass, ArrayDefinition::CONSTRUCTOR => [$parameterName => Reference::to($this->definitions[$typedParameterName])]]); if ($definition instanceof ArrayDefinition) {

Check warning on line 109 in src/DefinitionStorage.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.3-ubuntu-latest

Escaped Mutant for Mutator "ConcatOperandRemoval": --- Original +++ New @@ @@ if (isset($this->definitions[$id])) { return true; } - if ($parameterName !== null && (isset($this->definitions[$typedParameterName = $id . ' $' . $parameterName]) || isset($this->definitions[$typedParameterName = '$' . $parameterName])) && !empty($buildingClass = array_key_last($building)) && class_exists($buildingClass)) { + if ($parameterName !== null && (isset($this->definitions[$typedParameterName = $id . ' $' . $parameterName]) || isset($this->definitions[$typedParameterName = '$'])) && !empty($buildingClass = array_key_last($building)) && class_exists($buildingClass)) { $definition = $this->definitions[$buildingClass] ?? null; $temporaryDefinition = ArrayDefinition::fromConfig([ArrayDefinition::CLASS_NAME => $buildingClass, ArrayDefinition::CONSTRUCTOR => [$parameterName => Reference::to($this->definitions[$typedParameterName])]]); if ($definition instanceof ArrayDefinition) {
)
&& (!empty($buildingClass = array_key_last($building))) && class_exists($buildingClass)
) {
$definition = $this->definitions[$buildingClass] ?? null;
$temporaryDefinition = ArrayDefinition::fromConfig([
ArrayDefinition::CLASS_NAME => $buildingClass,
ArrayDefinition::CONSTRUCTOR => [
$parameterName => Reference::to($this->definitions[$typedParameterName]),
],
]);
if ($definition instanceof ArrayDefinition) {
$this->definitions[$buildingClass] = $definition->merge($temporaryDefinition);

Check warning on line 121 in src/DefinitionStorage.php

View check run for this annotation

Codecov / codecov/patch

src/DefinitionStorage.php#L113-L121

Added lines #L113 - L121 were not covered by tests
} else {
$this->definitions[$buildingClass] = $temporaryDefinition;

Check warning on line 123 in src/DefinitionStorage.php

View check run for this annotation

Codecov / codecov/patch

src/DefinitionStorage.php#L123

Added line #L123 was not covered by tests
}

return true;

Check warning on line 126 in src/DefinitionStorage.php

View check run for this annotation

Codecov / codecov/patch

src/DefinitionStorage.php#L126

Added line #L126 was not covered by tests
}

if ($this->useStrictMode || !class_exists($id)) {
$this->buildStack += $building + [$id => 1];
return false;
Expand Down Expand Up @@ -172,7 +196,7 @@
continue;
}
$unionTypes[] = $typeName;
if ($this->isResolvable($typeName, $building)) {
if ($this->isResolvable($typeName, $building, $parameter->getName())) {
$isUnionTypeResolvable = true;
/** @infection-ignore-all Mutation don't change behaviour, but degrade performance. */
break;
Expand Down Expand Up @@ -215,7 +239,7 @@
}

if (
!$this->isResolvable($typeName, $building)
!$this->isResolvable($typeName, $building, $parameter->getName())
&& ($this->delegateContainer === null || !$this->delegateContainer->has($typeName))
) {
$isResolvable = false;
Expand All @@ -227,7 +251,7 @@
$this->buildStack += $building;
}

if ($isResolvable) {
if ($isResolvable && !isset($this->definitions[$id])) {

Check warning on line 254 in src/DefinitionStorage.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.3-ubuntu-latest

Escaped Mutant for Mutator "LogicalAnd": --- Original +++ New @@ @@ } finally { $this->buildStack += $building; } - if ($isResolvable && !isset($this->definitions[$id])) { + if ($isResolvable || !isset($this->definitions[$id])) { $this->definitions[$id] = $id; } return $isResolvable; } }
$this->definitions[$id] = $id;
}

Expand Down
6 changes: 5 additions & 1 deletion src/Helpers/Normalizer.php
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems, this changes not need in this PR.

Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@
return $definition;
}

if ($definition instanceof DefinitionInterface) {
return $definition;

Check warning on line 57 in src/Helpers/Normalizer.php

View check run for this annotation

Codecov / codecov/patch

src/Helpers/Normalizer.php#L57

Added line #L57 was not covered by tests
}

if (is_string($definition)) {
// Current class
if (
Expand Down Expand Up @@ -88,7 +92,7 @@
}

// Ready object
if (is_object($definition) && !($definition instanceof DefinitionInterface)) {
if (is_object($definition)) {
return new ValueDefinition($definition);
}

Expand Down
Loading