yiisoft/factory
A flexible factory for Yii3 that creates objects from class names, definitions, or callables, resolving constructor dependencies through a PSR-11 container. Ideal for configurable services and dependency injection setups.
yiisoft/factory is a small, focused object factory: give it a PSR-11 container and a class name, and it reflects the constructor, pulls typed dependencies out of the container, and gives you a fully built instance. It is the object-construction layer that sits under yiisoft/di, so you will often use it indirectly — but it is genuinely useful on its own when you need to build objects with runtime arguments that should not be baked into container definitions.
composer require yiisoft/factory
You need a PSR-11 container implementation (yiisoft/di, league/container, or even a hand-written stub). Then:
use Yiisoft\Factory\Factory;
$factory = new Factory($container);
$report = $factory->create(ReportGenerator::class);
ReportGenerator's constructor dependencies are resolved by type from the container; anything the container cannot supply must be provided explicitly or have a default value.
Where to look first:
FactoryInterface::create(string $id, array $parameters = []) — this is the whole public contract.setDefinitions()), which is where 90% of the real configuration lives.src/Exception/ — the failure modes (NotFoundException, InvalidConfigException, circular-reference errors) tell you exactly what the factory will refuse to do.First meaningful use case: a class that needs a mix of injected services and per-call data.
final class InvoicePdf
{
public function __construct(
private RendererInterface $renderer, // from container
private string $currency, // runtime
) {}
}
$pdf = $factory->create(InvoicePdf::class, ['currency' => 'EUR']);
That's the sweet spot: services from the container, scalars from the call site.
Auto-wiring by type. For every constructor parameter with a class type, the factory asks the container for that type (or resolves it from definitions), recursing as needed. This means most of your services need zero configuration as long as their leaf dependencies are registered or instantiable.
Definitions map. Register interface→implementation mappings and per-class configuration once:
$factory->setDefinitions([
LoggerInterface::class => FileLogger::class,
ReportGenerator::class => [
'class' => ReportGenerator::class,
'format' => 'csv',
'__construct()' => ['limit' => 500], // explicit constructor args
],
]);
The array form is the workhorse: 'class' picks the implementation, remaining keys map to constructor parameter names.
Runtime parameter overrides. Pass a second argument to create() to override constructor arguments by name. Prefer named keys — positional keys silently break the moment someone reorders the constructor. Overrides apply to that object only; they do not mutate the definition.
Callable definitions for anything reflection can't do — static factories, named constructors, private constructors, or values that need imperative setup:
$factory->setDefinitions([
PDO::class => static fn (ContainerInterface $c) => new PDO($c->get('db.dsn')),
]);
Late binding with references. Yiisoft\Definitions\Reference::to(SomeId::class) defers resolution to the container, and DynamicReference::to(fn () => ...) defers to call time. Use these when a definition's value depends on another service or on environment that isn't known at config-build time.
Integration pattern — inject the interface, not the class. Type-hint FactoryInterface in services that need to build things at runtime, and bind it in your DI config:
FactoryInterface::class => static fn (ContainerInterface $c) => new Factory($c),
This keeps your code testable and avoids coupling to the concrete implementation.
Workflow in a Yii3 app. Before adding a definition to the factory, check config/common/di.php. If the object is a shared service, it belongs in the DI container's singletons; if it is a short-lived object with per-call parameters (a form model, a job payload handler, a report builder), it belongs in the factory.
Testing. Swap in a stub PSR-11 container to assert which dependencies get requested, or use the factory directly to build units under test with fake collaborators injected via definitions.
create() does not cache. Every call reflects and builds a new instance. If you want a singleton, that is yiisoft/di's job — do not use the factory as a service locator in a hot loop.string $dsn parameter with no default will throw unless you pass it in create() or the definition. Optional and nullable parameters fall back to defaults, so the failure can be delayed until the one call site that omits the value.'class' key is ambiguous; be explicit. When in doubt, use ArrayDefinition from yiisoft/definitions rather than raw arrays — you get validation and clearer errors.NotFoundException (class missing / not instantiable), InvalidConfigException (bad definition shape), and NotCallableException (callable definition that isn't callable) are distinct — handle them separately in code paths where misconfiguration should surface as a clear error.FactoryInterface with a decorator that wraps the real factory to add logging, timing, or metrics — cheaper and safer than subclassing Factory, whose internals are not a stability contract.How can I help you explore Laravel packages today?