-
-
Notifications
You must be signed in to change notification settings - Fork 24
Refactor configuration to immutable types #103
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
base: 3.17.x
Are you sure you want to change the base?
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| <?php | ||
|
|
||
| declare(strict_types=1); | ||
|
|
||
| namespace Laminas\Di\Config; | ||
|
|
||
| /** | ||
| * Provides type configuration for a type alias (virtual type) | ||
| * | ||
| * @readonly | ||
| */ | ||
| final class AliasConfig | ||
| { | ||
| public function __construct( | ||
| public readonly string $name, | ||
| public readonly TypeConfig $type, | ||
| ) { | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| <?php | ||
|
|
||
| declare(strict_types=1); | ||
|
|
||
| namespace Laminas\Di\Config\Exception; | ||
|
|
||
| use Laminas\Di\Exception\UnexpectedValueException; | ||
|
|
||
| class InvalidConfigException extends UnexpectedValueException | ||
|
Check failure on line 9 in src/Config/Exception/InvalidConfigException.php
|
||
| { | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| <?php | ||
|
|
||
| declare(strict_types=1); | ||
|
|
||
| namespace Laminas\Di\Config\Exception; | ||
|
|
||
| use function sprintf; | ||
|
|
||
| class InvalidParametersException extends InvalidConfigException | ||
|
Check failure on line 9 in src/Config/Exception/InvalidParametersException.php
|
||
| { | ||
| public static function numericParamKey(int $key): self | ||
| { | ||
| return new self( | ||
| sprintf( | ||
| 'Parameter name must be an identifier, got a numeric index %d', | ||
| $key, | ||
| ), | ||
| ); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| <?php | ||
|
|
||
| declare(strict_types=1); | ||
|
|
||
| namespace Laminas\Di\Config\Exception; | ||
|
|
||
| use Laminas\Di\Config\Exception\InvalidConfigException; | ||
|
|
||
| class InvalidTypePreferenceException extends InvalidConfigException | ||
|
Check failure on line 9 in src/Config/Exception/InvalidTypePreferenceException.php
|
||
| { | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| <?php | ||
|
|
||
| declare(strict_types=1); | ||
|
|
||
| namespace Laminas\Di\Config\Exception; | ||
|
|
||
| use Throwable; | ||
|
|
||
| use function sprintf; | ||
|
|
||
| /** | ||
| * This is thrown when a configured type does not exist | ||
| */ | ||
| class UndefinedClassException extends InvalidConfigException | ||
|
Check failure on line 14 in src/Config/Exception/UndefinedClassException.php
|
||
| { | ||
| public function __construct( | ||
| public readonly string $className, | ||
|
Check failure on line 17 in src/Config/Exception/UndefinedClassException.php
|
||
| string|null $message = null, | ||
| int $code = 0, | ||
| Throwable|null $previous = null | ||
| ) { | ||
| parent::__construct($message ?? sprintf('Class "%s" does not exists', $className), $code, $previous); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,166 @@ | ||
| <?php | ||
|
|
||
| declare(strict_types=1); | ||
|
|
||
| namespace Laminas\Di\Config; | ||
|
|
||
| use ArrayAccess; | ||
| use Laminas\Di\Config\Exception\InvalidConfigException; | ||
| use Laminas\Di\ConfigInterface; | ||
| use Laminas\Di\Exception\LogicException; | ||
|
|
||
| use function array_keys; | ||
| use function get_debug_type; | ||
| use function is_array; | ||
| use function sprintf; | ||
|
|
||
| /** | ||
| * Provides a typesafe and immutable DI configuration from a config array. | ||
| * | ||
| * This configures the instantiation process of the dependency injector. | ||
| * | ||
| * **Example:** | ||
| * | ||
| * <code> | ||
| * return [ | ||
| * // This section provides global type preferences. | ||
| * // Those are visited if a specific instance has no preference definitions. | ||
| * 'preferences' => [ | ||
| * // The key is the requested class or interface name, the values are | ||
| * // the types the dependency injector should prefer. | ||
| * Some\Interface::class => Some\Preference::class | ||
| * ], | ||
| * // This configures the instantiation of specific types. | ||
| * // Types may also be purely virtual by defining the aliasOf key. | ||
| * 'types' => [ | ||
| * My\Class::class => [ | ||
| * 'preferences' => [ | ||
| * // this supercedes the global type preferences | ||
| * // when My\Class is instantiated | ||
| * Some\Interface::class => 'My.SpecificAlias' | ||
| * ], | ||
| * | ||
| * // instantiation parameters. These will only be used for | ||
| * // the instantiator (i.e. the constructor) | ||
| * 'parameters' => [ | ||
| * 'foo' => My\FooImpl::class, // Use the given type to provide the injection (depends on definition) | ||
| * 'bar' => '*' // Use the type preferences | ||
| * ], | ||
| * ], | ||
| * | ||
| * 'My.Alias' => [ | ||
| * // typeOf defines virtual classes which can be used as type | ||
| * // preferences or for newInstance calls. They allow providing | ||
| * // custom configs for a class | ||
| * 'typeOf' => Some\Class::class, | ||
| * 'preferences' => [ | ||
| * Foo::class => Bar::class | ||
| * ] | ||
| * ] | ||
| * ] | ||
| * ]; | ||
| * </code> | ||
| * | ||
| * ## Notes on Injections | ||
| * | ||
| * Named arguments and Automatic type lookups will only work for Methods that | ||
| * are known to the dependency injector through its definitions. Injections for | ||
| * unknown methods do not perform type lookups on its own. | ||
| * | ||
| * A value injection without any lookups can be forced by providing a | ||
| * Resolver\ValueInjection instance. | ||
| * | ||
| * To force a service/class instance provide a Resolver\TypeInjection instance. | ||
| * For classes known from the definitions, a type preference might be the | ||
| * better approach | ||
| * | ||
| * @see \Laminas\Di\Resolver\ValueInjection A container to force injection of a value | ||
| * @see \Laminas\Di\Resolver\TypeInjection A container to force looking up a specific type instance for injection | ||
| * | ||
| * @readonly | ||
| */ | ||
| final class InjectionConfig implements ConfigInterface | ||
| { | ||
| /** | ||
| * @param array<string, TypeConfig|AliasConfig> $types | ||
| */ | ||
| public function __construct( | ||
| private readonly TypePreferences $preferences = new TypePreferences(), | ||
| private readonly array $types = [] | ||
| ) { | ||
| } | ||
|
|
||
| /** | ||
| * Constructs the injection config from a laminas config value | ||
| */ | ||
| public static function fromConfigValue(mixed $config): self | ||
| { | ||
| if (! is_array($config) && ! $config instanceof ArrayAccess) { | ||
| throw new InvalidConfigException( | ||
| sprintf( | ||
| 'Di configuration must be an array or implement ArrayAccess, got %s', | ||
| get_debug_type($config), | ||
| ), | ||
| ); | ||
| } | ||
|
|
||
| return new self( | ||
| TypePreferences::fromConfigValue($config['preferences'] ?? []), | ||
| TypeConfig::mapFromConfigValue($config['types'] ?? []), | ||
| ); | ||
| } | ||
|
|
||
| private function getTypeConfig(string $name): TypeConfig|null | ||
| { | ||
| $type = $this->types[$name] ?? null; | ||
| return $type instanceof AliasConfig ? $type->type : $type; | ||
| } | ||
|
|
||
| public function isAlias(string $name): bool | ||
|
Check failure on line 119 in src/Config/InjectionConfig.php
|
||
| { | ||
| $type = $this->types[$name] ?? null; | ||
| return $type instanceof AliasConfig; | ||
| } | ||
|
|
||
| public function getConfiguredTypeNames(): array | ||
|
Check failure on line 125 in src/Config/InjectionConfig.php
|
||
| { | ||
| return array_keys($this->types); | ||
| } | ||
|
|
||
| public function getClassForAlias(string $name): string|null | ||
|
Check failure on line 130 in src/Config/InjectionConfig.php
|
||
| { | ||
| return $this->getTypeConfig($name)?->getClassName(); | ||
| } | ||
|
|
||
| public function getParameters(string $type): array | ||
|
Check failure on line 135 in src/Config/InjectionConfig.php
|
||
| { | ||
| /** | ||
| * Psalm-Bug: https://github.com/vimeo/psalm/issues/7099 | ||
| * | ||
| * @psalm-suppress RedundantCondition | ||
| * @psalm-suppress TypeDoesNotContainNull | ||
| */ | ||
| return $this->getTypeConfig($type)?->parameters->toArray() ?? []; | ||
| } | ||
|
|
||
| public function setParameters(string $type, array $params) | ||
| { | ||
| throw new LogicException( | ||
| 'Injection config is considered immutable. You can set [dependencies][auto][mutableConfig] to ' | ||
| . 'true to restore the previous, but deprecated, behavior' | ||
| ); | ||
| } | ||
|
|
||
| public function getTypePreference(string $type, string|null $contextClass = null): string|null | ||
| { | ||
| if ($contextClass !== null) { | ||
| $preference = $this->getTypeConfig($contextClass)?->preferences->getPreferenceFor($type); | ||
|
|
||
| if ($preference !== null) { | ||
| return $preference; | ||
| } | ||
| } | ||
|
|
||
| return $this->preferences->getPreferenceFor($type); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| <?php | ||
|
|
||
| declare(strict_types=1); | ||
|
|
||
| namespace Laminas\Di\Config; | ||
|
|
||
| use Laminas\Di\Resolver\InjectionInterface; | ||
| use Laminas\Di\Resolver\ValueInjection; | ||
|
|
||
| use function is_string; | ||
|
|
||
| /** | ||
| * Parameter injection configuration | ||
| * | ||
| * @readonly | ||
| */ | ||
| final class Parameter | ||
| { | ||
| public function __construct( | ||
| public readonly string $name, | ||
| public readonly string|InjectionInterface $injection, | ||
| ) { | ||
| } | ||
|
|
||
| public static function fromValue(string $name, mixed $value): self | ||
| { | ||
| return new self( | ||
| $name, | ||
| is_string($value) || $value instanceof InjectionInterface | ||
| ? $value | ||
| : new ValueInjection($value), | ||
| ); | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FWIW, this can now be marked
readonlyvia the language itselfThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since support for php 8.1 is now gone, indeed.