PHPackages                             polymorphine/container - 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. polymorphine/container

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

polymorphine/container
======================

PSR-11 Container for libraries &amp; configuration

1.0.5(1y ago)03251MITPHPPHP ^7.4 || ^8.0

Since Mar 5Pushed 1y ago1 watchersCompare

[ Source](https://github.com/polymorphine/container)[ Packagist](https://packagist.org/packages/polymorphine/container)[ RSS](/packages/polymorphine-container/feed)WikiDiscussions develop Synced 3d ago

READMEChangelog (6)Dependencies (2)Versions (7)Used By (1)

Polymorphine/Container
======================

[](#polymorphinecontainer)

[![Latest stable release](https://camo.githubusercontent.com/3eefbc2e3c8be35d3e479a5b151d94a58ae239fdd114f2fa4b65d253cb3f4ecc/68747470733a2f2f706f7365722e707567782e6f72672f706f6c796d6f727068696e652f636f6e7461696e65722f76657273696f6e)](https://packagist.org/packages/polymorphine/container)[![Build status](https://github.com/polymorphine/container/workflows/build/badge.svg)](https://github.com/polymorphine/container/actions)[![Coverage status](https://camo.githubusercontent.com/b097d711dacb9f726d81e324a47af7ef6b87b9b05f4c98b65bb073bac6cb2af5/68747470733a2f2f636f766572616c6c732e696f2f7265706f732f6769746875622f706f6c796d6f727068696e652f636f6e7461696e65722f62616467652e7376673f6272616e63683d646576656c6f70)](https://coveralls.io/github/polymorphine/container?branch=develop)[![PHP version](https://camo.githubusercontent.com/cf2e3506d93ea56c9d1a337def469f5aeb9f33aa0f158cddc45e74cde083a9b3/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f7068702d762f706f6c796d6f727068696e652f636f6e7461696e65722e737667)](https://packagist.org/packages/polymorphine/container)[![LICENSE](https://camo.githubusercontent.com/0c8475f8f14de6e14dde98bc7bff5b0ea9c536ba29bf8b4e01657dc0dbd3d522/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f6c6963656e73652f706f6c796d6f727068696e652f636f6e7461696e65722e7376673f636f6c6f723d626c7565)](LICENSE)

### PSR-11 Container for libraries &amp; configuration

[](#psr-11-container-for-libraries--configuration)

#### Concept features:

[](#concept-features)

- immutable PSR-11 implementation
- encapsulated configuration ([more](#read-and-write-separation))
- abstract strategy to retrieve stored values ready to extend functionality with some original built-in strategies ([more](#records-decide-how-it-works-internally))
- composition with sub-Containers
- optional path notation access to config values ([more](#configuration-container))
- dev mode for integrity checks, call stack tracking &amp; circular reference protection ([more](#secure-setup--circular-reference-detection))
- explicit configuration by default - auto-wired dependencies can be resolved by custom strategies (not included)
- intended limited use for generic stuff: libraries, configuration and other context independent objects or functions ([more](#recommended-use))

### Installation with [Composer](https://getcomposer.org/)

[](#installation-with-composer)

```
composer require polymorphine/container
```

### Container setup

[](#container-setup)

This example will show how to set up simple container. It starts with instantiating [`Setup`](src/Setup.php) type object, and using its methods to set container's entries:

```
use Polymorphine\Container\Setup;

$setup = Setup::production();
$setup->set('value')->value('Hello world!');
$setup->set('domain')->value('http://api.example.com');
$setup->set('direct.object')->value(new ClassInstance());
$setup->set('deferred.object')->callback(function (ContainerInterface $c) {
    return new DeferredClassInstance($c->get('value'));
});
$setup->set('composed.factory')->instance(ComposedClass::class, 'direct.object', 'deferred.object');
$setup->set('factory.product')->product('composed.factory', 'create', 'domain');

$container = $setup->container();

$container->has('composed.factory'); // true
$container->get('factory.product'); // return result of ComposedClass::create() call
```

Instead configuring each entry using builder methods you can pass arrays of [`Record`](src/Records/Record.php)instances to one of `Setup` constructors:

```
$setup = Setup::production([
   'value'            => new Record\ValueRecord('Hello world!'),
   'domain'           => new Record\ValueRecord('http://api.example.com'),
   'direct.object'    => new Record\ValueRecord(new ClassInstance()),
   'deferred.object'  => new Record\CallbackRecord(function (ContainerInterface $c) {
                             return new DeferredClassInstance($c->get('env.value'));
                         }),
   'composed.factory' => new Record\InstanceRecord(Factory::class, 'direct.object', 'deferred.object'),
   'factory.product'  => new Record\ProductRecord('composed.factory', 'create', 'domain')
]);

// add more entries here with set() methods
// and instantiate container...

$container = $setup->container();
```

Of course if all entries will be added with constructor `Setup` instantiation is not even necessary, and [Instantiating Container directly](#direct-instantiation--container-composition) might be better idea.

`Setup::container()` may be called again after more entries were added, but the call will return new, independent container instance. It is also recommended to encapsulate [`Setup`](src/Setup.php) within controlled scope ad described in section on [read and Write separation](#read-and-write-separation).

#### Records decide how it works internally

[](#records-decide-how-it-works-internally)

Values returned from Container are initially wrapped into [`Record`](src/Records/Record.php) abstraction that allows for different strategies to produce them - it may be either returned directly or internally created by calling its (lazy) initialization procedure. Here's short explanation of package's Record implementations:

- `ValueRecord`: Direct value, that will be returned as it was passed (callbacks will be returned without evaluation as well). To push value record mapped to given string `identifier` into container with setup object use `Entry::value()` method: ```
    $setup->set('identifier')->value($anything);
    ```
- `CallbackRecord`: Lazily invoked and cached value. Takes callback that will be given container as parameter, and value of this call will be cached and returned on subsequent calls. Records are added to setup with `Entry::callback()` method: ```
    $setup->set('identifier')->callback(function ($container) { return ... });
    ```
- `InstanceRecord`: Lazy instantiated (and cached) object of given class. Constructor parameters are passed as resolved aliases to other container entries. Setup with `Entry::instance()` method: ```
    $setup->set('identifier')->instance(Namespace\ClassName::class, 'dependency-identifier', 'another', ...);
    ```
- `ProductRecord`: Similar to instance method, but object is created (and cached) by calling method given as string on container provided instance of factory, and container identifiers will resolve into arguments for this method. Setup with `Entry::product()` method: ```
    $setup->set('identifier')->create('factory.id', 'createMethod', 'container.param1', 'container.param2', ...);
    ```
- `ComposedInstanceRecord`: With `Entry::wrappedInstance()` method it is possible to build single entry in chained call allowing to compose multi layered structure of wrapped (decorated) instance entries ([read more](#composed-entries))

Custom `Record` implementations might be mutable, return different values on subsequent calls or introduce various side effects, but it is not recommended.

#### Composed entries

[](#composed-entries)

##### Record composition using Wrapper

[](#record-composition-using-wrapper)

Entry may be built with multiple instance descriptors (same parameters as `InstanceRecord` uses) created in chained [`Wrapper`](src/Setup/Wrapper.php) calls:

```
$setup->set('A')
      ->wrappedInstance(SomeClass::class, 'B', 'C')
      ->with(AnotherClass::class, 'A', 'D')
      ->with(AndAnother::class, 'A')
      ->compose();
```

Notice that *wrapper definition contains reference to wrapped entry* as one of its dependencies. Without it exception will be thrown because it wouldn't constitute composition but definition of different instance that should've been defined with `Entry::instance()` method. This self-reference will not cause circular calls because it isn't used as standalone container entry (as identifiers for other dependencies), but a placeholder pointing wrapped instance in composition process.

#### Composite Container

[](#composite-container)

`Entry::container()` method can be used to add another ContainerInterface instances and create composite container by wrapping multiple sub-containers which values (or containers themselves) may be accessed with container's id prefix (dot notation):

```
$subContainer = new PSRContainerImplementation();
$setup->set('env')->container($subContainer);

$container = $setup->container();
$container->get('env') === $subContainer; //true
$container->has('env.some.id') === $subContainer->has('some.id'); //true
```

Passing array of ContainerInterface instances together with records `Setup` will also build composite container:

```
$setup = Setup::production($records, ['env' => new PSRContainerImplementation()]);
```

#### Secure setup &amp; circular reference detection

[](#secure-setup--circular-reference-detection)

Secure setup is designed as a development tool that helps with setup debugging. It is instantiated either with `development` static constructor:

```
$setup = Setup::development($records, $containers);
```

or with [`ValidatedBuild`](src/Setup/Build/ValidatedBuild.php) instance passed to default constructor:

```
$setup = new Setup(new Setup\Build\ValidatedBuild($records, $containers));
```

##### Naming rules and inaccessible entries

[](#naming-rules-and-inaccessible-entries)

Because the way enclosed containers are accessed and because they're stored separately from Record instances some naming constraints are required:

> *Sub-container identifier MUST be a string, MUST NOT contain separator (`.` by default) and MUST NOT be used as id prefix for stored `Record`.*

Having container stored with `foo` identifier would make `foo.bar` record inaccessible, because this value would be assumed to come from `foo` container. The rules might be hard to follow with multiple entries and sub-containers, so runtime checks were implemented.

Basic (production) `Setup` instantiated directly or with `Setup::production()` method won't check whether given identifiers are already defined or whether they will cause name collision that would make some entries inaccessible (sub-containers with identifier used record entry prefix).

Instantiating validated `Setup::development()` or directly with `ValidatedBuild` instance will enable runtime integrity checks for container configuration, and make sure that all defined identifiers can be accessed with `ContainerInterface::get()` method.

##### Circular references

[](#circular-references)

Because Records may refer to other container entries to be built (instantiated) a hard to spot bug might be introduced where entry `A` in order to be resolved will need to retrieve itself during build process starting endless loop and eventually blowing up the stack. For example:

```
$setup->set('A')->instance(SomeClass::class, 'B');
$setup->set('B')->instance(AnotherClass::class, 'C', 'A');
```

Both entries `A` and `B` refer each other, so instantiating `B` would need `A` that will attempt to instantiate `B` in nested context. Neither class can be instantiated, because its dependencies cannot be fully resolved (are currently being resolved on higher context level) - without detection the instantiation process would continue until call stack is overflown.

Container able to detect those circular references and append call stack information to exceptions being thrown (for both circular references and missing entries) is another feature that `development` setup comes with. `ContainerInterface::get()` would throw `CircularReferenceException` immediately after recursive container call on deeper context level would try to retrieve currently resolved record, which will allow to exit the endless loop.

> These checks are not included in `Setup::production()`, because they should not be required in production environment. Although it is recommended to use them during **development**.

Integration tests are necessary in development, because misconfigured container will most likely crash the application, and it cannot be controlled by code in reliable way. Development setup will not prevent all the bugs that might happen, so it becomes needless performance overhead in production environment. It's worth noticing however, that visible drop in performance by using those checks in development stage will most likely mean that container is used too extensively - see [recommended use](#recommended-use) section.

#### Direct instantiation &amp; container composition

[](#direct-instantiation--container-composition)

`Setup` provides helper methods to create `Record` instances and collect them together, optionally with sub-container entries and additional validation checks creating immutable container composition. Creating container directly is also possible - for example simple container containing only `Record`entries would be instantiated with as flat `Record[]` array (here stored in `$records` variable) this way:

```
$container = new RecordContainer(new Records($records));
```

When container needs circular reference checking and encapsulate some sub-containers stored in `$containers`variable as flat `ContainerInterface[]` array its instantiation would change into this composition:

```
$container = new CompositeContainer(new TrackedRecords($records), $containers);
```

### Configuration Container

[](#configuration-container)

[`ConfigContainer`](src/ConfigContainer.php) that comes with this package is a convenient way to store and retrieve values from multidimensional associative arrays using path notation. This container is instantiated directly with array passed to constructor, which values can be accessed by dot-separated keys on consecutive nesting levels. Example:

```
$container = new ConfigContainer([
    'value' => 'Hello World!',
    'domain' => 'http://api.example.com',
    'pdo' => [
        'dsn' => 'mysql:dbname=testdb;host=localhost',
        'user' => 'root',
        'pass' => 'secret',
        'options' => [
            PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8',
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
        ]
    ]
]);

$container->get('pdo'); // ['dsn => 'mysql:dbname=testdb;host=localhost', 'user' => 'root', ...]
$container->get('pdo.user'); // root
$container->get('pdo.options'); // [ PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8', ... ]
```

As it was described in [composite container](#composite-container) section you can use both record-based and config container as a single Container using `Entry::container()` method. Having above `$container` defined, you can recreate main example which uses its `value` and `domain` entries:

```
...
$setup = new Setup();
$setup->set('env')->container($conatiner);
$setup->set('direct.object')->value(new ClassInstance());
$setup->set('deferred.object')->callback(function (ContainerInterface $c) {
  return new DeferredClassInstance($c->get('env.value'));
});
$setup->set('factory.object')->instance(FactoryClass::class, 'direct.object', 'deferred.object');
$setup->set('factory.product')->product('factory.object', 'create', 'env.domain');

$container = $setup->container();
```

Note additional path prefixes for `value` and `domain` within `deferred.object` and `factory.product`definitions compared to records used in original example. These values are still fetched from `ConfigContainer`, but accessed through composite container using `env` prefix. This way values from both config and record containers encapsulated inside composite container can be retrieved:

```
...
echo $container->get('env.value'); // Hello world!
echo $container->get('env.pdo.user'); // root
$object = $container->get('factory.product');
```

Object created with `$container->get('factory.product')` will be the same as instantiated objects directly using `new` operator shown in [Containers vs direct instantiation](#containers-vs-direct-instantiation)section with extended take on the subject.

### Recommended use

[](#recommended-use)

#### Read and Write separation

[](#read-and-write-separation)

`Setup` builder-like API allows for setting up container and creating its instance, but it would result in cleaner design to have container encapsulated and still be able to configure it from outside scope. This could be achieved by proxy object exposing only setup methods.

Calling `Setup::set()` returns write-only `Entry` helper object. Beside providing methods to define various implementations of `Record` or sub-containers for configured *container* it allows to implement proxy with single method instead polluting its interface with multiple setup methods. For example, if you have front controller bootstrap class similar to...

```
class App
{
    private $setup;
    ...
    public function config(string $name): Entry
    {
        return $this->setup->set($name);
    }
    ...
}
```

...you can still use all helper methods provided by `Entry` object. Now You can push values into container from the scope of `App` class object, but cannot access container afterwards. `App`controls the `Setup` and will call `Setup::container()` to use on its own terms.

```
$app = new App(parse_ini_file('pdo.ini'));
$app->config('database')->callback(function (ContainerInterface $c) {
    return new PDO(...$c->get('env.pdo'));
});
```

Nothing in outer scope will be able to use instance of container created within `App`. It is possible to achieve with some configuration efforts, but this is not recommended, so details won't be explained here.

#### Real advantage of container

[](#real-advantage-of-container)

##### Containers vs direct instantiation

[](#containers-vs-direct-instantiation)

Instantiating container with setup commands used in [main example](README.md#container-setup) and getting `factory.product` object will be equivalent to factory instantiated directly with `new` operator and calling `create()` method on it with `http://api.example.com` parameter:

```
$factory = new ComposedClass(new ClassInstance(), new DeferredClassInstance('Hello world!'));
$object  = $factory->create('http://api.example.com');
```

As you can see the container does not give any visible advantage here over creating object directly, and assuming this object is used only in single use-case scenario there won't be any.

For libraries used in various request contexts or reused in the structures where the same instance should be passed (like database connection) having it configured in one place saves lots of trouble and repetition. Suppose that class is some api library that requires configuration and composition used by a few endpoints of your application - you would have to repeat this instantiation for each endpoint. You can still solve this problem encapsulating instantiation within hardcoded factory class and replace `$container->get()` with single (static) call (and type-hinted result!)

##### Containers vs decomposed factories and Singleton pattern

[](#containers-vs-decomposed-factories-and-singleton-pattern)

Mentioned factories introduce another problem though. You might not be able to tell up front which individual component of created object might be needed elsewhere and it would be necessary to extract it from existing factory into another factory and call it in both places - the factory it was extracted from and the other part that needs this component. When the same instance is needed such factory in some cases would need to cache created object - most probably using [*Singleton pattern*](https://en.wikipedia.org/wiki/Singleton_pattern).

*Singleton pattern* is needed when same object needs to be provided in different scopes of the code. When only single factory uses it there's no need for singleton. The number of injection points doesn't matter since it can be passed in all of them as previously created local variable. Singleton pattern objects are available in global scope for any part of the code and this makes them hard to maintain since you don't really know where it is or will be used.

For example authentication service might use session, so it's not enough to write factory for auth service, but one for session is also needed, because session might be used not only in many different contexts, but also in different application scopes (building both middleware and use case compositions). Having a number of singleton factories called in multiple places results in hard to comprehend code even when they're used in disciplined manner - that is only in composition layer ("main" partition).

Container solves both decomposition and scope control problem, because all components can also be container entries and it's usage scope is strictly limited to the places it was injected. This flexibility is the only advantage of (standard) containers that cannot be easily replaced in other way. However some discipline regarding containers is also required.

##### Containers and Service locator anti-pattern

[](#containers-and-service-locator-anti-pattern)

Containers shouldn't be injected as a wrapper providing direct (objects that will be called in the same scope) dependencies of the object, because that will expose dependency on container while hiding types of objects we really depend on. It may seem appealing that we can freely inject lazily invoked objects with possibility of not using them, but these unused objects, in vast majority of cases, should denote that our object's scope is too broad. Branching that leads to skipping method call (OOP message sending) on one of dependencies should be handled up front, which would make our class easy to test and read. Making exceptions for sake of easier implementation will quickly turn into standard practice (especially within larger or remote working teams), because consistency seems plausible even when it concerns bad practices. Healthy constraints are more reliable than expected reasoning.

##### Container in factory is harmless

[](#container-in-factory-is-harmless)

Dependency injection container should help with dependency injection, but not replace it. It's fine to **inject container into main factory objects** in framework controlled scope, because factory itself does not make calls on objects container provides and it doesn't matter what objects factory is coupled to. Treat application objects composition as a form of configuration.

#### Why no auto-wiring (yet)?

[](#why-no-auto-wiring-yet)

Explicitly hardcoded class compositions whether instantiated directly or indirectly through container might be traded for convenient auto-wiring, but in my opinion its cost includes important part of polymorphism, which is resolving preconditions. This is not the price you pay up front, and while debt itself is not inherently bad, forgetting you have one until you can't pay it back definitely is.

###  Health Score

34

—

LowBetter than 77% of packages

Maintenance42

Moderate activity, may be stable

Popularity12

Limited adoption so far

Community9

Small or concentrated contributor base

Maturity62

Established project with proven stability

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

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 ~285 days

Recently: every ~225 days

Total

6

Last Release

473d ago

PHP version history (2 changes)1.0.0PHP ^7.4

1.0.1PHP ^7.4 || ^8.0

### Community

Maintainers

![](https://www.gravatar.com/avatar/5b83b30083b2ca0951558f0516ded598877c690f2be60cf949d9be3fdf0389ca?d=identicon)[shudd3r](/maintainers/shudd3r)

---

Top Contributors

[![shudd3r](https://avatars.githubusercontent.com/u/9908030?v=4)](https://github.com/shudd3r "shudd3r (293 commits)")

---

Tags

containerdependency-injectionpsr-11

### Embed Badge

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

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

###  Alternatives

[pimple/pimple

Pimple, a simple Dependency Injection Container

2.7k130.5M1.4k](/packages/pimple-pimple)[league/container

A fast and intuitive dependency injection container.

86387.8M343](/packages/league-container)[lctrs/psalm-psr-container-plugin

Let Psalm understand better psr11 containers

17648.1k13](/packages/lctrs-psalm-psr-container-plugin)[phpwatch/simple-container

A fast and minimal PSR-11 compatible Dependency Injection Container with array-syntax and without auto-wiring

1810.1k2](/packages/phpwatch-simple-container)

PHPackages © 2026

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