Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Container Laravel Package

fastd/container

A lightweight PSR-11 dependency injection container for PHP applications. It provides service registration, lazy loading, autowiring, aliases, and service providers, with simple integration into the FastD framework and other PSR-compatible projects.

View on GitHub
Deep Wiki
Context7

Getting Started

The package is a small PSR-11 service container from the FastD microservice stack. Treat it as a dependency-injection primitive, not a framework: the value it gives you is centralising object construction so that classes ask for collaborators instead of new-ing them.

Install and confirm the contract

composer require fastd/container

It pulls in psr/container, so you get Psr\Container\ContainerInterface for free. That interface is the only API you can rely on across versions — get(string $id) and has(string $id). Anything beyond that (setters, autowiring helpers, singleton scoping) is package-specific and should be read off the class before you build on it.

Where to look first

  1. src/Container.php — with a package this small the container is essentially one class. Read it end to end; it will take ten minutes and will tell you more than the README.
  2. composer.json — check the PHP constraint and the psr/container version. That pins whether you are on the PSR-11 1.x or 2.x signature (the 2.x one adds return types, which changes how subclasses and decorators are written).
  3. The test suite, if present — it is the de facto documentation for definition semantics (closure vs. class-string, shared vs. fresh instance).
  4. The tag list. The latest tag is dated ahead of the visible docs, so verify the tag you install actually matches the source you are reading.

First use case

The honest first use case is a small service layer in a script or a FastD-based app where you currently have constructor chains like new Mailer(new HttpClient(new Config(...))). Register the leaf services once, then resolve the top of the graph:

use FastD\Container\Container;

$container = new Container();

$container->set('config', fn () => require __DIR__ . '/config.php');
$container->set(Client::class, fn ($c) => new Client($c->get('config')['api_url']));
$container->set(Mailer::class, fn ($c) => new Mailer($c->get(Client::class)));

$mailer = $container->get(Mailer::class);

If your installed version supports reflection-based autowiring, the middle two lines collapse and you only register the scalars. Confirm that by reading the class before you delete the explicit bindings.

Implementation Patterns

Definition styles. You will typically see three shapes for a definition, and it matters which one you use:

  • Instance — $c->set('clock', new SystemClock()). Always the same object; good for value objects, config, and things that are expensive to build.
  • Factory closure — $c->set(Repo::class, fn ($c) => new Repo($c->get(Pdo::class))). Called on resolution; gives you lazy construction and lets you control sharing yourself.
  • Class string — $c->set(Repo::class, Repo::class), relying on autowiring to fill the constructor. The tersest, but it silently breaks the moment a parameter is a scalar or an interface.

Mixing these inconsistently is the main source of "why did I get a different object?" questions. Pick one convention per layer and stick to it.

Interface-to-implementation binding. The highest-value pattern in any container: register against the abstraction, resolve against the abstraction.

$container->set(PaymentGateway::class, fn ($c) => new StripeGateway($c->get(Client::class)));

// call sites depend only on the interface
class Checkout
{
    public function __construct(private PaymentGateway $gateway) {}
}

This is what makes the container worth having — it is the seam where you swap Stripe for a fake in tests, or for a second provider per tenant.

Constructor injection end to end. Resist the urge to pass the container around. Instead, let the container build the outer object and have that object's constructor declare its own needs. Services that receive ContainerInterface tend to become service locators, and the dependency graph stops being readable from signatures.

Bootstrap aggregation. If you are using this inside FastD, keep a single bootstrap file that registers everything and hands the configured container to the app kernel. Splitting registration across many files is fine, but they should all receive the same container instance and be ordered explicitly — ordering matters when a factory closure eagerly resolves something at registration time.

Testing seam. In setUp, build a fresh container and override the slow or non-deterministic definitions:

$c = new Container();
$c->set(Clock::class, new FrozenClock('2025-01-01'));
$c->set(Mailer::class, new InMemoryMailer());
$this->checkout = $c->get(Checkout::class);

Building a new container per test (rather than a shared static) is worth the tiny cost; see the gotchas.

Integrating with code you do not control. For a legacy class that takes a container or a service locator, register a closure that pulls the real dependency out and injects it, so the legacy class never sees the container. Keep that adapter in one place.

Gotchas and Tips

get() throws, and it throws two different things. Per PSR-11, a miss raises NotFoundExceptionInterface and a construction failure raises ContainerExceptionInterface. Do not catch \Throwable around resolution and treat it as "not found" — you will mask a genuine constructor error as a missing binding and spend an hour on it. Catch the not-found interface specifically, and let the rest bubble.

Circular dependencies do not fail politely. A depends on B depends on A typically manifests as infinite recursion or a stack exhaustion rather than a clear "circular dependency" message. If you see a hang or a segfault on get(), suspect a cycle first. Break it with a setter injection or a lazy factory closure.

Closures are ambiguous. A closure that returns an object is a factory; a closure that is the service is not expressible. If you genuinely need to store a callable as a service, wrap it: fn () => $myCallable. Otherwise you will get the callable invoked back at you.

Autowiring dies on scalars and interfaces. Reflection can only build a parameter it can resolve by type. An int $retries or a LoggerInterface with no binding will fail at resolution time, not registration time — which means the failure surfaces deep in a request. Register scalars explicitly and prefer failing at bootstrap by resolving your top-level services once during startup in a smoke test.

Sharing is the trap in tests. If definitions are shared/cached by default, a container reused across tests leaks state: a singleton that captured a mock, a memoised repository, a mutated config array. Always construct a fresh container per test. Conversely, if your factories are not shared, you can accidentally construct the same expensive client three times in one request — decide per definition and be explicit.

Version pinning, given the ecosystem. Three stars and zero dependents means you are effectively the maintainer of your own copy for practical purposes. Pin the exact version in composer.json, and if the container becomes load-bearing, consider vendoring Container.php — it is small enough to own. Before adopting, check that the constraint on psr/container is compatible with the rest of your dependency graph; a stale 1.x constraint will conflict with modern packages and the resolver will blame you, not the container.

Debugging resolutions. When something resolves to the wrong object, dump the definition map and tag each entry with the file that registered it (a one-line wrapper around your set helper is enough). Nine out of ten container bugs are "two places registered the same id, later one won." A registration log turns that into a five-second diagnosis.

Extension points to prefer. Subclassing the container or decorating get() to add logging/decoration is the least invasive extension. Avoid reaching into private definition storage; if the class does not expose iteration over its definitions, add that in your subclass rather than reflecting into it — reflected internals are the first thing to break on a minor bump.

When not to use it. For a project with a handful of classes and no interfaces, the container is pure overhead and an indirection you have to debug. Its payoff starts at the point where you have multiple implementations of an abstraction, or where construction order and laziness genuinely matter.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
skyraptor/chatter
edlugz/sasapay
abbasudo/artisan-route
mirvan/ippanel
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource