Currently getClassDefinitions allows you to register classes in one of two ways:
as a single concrete => abstract binding, or as a concrete => [array, of, abstracts] set of bindings
/** @inheritDoc */publicfunctiongetClassDefinitions(): array
{
return [
WordPressPluginConfigProvider::class => [
HasTextDomain::class,
HasRestNamespace::class,
HasLocalDatabasePrefix::class,
HasCacheKeyPrefix::class,
HasDefaultTtl::class,
PlatformContextProvider::class,
CanResolvePaths::class,
CanResolveUrls::class
],
JwtStrategy::class => JwtStrategyInterface::class
];
}But it would be nice to be able to conditionally set which concrete is used for different interfaces. This could reduce our reliance on registries, and provider classes by allowing us to embed logic in a standardized way.
A plausible way to do this is with some kind of resolver:
interface ClassResolverInterface
{
publicfunctionresolve(RequestContext$context): ?string;
}class RequestContext
{
protectedstring$requestedClass;
protectedarray$constructorDependencies;
protectedarray$implementedInterfaces;
publicfunction__construct(string$requestedClass, array$constructorDependencies, array$implementedInterfaces)
{
$this->requestedClass = $requestedClass;
$this->constructorDependencies = $constructorDependencies;
$this->implementedInterfaces = $implementedInterfaces;
}
publicfunctiongetRequestedClass(): string
{
return$this->requestedClass;
}
publicfunctiongetConstructorDependencies(): array
{
return$this->constructorDependencies;
}
publicfunctiongetImplementedInterfaces(): array
{
return$this->implementedInterfaces;
}
}and then use that in getClassDefinitions
publicfunctiongetClassDefinitions(): array
{
return [
WordPressPluginConfigProvider::class => [
HasTextDomain::class,
HasRestNamespace::class,
HasLocalDatabasePrefix::class,
HasCacheKeyPrefix::class,
HasDefaultTtl::class,
PlatformContextProvider::class,
CanResolvePaths::class,
CanResolveUrls::class,
],
JwtStrategy::class => JwtStrategyInterface::class
BasicCache::class => [
'bindings' => [CacheStrategyInterface::class],
'resolver' => CacheStrategyResolver::class, // Resolver for conditional binding
],
];
}The cache in this example could conditionally use redis, but fallback to the basic cache if none is provided:
class CacheStrategyResolver implements ClassResolverInterface
{
publicfunctionresolve(RequestContext$context): ?string
{
// Check if RedisCache is better suited based on context informationif ($this->requiresRedisCache($context)) {
return RedisCache::class;
}
// Fall back to the default if conditions aren't metreturnnull;
}
protectedfunctionrequiresRedisCache(RequestContext$context): bool
{
// Criterion 1: If the requested class specifically implements a certain interfaceif (in_array(DistributedCacheInterface::class, $context->getImplementedInterfaces(), true)) {
returntrue;
}
// Criterion 2: If specific dependencies are needed, e.g., Redis client$dependencies = $context->getConstructorDependencies();
if (in_array(RedisClient::class, $dependencies, true)) {
returntrue;
}
// No criteria met, default to BasicCachereturnfalse;
}
}class RequestContext
{
protectedstring$requestedClass;
protectedarray$constructorDependencies;
protectedarray$implementedInterfaces;
publicfunction__construct(string$requestedClass, array$constructorDependencies, array$implementedInterfaces)
{
$this->requestedClass = $requestedClass;
$this->constructorDependencies = $constructorDependencies;
$this->implementedInterfaces = $implementedInterfaces;
}
publicfunctiongetRequestedClass(): string
{
return$this->requestedClass;
}
publicfunctiongetConstructorDependencies(): array
{
return$this->constructorDependencies;
}
publicfunctiongetImplementedInterfaces(): array
{
return$this->implementedInterfaces;
}
}I think this would also allow us to embed a registry pattern directly into the container logic, which could drastically simply our registries. Check out this example of a path resolver solution that allows us to register multiple paths automatically based on the namespace:
First we define a namespace registry that allows us to register multiple paths across the platform.
namespacePHPNomad\Template;
usePHPNomad\Template\Interfaces\CanResolvePaths;
class NamespaceRegistry
{
protectedarray$registry = [];
publicfunctionregister(string$namespace, CanResolvePaths$resolver): void
{
$this->registry[$namespace] = $resolver;
}
publicfunctiongetResolverForNamespace(string$namespace): ?CanResolvePaths
{
foreach ($this->registryas$registeredNamespace => $resolver) {
if (strpos($namespace, $registeredNamespace) === 0) {
return$resolver;
}
}
returnnull;
}
}Then we set up the resolver:
namespacePHPNomad\Template\Resolvers;
usePHPNomad\Template\NamespaceRegistry;
usePHPNomad\Template\Interfaces\CanResolvePaths;
usePHPNomad\Core\RequestContext;
usePHPNomad\Core\ClassResolverInterface;
class PathStrategyResolver implements ClassResolverInterface
{
protectedNamespaceRegistry$registry;
publicfunction__construct(NamespaceRegistry$registry)
{
$this->registry = $registry;
}
publicfunctionresolve(RequestContext$context): ?string
{
// Get the requested class namespace$namespace = $this->getNamespace($context->getRequestedClass());
// Retrieve a resolver based on the namespace$resolver = $this->registry->getResolverForNamespace($namespace);
// Return the class name of the resolver if found; otherwise, null for default. Could optionally throw a DI exception here, too.return$resolver ? get_class($resolver) : null;
}
protectedfunctiongetNamespace(string$class): string
{
returnsubstr($class, 0, strrpos($class, '\\'));
}
}publicfunctiongetClassDefinitions(): array
{
return [
DefaultPathResolver::class => [
'bindings' => CanResolvePaths::class,
'resolver' => PathStrategyResolver::class, // Uses PathStrategyResolver to resolve based on namespace
],
];
}
Currently getClassDefinitions allows you to register classes in one of two ways:
as a single concrete => abstract binding, or as a concrete => [array, of, abstracts] set of bindings
But it would be nice to be able to conditionally set which concrete is used for different interfaces. This could reduce our reliance on registries, and provider classes by allowing us to embed logic in a standardized way.
A plausible way to do this is with some kind of resolver:
and then use that in
getClassDefinitionsThe cache in this example could conditionally use redis, but fallback to the basic cache if none is provided:
I think this would also allow us to embed a registry pattern directly into the container logic, which could drastically simply our registries. Check out this example of a path resolver solution that allows us to register multiple paths automatically based on the namespace:
First we define a namespace registry that allows us to register multiple paths across the platform.
Then we set up the resolver: