milpa/container

Reflection-autowiring PSR-11 dependency injection container for the Milpa PHP framework: lazy singleton resolution, circular-dependency detection with chain reporting, and safe optional retrieval.

Maintainers

Package info

github.com/getmilpa/container

pkg:composer/milpa/container

Transparency log

Statistics

Installs: 1

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v0.1.0 2026-07-07 18:26 UTC

This package is auto-updated.

Last update: 2026-07-07 19:45:49 UTC


README

Milpa

Milpa Container

The reference dependency injection container for the Milpa PHP framework, built on milpa/core. It implements milpa/core's DIContainerInterface with reflection autowiring, lazy singleton resolution, and circular-dependency detection that reports the full chain — on top of a PSR-11 surface backed by Symfony's ContainerBuilder.

CI Packagist PHP License Docs

milpa/container is the concrete DIContainer behind milpa/core's Milpa\Interfaces\Di\DIContainerInterface — the contract every Milpa component codes against. No product coupling, no service-definition config format of its own: register services by hand, or let the container find them by reflection.

Install

composer require milpa/container

What it guarantees, beyond the interface

DIContainerInterface is deliberately conservative: it documents auto-resolution of get()/has() as a MAY, not a MUST — a minimal, spec-conformant implementation is allowed to throw/return false for anything not explicitly registered via registerService(). resolve() is the one method whose contract is auto-wiring for every implementation.

DIContainer exercises that MAY. Concretely, this implementation guarantees:

  • get() and has() auto-resolve any existing, non-abstract, non-interface class whose constructor dependencies are themselves resolvable, recursively — not just identifiers registered via registerService().
  • Auto-resolved classes are cached as singletons on first resolution: later get() calls for the same identifier return the same instance, unless resolve() was called directly with $singleton = false.
  • Circular dependencies are detected and reported with the full chainA needs B needs A, directly or transitively, throws CircularDependencyException with every class name in the cycle, not just a generic "circular dependency" message.
  • tryGet() never throws — it returns null for anything unregistered and unresolvable, instead of propagating ServiceNotFoundException or ContainerResolutionException.
  • Non-resolvable classes are cached as such (interfaces, abstract classes, classes with unresolvable constructor parameters), so repeated lookups don't re-run reflection.

If you need one of these guarantees, depend on DIContainer (or its documented behavior) directly — DIContainerInterface alone does not promise them.

Quick example

use Milpa\Container\DIContainer;

class Logger
{
    public function log(string $message): void
    {
        echo $message . "\n";
    }
}

class Greeter
{
    public function __construct(private Logger $logger)
    {
    }

    public function greet(string $name): void
    {
        $this->logger->log("Hello, {$name}!");
    }
}

$container = new DIContainer();

// Auto-wiring: no registerService() call needed — Greeter's constructor
// dependency (Logger) is resolved recursively.
$greeter = $container->get(Greeter::class);
$greeter->greet('World'); // "Hello, World!"

// get() caches auto-resolved classes as singletons.
$container->get(Greeter::class) === $greeter; // true

// tryGet() never throws — null for anything unregistered/unresolvable.
$container->tryGet('Nonexistent\Service'); // null

// Explicit registration still wins over auto-wiring for the same id.
$container->registerService(Logger::class, new Logger());

Circular dependencies fail loudly, with the chain that caused them:

class A { public function __construct(public B $b) {} }
class B { public function __construct(public A $a) {} }

$container->get(A::class);
// throws Milpa\Exceptions\CircularDependencyException:
// "Circular dependency detected while resolving: A -> B -> A."

What lives where

Layer Package Owns
Contracts milpa/core DIContainerInterface (extends PSR-11 ContainerInterface), ServiceNotFoundException, ContainerResolutionException, CircularDependencyException — the seam, not the engine.
Implementation milpa/container (this package) The concrete DIContainer: reflection autowiring, singleton caching, circular-dependency detection, and safe (tryGet()) retrieval on top of Symfony's ContainerBuilder.
Your app your host / plugins Wiring decisions — which services to register explicitly vs. leave to autowiring, and when to call compileContainer().

Requirements

Documentation

Full API reference: getmilpa.github.io/container — generated straight from the source DocBlocks and dressed with the Milpa design system.

Contributing

Contributions are welcome — see CONTRIBUTING.md. Please report security issues via SECURITY.md, and note that this project follows a Code of Conduct.

License

Apache-2.0 © TeamX Agency.

Milpa is designed, built, and maintained by TeamX Agency.