Search by

waffle-commons / container

LesliePetrimaux

PSR-11 dependency-injection container for Waffle Commons: autowiring, singleton lifecycles, and per-request reset semantics for resident-worker mode.

Package info

github.com/waffle-commons/container

pkg:composer/waffle-commons/container

Statistics

Installs: 259

Dependents: 2

Suggesters: 0

Stars: 1

Open Issues: 1

0.1.0-beta7 2026-11-05 01:38 UTC

README

Discord PHP Version Require PHP CI codecov Latest Stable Version Latest Unstable Version Total Downloads Packagist License

Waffle Container Component

Release: 0.1.0-beta7 Β |Β  CHANGELOG.md PSR Compliance: PSR-11 (Psr\Container\ContainerInterface)

A strict PSR-11 service container with reflection-based autowiring, circular-dependency detection, and worker-mode resettability. Core services (the PSR-11 ContainerInterface itself) are locked from override after registration.

πŸ†• Beta-7 β€” one instance per service in AOT mode (CARRY-06)

public function memoise(string $id, Closure $build): object|string|null

Container::memoise() returns the instance the container holds for an id, building it with $build the first time, under the same circular-dependency guard as get(), which now builds through it. A compiled container (AOT, RFC-019) memoises the services it wires statically through it, so whichever path resolves an id first, the runtime and the compiled container share one instance, and reset() covers it once. It memoises instances, not definitions: it still works after lock(), but only for an id the container can resolve, and an unknown id throws NotFoundException, as get() does.

πŸ“¦ Installation

composer require waffle-commons/container

🧱 Surface

Class Role
Waffle\Commons\Container\Container The container. Implements Waffle\Commons\Contracts\Container\ContainerInterface (PSR-11 + ResettableInterface).
Waffle\Commons\Container\Autowire Reflection-based autowiring helper used by Container::build() to resolve constructor parameters.
Waffle\Commons\Container\Exception\ContainerException Thrown for retrieval / resolution failures.
Waffle\Commons\Container\Exception\NotFoundException Thrown when get($id) cannot resolve the identifier.

πŸš€ Usage

use Waffle\Commons\Container\Container;

$container = new Container([
    // Direct instance
    LoggerInterface::class => new StreamLogger(),

    // Class string β€” autowired via reflection on first get()
    UserService::class => UserService::class,

    // Factory closure
    'app.config' => static fn() => new Config(__DIR__ . '/config', 'prod'),
]);

$logger = $container->get(LoggerInterface::class);
$exists = $container->has(UserService::class);

$container->set('db.cache', new ArrayCache());

The exact public signature, verbatim from Waffle\Commons\Contracts\Container\ContainerInterface:

public function get(string $id): mixed;
public function has(string $id): bool;
public function set(string $id, object|callable|string $concrete): void;
public function reset(): void; // from ResettableInterface

πŸ” Worker-mode reset

Container implements ResettableInterface. After each request, the kernel calls reset() so the container drops its instance cache while keeping the registered definitions, preventing user-context leaks across FrankenPHP worker requests.

πŸ›‘οΈ Locked core services

The constant Container::CORE_SERVICES lists identifiers that must not be redefined once registered. The PSR-11 ContainerInterface itself is in that list β€” any attempt to override it after the container is built throws ContainerException.

πŸ”„ Circular-dependency detection

Container::get($id) tracks the resolution stack in $resolving and throws ContainerException if a cycle is detected before infinite recursion can occur.

🐘 PHP 8.5 features used

  • final class Container β€” no subclassing.
  • Typed properties throughout.
  • Typed constants for service registries: private const CORE_SERVICES = [...];.
  • #[\Override] on every method that overrides PSR-11.

🧭 Architectural boundary (mago guard)

An active dependency perimeter is enforced on every CI run by vendor/bin/mago guard (bundled into composer mago; zero baselines). The rules live in mago.toml under [guard.perimeter] β€” a forbidden use statement fails the build, not a reviewer.

Production code under Waffle\Commons\Container may depend only on:

  • Waffle\Commons\Container\** β€” itself
  • Waffle\Commons\Contracts\** β€” the shared contracts package, the only Waffle dependency permitted
  • Psr\** β€” PSR interfaces (PSR-11)
  • @global + Psl\** β€” PHP core and the PHP Standard Library

Test code under WaffleTests\Commons\Container is unrestricted (@all). Structural rules are guarded too: interfaces must be named *Interface, Exception\** classes must end in *Exception, and any Enum\** namespace may hold only enum declarations.

Contract-first, component-agnostic by construction: components compose through waffle-commons/contracts, never directly through one another.

πŸ§ͺ Testing

docker exec -w /waffle-commons/container waffle-dev composer tests

πŸ“š Documentation

Full guides live in the central DiΓ‘taxis documentation tree:

πŸ“„ License

MIT β€” see LICENSE.md.