waffle-commons / container
PSR-11 dependency-injection container for Waffle Commons: autowiring, singleton lifecycles, and per-request reset semantics for resident-worker mode.
Requires
- php: ^8.5
- psr/container: ^2.0
- waffle-commons/contracts: 0.1.0-beta7
Requires (Dev)
- carthage-software/mago: ^1.59
- cyclonedx/cyclonedx-php-composer: ^6.2
- igor-php/igor-php: ^0.10.1
- php-mock/php-mock-phpunit: ^2.16
- phpunit/phpunit: ^12.5
- vimeo/psalm: ^6.20
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-11-05 01:59:11 UTC
README
Waffle Container Component
Release:
0.1.0-beta7Β |ΒCHANGELOG.mdPSR 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\**β itselfWaffle\Commons\Contracts\**β the shared contracts package, the only Waffle dependency permittedPsr\**β 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.