PHPackages                             sirix/container-resolver - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [PSR &amp; Standards](/categories/psr-standards)
4. /
5. sirix/container-resolver

ActiveLibrary[PSR &amp; Standards](/categories/psr-standards)

sirix/container-resolver
========================

Strict PSR-11 container service resolver and typed configuration reader

0.1.2(1mo ago)0485↓78.4%5MITPHPPHP ~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0CI passing

Since May 28Pushed 1mo ago1 watchersCompare

[ Source](https://github.com/sirix777/container-resolver)[ Packagist](https://packagist.org/packages/sirix/container-resolver)[ Fund](https://buymeacoffee.com/sirix)[ GitHub Sponsors](https://github.com/sirix777)[ RSS](/packages/sirix-container-resolver/feed)WikiDiscussions main Synced 1w ago

READMEChangelog (3)Dependencies (3)Versions (4)Used By (5)

Container Resolver
==================

[](#container-resolver)

Strict PSR-11 container service resolver and typed configuration reader for reusable PHP packages.

Stability
---------

[](#stability)

This package is currently pre-stable.

Until the first stable release, public contracts and behavior may change between releases based on real usage in Sirix packages. Semantic Versioning guarantees are not applied during this early iteration phase.

Use exact version constraints or review changelogs carefully when upgrading pre-stable versions.

Installation
------------

[](#installation)

```
composer require sirix/container-resolver
```

Why
---

[](#why)

Package factories often need to read services and configuration from framework containers. Manual checks tend to repeat the same code, silently coerce invalid values, or fail with messages that do not explain which factory needs which value.

`ContainerResolver` and `ConfigReader` keep factory code small while preserving explicit failures:

- missing container services throw package-level not-found exceptions;
- invalid service types throw package-level container exceptions;
- missing required config values throw package-level config exceptions;
- existing invalid config values always throw;
- optional missing config values can use defaults;
- scalar config values are not coerced.

The package is framework-agnostic and only requires PHP and `psr/container` at runtime.

Container services
------------------

[](#container-services)

Use `ContainerResolver` when a service id is the expected class or interface:

```
use Psr\Container\ContainerInterface;
use Sirix\ContainerResolver\ContainerResolver;

final class AuthManagerFactory
{
    public function __invoke(ContainerInterface $container): AuthManagerInterface
    {
        $resolver = ContainerResolver::forFactory($container, self::class);

        return new AuthenticationManager(
            $resolver->get(TokenStorageProviderInterface::class),
            $resolver->get(TokenTransportInterface::class),
        );
    }
}
```

Use `getAs()` when the service id is a custom string but the expected type is known:

```
$storage = $resolver->getAs('app.storage.redis', TokenStorageInterface::class);
```

Use `getExisting()` when you want the raw service value and will validate it yourself:

```
$value = $resolver->getExisting('config');
```

Use `optional()` for optional services:

```
$logger = $resolver->optional(LoggerInterface::class);
```

Use `optionalArray()` for array services such as `config`:

```
$config = $resolver->optionalArray('config');
```

If the service is missing, `optionalArray()` returns `[]`. If the service exists but is not an array, it throws `InvalidContainerServiceException`.

Typed configuration
-------------------

[](#typed-configuration)

`ConfigReader` reads nested arrays with dot paths:

```
use Sirix\ContainerResolver\ConfigReader;
use Sirix\ContainerResolver\ContainerResolver;

$resolver = ContainerResolver::forFactory($container, self::class);
$config = ConfigReader::fromContainer($resolver);

$driver = $config->stringEnum(
    'authentication.transport.driver',
    ['bearer', 'cookie'],
    default: 'bearer',
);
```

Available methods:

```
$config->has('app.name');
$config->get('app.name', default: 'demo');
$config->required('app.name');

$config->string('app.name', default: 'demo');
$config->requiredString('app.name');
$config->nonEmptyString('app.name', default: 'demo');
$config->requiredNonEmptyString('app.name');
$config->optionalString('app.name');
$config->optionalNonEmptyString('app.name');

$config->bool('debug', default: false);
$config->requiredBool('debug');

$config->int('port', default: 8080);
$config->requiredInt('port');

$config->array('items', default: []);
$config->requiredArray('items');

$config->list('entities', default: []);
$config->requiredList('entities');
$config->stringList('entities', default: []);
$config->requiredStringList('entities');
$config->nonEmptyStringList('entities', default: []);
$config->requiredNonEmptyStringList('entities');

$config->map('storages', default: []);
$config->requiredMap('storages');

$config->enum('log.level', LogLevel::class, default: LogLevel::Info);
$config->requiredEnum('log.level', LogLevel::class);

$config->stringEnum('driver', ['bearer', 'cookie'], default: 'bearer');
$config->requiredStringEnum('driver', ['bearer', 'cookie']);
```

Strictness rules
----------------

[](#strictness-rules)

Missing optional values return the supplied default:

```
$config->string('app.name', default: 'demo');
```

Existing invalid values always throw `InvalidConfigValueException`:

```
['app' => ['name' => 123]]; // invalid for string()
['debug' => 'false'];       // invalid for bool()
['port' => '8080'];         // invalid for int()
```

Valid scalar values must already have the expected type:

```
['app' => ['name' => 'api']];
['debug' => false];
['port' => 8080];
```

String readers trim leading and trailing whitespace from configured string values:

```
['app' => ['name' => ' api ']]; // returned as 'api'
```

This applies to string values, non-empty strings, string lists, string enums, and PHP enum case names/string-backed values. Trimming is normalization of strings only; scalar coercion is still not performed.

`optionalNonEmptyString()` is useful for optional values where an empty string should be treated as not configured:

```
$config->optionalNonEmptyString('cookie.domain');
```

Behavior:

```
[];                              // null
['cookie' => ['domain' => '']];  // null
['cookie' => ['domain' => '   ']]; // null
['cookie' => ['domain' => ' example.com ']]; // 'example.com'
['cookie' => ['domain' => 123]]; // invalid
```

`list()` requires a sequential list array:

```
$config = ConfigReader::fromArray([
    'entities' => [
        'src/App/src/Entity',
    ],
]);

$entities = $config->nonEmptyStringList('entities', default: []);
```

`entities` may be absent; in that case the default `[]` is returned. If `entities` exists, it must be a list of non-empty strings.

`enum()` returns real PHP enum instances:

```
enum LogLevel: int
{
    case Debug = 100;
    case Info = 200;
    case Warning = 300;
}

$level = $config->enum(
    'logging.level',
    LogLevel::class,
    default: LogLevel::Info,
);
```

Accepted configured values:

```
['logging' => ['level' => LogLevel::Debug]]; // enum instance
['logging' => ['level' => 'Debug']];         // case name
['logging' => ['level' => ' debug ']];       // trimmed case name, case-insensitive
['logging' => ['level' => 100]];             // backed value for int-backed enum
```

Numeric strings are not coerced for int-backed enums:

```
['logging' => ['level' => '100']]; // invalid for LogLevel: int
```

For string-backed enums, the string backed value is accepted:

```
enum Driver: string
{
    case Bearer = 'bearer';
    case Cookie = 'cookie';
}

$driver = $config->requiredEnum('driver', Driver::class);
```

`map()` requires all keys to be strings:

```
$config = ConfigReader::fromArray([
    'storages' => [
        'redis' => 'app.storage.redis',
        'db' => 'app.storage.db',
    ],
]);

$storages = $config->map('storages', default: []);
```

Lists are rejected by `map()` because they do not describe named configuration entries. String-keyed maps are rejected by `list()` because they are not sequential lists.

Long-running process safety
---------------------------

[](#long-running-process-safety)

The package is safe to use in long-running processes such as RoadRunner, Swoole, ReactPHP, queue workers, and persistent Mezzio/Laminas applications.

It does not use:

- global mutable state;
- static runtime caches;
- request-specific static properties;
- singleton resolver instances.

Resolver and reader instances are cheap to create per factory invocation:

```
public function __invoke(ContainerInterface $container): SomeService
{
    $resolver = ContainerResolver::forFactory($container, self::class);
    $config = ConfigReader::fromContainer($resolver);

    return new SomeService(
        dependency: $resolver->get(DependencyInterface::class),
        enabled: $config->bool('some.enabled', default: true),
    );
}
```

Exceptions do not retain service instances or full config arrays.

Exceptions
----------

[](#exceptions)

All package exceptions implement:

```
Sirix\ContainerResolver\Exception\ResolverException
```

Container-related exceptions also implement:

```
Sirix\ContainerResolver\Exception\ContainerResolverException
```

Config-related exceptions also implement:

```
Sirix\ContainerResolver\Exception\ConfigReaderException
```

Concrete exceptions:

```
Sirix\ContainerResolver\Exception\MissingContainerServiceException
Sirix\ContainerResolver\Exception\InvalidContainerServiceException
Sirix\ContainerResolver\Exception\MissingConfigValueException
Sirix\ContainerResolver\Exception\InvalidConfigValueException
```

`MissingContainerServiceException` implements `Psr\Container\NotFoundExceptionInterface`. `InvalidContainerServiceException` implements `Psr\Container\ContainerExceptionInterface`.

`ContainerResolver` wraps missing-service and invalid-type failures into package exceptions. Other container resolution failures from the underlying PSR-11 container are propagated unchanged.

That means:

- missing service or `NotFoundExceptionInterface` from the container becomes `MissingContainerServiceException`;
- wrong resolved service type becomes `InvalidContainerServiceException`;
- other `ContainerExceptionInterface` failures from the container remain the original container exception.

Public methods document their expected failure modes with `@throws` annotations.

Consumers can catch and wrap package exceptions:

```
use Sirix\ContainerResolver\Exception\ResolverException;

try {
    // factory logic
} catch (ResolverException $exception) {
    throw MyPackageConfigurationException::fromPrevious($exception);
}
```

Examples
--------

[](#examples)

Factory with configured service ids:

```
use Psr\Container\ContainerInterface;
use Sirix\ContainerResolver\ConfigReader;
use Sirix\ContainerResolver\ContainerResolver;
use Sirix\ContainerResolver\Exception\InvalidConfigValueException;

final class TokenStorageProviderFactory
{
    public function __invoke(ContainerInterface $container): TokenStorageProviderInterface
    {
        $resolver = ContainerResolver::forFactory($container, self::class);
        $config = ConfigReader::fromContainer($resolver);

        $defaultStorage = $config->nonEmptyString('authentication.default_storage', default: 'null');

        $storages = [
            'null' => $resolver->get(NullTokenStorage::class),
        ];

        foreach ($config->map('authentication.storages', default: []) as $name => $serviceId) {
            if (! is_string($serviceId) || '' === $serviceId) {
                throw InvalidConfigValueException::forType(
                    "authentication.storages.{$name}",
                    'non-empty-string',
                    $serviceId,
                    self::class,
                );
            }

            $storages[$name] = $resolver->getAs($serviceId, TokenStorageInterface::class);
        }

        if (! isset($storages[$defaultStorage])) {
            throw InvalidConfigValueException::forAllowedValues(
                'authentication.default_storage',
                array_keys($storages),
                $defaultStorage,
                self::class,
            );
        }

        return new TokenStorageProvider($defaultStorage, $storages);
    }
}
```

Design notes
------------

[](#design-notes)

This package intentionally does not provide:

- a DI container;
- autowiring;
- service definitions;
- framework adapters;
- config schema compilation;
- deep config merging;
- environment variable processors;
- secret resolution;
- runtime caching;
- scalar coercion.

###  Health Score

42

—

FairBetter than 88% of packages

Maintenance89

Actively maintained with recent releases

Popularity18

Limited adoption so far

Community10

Small or concentrated contributor base

Maturity43

Maturing project, gaining track record

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~1 days

Total

3

Last Release

56d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/6ecccf9003c061847e877eeea3bdf1b382f6f9dbb11d33112d6b2740bf0533f9?d=identicon)[sirix777](/maintainers/sirix777)

---

Tags

containerPSR-11factoryconfigurationconfigstrict

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/sirix-container-resolver/health.svg)

```
[![Health](https://phpackages.com/badges/sirix-container-resolver/health.svg)](https://phpackages.com/packages/sirix-container-resolver)
```

###  Alternatives

[sandrokeil/interop-config

Provides interfaces and a concrete implementation to create instances depending on configuration via factory classes and ensures a valid config structure. It can also be used to auto discover factories and to create configuration files.

58483.7k37](/packages/sandrokeil-interop-config)[infocyph/intermix

A lightweight PHP DI container, invoker, serializer, and utility toolkit.

137.7k3](/packages/infocyph-intermix)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
