Factory
PSR-11 container with three-stage resolution, from pre-registration through backend delegation to reflection.
Introduction
There are many dependency injection containers. Most require extensive configuration, YAML files, PHP definitions, annotations. For a platform package like Jardis, that is too much overhead: most services need no configuration, some are wired manually, and only a few live in an external container.
jardissupport/factory solves exactly this problem with a three-stage resolution chain: first pre-registered instances are checked, then an optional backend container (Symfony, Laravel, PHP-DI), and as a fallback the factory instantiates the class via reflection. No YAML, no annotations, no magic, three files, one interface.
- PSR-11 compatible — works everywhere
ContainerInterfaceis expected - Three-stage resolution — Instances → Backend → Reflection
- Zero configuration — classes without required parameters are automatically instantiated
- Backend integration — Symfony, Laravel or any other PSR-11 container as fallback
- Immutable — not modifiable after construction, readonly properties
Installation
composer require jardissupport/factoryGitHub: jardisSupport/factory
Basic Usage
Simplest Case — Reflection
use JardisSupport\Factory\Factory;
$factory = new Factory();
// Classes without required parameters are automatically instantiated
$service = $factory->get(SimpleService::class);Pre-Registration
$factory = new Factory(instances: [
LoggerInterface::class => $psrLogger,
CacheInterface::class => $cacheService,
'app.config' => ['debug' => true],
]);
$logger = $factory->get(LoggerInterface::class); // $psrLogger
$config = $factory->get('app.config'); // ['debug' => true]null and false are valid values, the check uses array_key_exists, not isset.
With Backend Container
// Symfony container as fallback
$factory = new Factory(
container: $symfonyContainer,
instances: [
CacheInterface::class => $customCache, // overrides Symfony
],
);Instances always take precedence over the backend container.
Resolution Chain
get() checks three stages, the first one that matches wins:
get('MyService')
├── 1. Instances[$id] present? → return instance
├── 2. Backend container->has($id)? → Backend->get($id)
└── 3. class_exists($id)? → new $id() via reflection
└── No stage matches? → NotFoundExceptionStage 3 — Reflection
The reflection stage instantiates classes only if they have no required constructor parameters:
// Works — no constructor or only optional parameters
class SimpleService {}
class ConfigurableService {
public function __construct(private string $name = 'default') {}
}
// Throws ContainerException — required parameters
class DatabaseService {
public function __construct(private PDO $pdo) {}
}For classes with required parameters: use create() or register as instance.
create() — Explicit Instantiation
create() bypasses the resolution chain entirely and instantiates via reflection with parameters:
// With parameters
$command = $factory->create(ProcessOrder::class, $orderId, $userId);
// Without parameters (like get(), but bypasses instances and backend)
$service = $factory->create(SimpleService::class);get() vs. create()
get($id) | create($class, ...$params) | |
|---|---|---|
| Checks instances | Yes | No |
| Checks backend | Yes | No |
| Accepts parameters | No | Yes |
| New instance guaranteed | Only on reflection | Always |
Error Handling
| Exception | Cause | PSR-11 Interface |
|---|---|---|
NotFoundException | None of the three stages finds $id | NotFoundExceptionInterface |
ContainerException | Class has required parameters or does not exist | ContainerExceptionInterface |
use JardisSupport\Factory\NotFoundException;
use JardisSupport\Factory\ContainerException;
try {
$service = $factory->get('unknown.service');
} catch (NotFoundException $e) {
// "Entry "unknown.service" not found in container."
}
try {
$db = $factory->get(DatabaseService::class);
} catch (ContainerException $e) {
// "Class DatabaseService has required constructor parameters.
// Register it as instance or use create()."
}Architecture
Three files, deliberately minimal:
src/
├── Factory.php ← PSR-11 container (orchestrator)
├── ContainerException.php ← Technical errors
└── NotFoundException.php ← ID not foundThe factory is immutable. $instances and $container are readonly properties. No register(), no set() after construction.
API Reference
Factory
| Method | Signature | Description |
|---|---|---|
get | get(string $id): mixed | Three-stage resolution (PSR-11) |
has | has(string $id): bool | Checks if $id would be resolvable (PSR-11) |
create | create(string $className, mixed ...$params): object | Direct instantiation via reflection |
Complete Example
use JardisSupport\Factory\Factory;
use Psr\Log\LoggerInterface;
use Psr\SimpleCache\CacheInterface;
// Prepare services
$logger = new FileLogger('/var/log/app.log');
$cache = new RedisCache($redis);
$pdo = new PDO('mysql:host=localhost;dbname=shop', 'root', '');
// Configure factory
$factory = new Factory(instances: [
LoggerInterface::class => $logger,
CacheInterface::class => $cache,
PDO::class => $pdo,
]);
// Resolution
$factory->get(LoggerInterface::class); // $logger (stage 1: instance)
$factory->get(SimpleValidator::class); // new SimpleValidator() (stage 3: reflection)
// Explicit creation with parameters
$handler = $factory->create(
OrderHandler::class,
$factory->get(PDO::class),
$factory->get(CacheInterface::class),
);