Skip to content

Consider expanding on getClassDefinitions to support dynamic instance setting #2

Description

@alexstandiford

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
],
];
}

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions