Codex solves one problem: given a class name, create an instance of it with all its dependencies automatically resolved.
Say you have a class like this:
class OrderService {
public function __construct(
private Database $db,
private Logger $logger,
) {}
}Normally you'd have to wire this up yourself:
$logger = new Logger();
$db = new Database($connectionString);
$service = new OrderService($db, $logger);With Codex, you just say:
$resolver = Resolver::forContainer($container);
$service = $resolver->resolve(OrderService::class);Codex reads the constructor using PHP's Reflection API, sees it needs a Database and a Logger, resolves those too (recursively), and hands you a fully built OrderService. It's like a factory that can build anything.
When you call resolve(OrderService::class):
- Event — fires a
ClassRequestedevent so listeners know something's being built. - Container check — if this is a dependency (not the top-level call), it checks the PSR-11 container first. Maybe someone already registered this class.
- Reflection — uses
ReflectionClassto inspect the constructor. - No constructor / no params? Just
new $className()and you're done. - Has parameters? Loops through each one and figures out what to pass:
- Variable specification — did someone tell the resolver "when building X, use this value for
$paramName"? Use that. - Class parameter — is the type hint a class? Resolve it recursively. This is where the magic happens — dependencies of dependencies get resolved too.
- Primitive parameter — is it a string, int, etc.? Use the default value if one exists, otherwise throw an error.
- Variable specification — did someone tell the resolver "when building X, use this value for
- Instantiate — creates the object with all resolved dependencies.
- Finalize — fires a
ClassResolvedevent and returns the instance.
Sometimes auto-resolution isn't enough. Specifications let you tell the resolver exactly what to use:
// When building OrderService, use this specific connection string
$resolver->specify(OrderService::class, '$connectionString', 'mysql://...');
// When building OrderService, use RedisCache instead of the default Cache
$resolver->specify(OrderService::class, CacheInterface::class, RedisCache::class);
// Apply the same spec to multiple classes at once
$resolver->specify(
[OrderService::class, UserService::class],
LoggerInterface::class,
FileLogger::class,
);Specifications can be:
- A raw value (for primitives like strings and ints)
- A class name (Codex will resolve it recursively)
- A callable (Codex will call it with the container)
- An array of any of the above
When you know exactly which classes to pass for each constructor parameter by position:
$resolver->resolveWith(OrderService::class, [
Database::class, // param 0
FileLogger::class, // param 1
]);Each argument is resolved through Codex, so you get full recursive resolution. If a parameter isn't provided and has a default value, the default is used.
ClassNameResolver — a static utility that extracts the class name from a parameter's type hint using reflection. Handles ReflectionNamedType, the parent keyword, and returns null for built-in types (int, string, etc.).
PrimitiveResolver — handles non-class parameters. If the parameter has a default value, use it. If it's variadic, return an empty array. Otherwise, throw an error — Codex can't guess what a primitive should be.
Codex integrates with the Echo event system. Two events fire during resolution:
- ClassRequested — fired before resolution starts. Contains the class name being requested.
- ClassResolved — fired after resolution completes. Contains the fully built instance.
Any resolved class that implements Codex\EventDispatcher gets automatically registered as a listener. This means the event system itself can be resolved by Codex — it bootstraps itself.
All errors extend Unresolvable (which implements PSR-11's ContainerExceptionInterface):
- UnknownClass — the class doesn't exist.
- UnresolvableClass — the class exists but can't be instantiated (abstract, interface, or missing required arguments).
- UnresolvablePrimitive — a primitive parameter with no default value and no specification.
- UnresolvableUnionType — a union-typed parameter (
Foo|Bar) that Codex can't automatically resolve.
Resolver (ClassResolver + Specifier)
|-- Uses: ClassNameResolver (extracts class from type hints)
|-- Uses: PrimitiveResolver (handles non-class params)
|-- Manages: specifications (manual overrides)
|-- Dispatches: CodexEvent
| |-- ClassRequested (before resolution)
| \-- ClassResolved (after resolution)
\-- Errors: Unresolvable
|-- UnknownClass
|-- UnresolvableClass
|-- UnresolvablePrimitive
\-- UnresolvableUnionType