Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions psalm.xml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd"
errorBaseline="psalm-baseline.xml"
findUnusedPsalmSuppress="true"
findUnusedBaselineEntry="true"
findUnusedCode="true"
>
<projectFiles>
Expand Down
4 changes: 3 additions & 1 deletion src/Config.php
Original file line number Diff line number Diff line change
Expand Up @@ -74,11 +74,13 @@
* For classes known from the definitions, a type preference might be the
* better approach
*
* @deprecated Since 3.16.0. This class will be removed in 4.0. Please use the immutable and typesafe
* {@link Config\InjectionConfig} variant.
*
* @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
*
* @final
*
* @psalm-type TypeConfigArray = array{
* typeOf?: class-string|null,
* preferences?: array<string, string>|null,
Expand Down
17 changes: 17 additions & 0 deletions src/Config/AliasConfig.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php

declare(strict_types=1);

namespace Laminas\Di\Config;

/**
* Provides type configuration for a type alias (virtual type)
*/
final readonly class AliasConfig
{
public function __construct(
public string $name,
public TypeConfig $type,
) {
}
}
11 changes: 11 additions & 0 deletions src/Config/Exception/InvalidConfigException.php
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

View workflow job for this annotation

GitHub Actions / ci / QA Checks (Psalm [8.2, locked], ubuntu-latest, laminas/laminas-continuous-integration-action@v1, ...

InvalidExtendClass

src/Config/Exception/InvalidConfigException.php:9:38: InvalidExtendClass: Class Laminas\Di\Config\Exception\InvalidConfigException may not inherit from final class Laminas\Di\Exception\UnexpectedValueException (see https://psalm.dev/232)
{
}
20 changes: 20 additions & 0 deletions src/Config/Exception/InvalidParametersException.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

View workflow job for this annotation

GitHub Actions / ci / QA Checks (Psalm [8.2, locked], ubuntu-latest, laminas/laminas-continuous-integration-action@v1, ...

ClassMustBeFinal

src/Config/Exception/InvalidParametersException.php:9:7: ClassMustBeFinal: Class Laminas\Di\Config\Exception\InvalidParametersException is never extended and is not part of the public API, and thus must be made final. (see https://psalm.dev/361)
{
public static function numericParamKey(int $key): self
{
return new self(
sprintf(
'Parameter name must be an identifier, got a numeric index %d',
$key,
),
);
}
}
11 changes: 11 additions & 0 deletions src/Config/Exception/InvalidTypePreferenceException.php
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

View workflow job for this annotation

GitHub Actions / ci / QA Checks (Psalm [8.2, locked], ubuntu-latest, laminas/laminas-continuous-integration-action@v1, ...

ClassMustBeFinal

src/Config/Exception/InvalidTypePreferenceException.php:9:7: ClassMustBeFinal: Class Laminas\Di\Config\Exception\InvalidTypePreferenceException is never extended and is not part of the public API, and thus must be made final. (see https://psalm.dev/361)
{
}
24 changes: 24 additions & 0 deletions src/Config/Exception/UndefinedClassException.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

View workflow job for this annotation

GitHub Actions / ci / QA Checks (Psalm [8.2, locked], ubuntu-latest, laminas/laminas-continuous-integration-action@v1, ...

ClassMustBeFinal

src/Config/Exception/UndefinedClassException.php:14:7: ClassMustBeFinal: Class Laminas\Di\Config\Exception\UndefinedClassException is never extended and is not part of the public API, and thus must be made final. (see https://psalm.dev/361)
{
public function __construct(
public readonly string $className,

Check failure on line 17 in src/Config/Exception/UndefinedClassException.php

View workflow job for this annotation

GitHub Actions / ci / QA Checks (Psalm [8.2, locked], ubuntu-latest, laminas/laminas-continuous-integration-action@v1, ...

PossiblyUnusedProperty

src/Config/Exception/UndefinedClassException.php:17:32: PossiblyUnusedProperty: Cannot find any references to property Laminas\Di\Config\Exception\UndefinedClassException::$className (see https://psalm.dev/149)
string|null $message = null,
int $code = 0,
Throwable|null $previous = null
) {
parent::__construct($message ?? sprintf('Class "%s" does not exists', $className), $code, $previous);
}
}
164 changes: 164 additions & 0 deletions src/Config/InjectionConfig.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
<?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
*/
final readonly class InjectionConfig implements ConfigInterface
{
/**
* @param array<string, TypeConfig|AliasConfig> $types
*/
public function __construct(
private TypePreferences $preferences = new TypePreferences(),
private 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 117 in src/Config/InjectionConfig.php

View workflow job for this annotation

GitHub Actions / ci / QA Checks (Psalm [8.2, locked], ubuntu-latest, laminas/laminas-continuous-integration-action@v1, ...

MissingOverrideAttribute

src/Config/InjectionConfig.php:117:5: MissingOverrideAttribute: Method Laminas\Di\Config\InjectionConfig::isalias should have the "Override" attribute (see https://psalm.dev/358)
{
$type = $this->types[$name] ?? null;
return $type instanceof AliasConfig;
}

public function getConfiguredTypeNames(): array

Check failure on line 123 in src/Config/InjectionConfig.php

View workflow job for this annotation

GitHub Actions / ci / QA Checks (Psalm [8.2, locked], ubuntu-latest, laminas/laminas-continuous-integration-action@v1, ...

MissingOverrideAttribute

src/Config/InjectionConfig.php:123:5: MissingOverrideAttribute: Method Laminas\Di\Config\InjectionConfig::getconfiguredtypenames should have the "Override" attribute (see https://psalm.dev/358)
{
return array_keys($this->types);
}

public function getClassForAlias(string $name): string|null

Check failure on line 128 in src/Config/InjectionConfig.php

View workflow job for this annotation

GitHub Actions / ci / QA Checks (Psalm [8.2, locked], ubuntu-latest, laminas/laminas-continuous-integration-action@v1, ...

MissingOverrideAttribute

src/Config/InjectionConfig.php:128:5: MissingOverrideAttribute: Method Laminas\Di\Config\InjectionConfig::getclassforalias should have the "Override" attribute (see https://psalm.dev/358)
{
return $this->getTypeConfig($name)?->getClassName();
}

public function getParameters(string $type): array

Check failure on line 133 in src/Config/InjectionConfig.php

View workflow job for this annotation

GitHub Actions / ci / QA Checks (Psalm [8.2, locked], ubuntu-latest, laminas/laminas-continuous-integration-action@v1, ...

MissingOverrideAttribute

src/Config/InjectionConfig.php:133:5: MissingOverrideAttribute: Method Laminas\Di\Config\InjectionConfig::getparameters should have the "Override" attribute (see https://psalm.dev/358)
{
/**
* 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);
}
}
32 changes: 32 additions & 0 deletions src/Config/Parameter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?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
*/
final readonly class Parameter
{
public function __construct(
public string $name,
public 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),
);
}
}
84 changes: 84 additions & 0 deletions src/Config/ParameterMap.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
<?php

declare(strict_types=1);

namespace Laminas\Di\Config;

use Laminas\Di\Config\Exception\InvalidParametersException;
use Laminas\Di\Resolver\InjectionInterface;
use Laminas\Di\Util;

use function array_map;
use function array_values;
use function get_debug_type;
use function is_iterable;
use function is_string;
use function sprintf;

final readonly class ParameterMap
{
/** @var array<string, Parameter> */
private array $parameters;

public function __construct(Parameter ...$parameters)
{
$map = [];

foreach ($parameters as $parameter) {
$map[$parameter->name] = $parameter;
}

$this->parameters = $map;
}

/**
* @param mixed $parameters the parameters array from the laminas config
* @throws InvalidParametersException When a numeric key is encountered.
*/
public static function fromConfigValue(mixed $parameters): self
{
if (! is_iterable($parameters)) {
throw new InvalidParametersException(
sprintf(
'Injection parameters must be an array, got %s',
get_debug_type($parameters)
),
);
}

return new self(
...array_values(
Util::mapIterable(
$parameters,
static function (mixed $value, string|int $key): Parameter {
if ($value instanceof Parameter) {
return $value;
}

if (! is_string($key)) {
throw InvalidParametersException::numericParamKey($key);
}

return Parameter::fromValue($key, $value);
},
),
),
);
}

/**
* @return array<string, string|InjectionInterface>
*/
public function toArray(): array
{
return array_map(
static fn (Parameter $parameter) => $parameter->injection,
$this->parameters
);
}

public function get(string $key): Parameter | null
{
return $this->parameters[$key] ?? null;
}
}
Loading
Loading