InstanceStore.php
PHP
Path: src/Util/InstanceStore.php
<?php
namespace mini\Util;
use ArrayAccess;
use Countable;
use IteratorAggregate;
use ArrayIterator;
use InvalidArgumentException;
use mini\Contracts\MapInterface;
/**
* Generic instance store that mirrors WeakMap API but with type validation
*
* Provides a type-safe way to store singleton instances with interface validation.
* Mirrors PHP's WeakMap API for familiar usage patterns.
*
* @template T of object
* @implements MapInterface<string, T>
*/
class InstanceStore implements MapInterface
{
/** @var array<string, T> */
private array $instances = [];
/** @var class-string<T> */
private string $requiredType;
/**
* @param class-string<T> $requiredType Class name or interface name for type validation
*/
public function __construct(string $requiredType)
{
$this->requiredType = $requiredType;
}
/**
* Check if a key exists (mirrors WeakMap::offsetExists)
*/
public function offsetExists(mixed $offset): bool
{
return isset($this->instances[$offset]);
}
/**
* Get an instance by key (mirrors WeakMap::offsetGet)
* @return T|null
*/
public function offsetGet(mixed $offset): mixed
{
return $this->instances[$offset] ?? null;
}
/**
* Set an instance with type validation (mirrors WeakMap::offsetSet)
* @param T|null $value
*/
public function offsetSet(mixed $offset, mixed $value): void
{
if ($value !== null && !($value instanceof $this->requiredType)) {
throw new InvalidArgumentException(
sprintf('Value must be an instance of %s, %s given',
$this->requiredType,
get_debug_type($value)
)
);
}
if ($offset === null) {
$this->instances[] = $value;
} else {
$this->instances[$offset] = $value;
}
}
/**
* Remove an instance (mirrors WeakMap::offsetUnset)
*/
public function offsetUnset(mixed $offset): void
{
unset($this->instances[$offset]);
}
/**
* Get the number of stored instances
*/
public function count(): int
{
return count($this->instances);
}
/**
* Get iterator for stored instances
* @return ArrayIterator<string, T>
*/
public function getIterator(): ArrayIterator
{
return new ArrayIterator($this->instances);
}
/**
* Check if key exists (WeakMap-style method)
*/
public function has(mixed $key): bool
{
return isset($this->instances[$key]);
}
/**
* Get instance by key (WeakMap-style method)
* @param string $key
* @return T|null
*/
public function get(mixed $key): mixed
{
return $this->instances[$key] ?? null;
}
/**
* Get instance by member access
*
* @param string $key
* @return T
*/
public function __get(mixed $key): mixed
{
if (!isset($this->instances[$key])) {
throw new \RuntimeException("Key '$key' does not exist in instance store");
}
return $this->instances[$key];
}
/**
* Set instance by member access
*
* @param string $key
* @param T $value
* @throws InvalidArgumentException
*/
public function __set(mixed $key, mixed $value): void
{
$this->set($key, $value);
}
/**
* Set instance with type validation (WeakMap-style method)
* @param T|null $value
*/
public function set(mixed $key, mixed $value): void
{
$this->offsetSet($key, $value);
}
/**
* Add instance with type validation (throws if key already exists)
* @param T|null $value
* @throws \RuntimeException If the key already exists
*/
public function add(mixed $key, mixed $value): void
{
if ($this->has($key)) {
throw new \RuntimeException("Key '$key' already exists in collection");
}
$this->set($key, $value);
}
/**
* Delete instance (WeakMap-style method)
*/
public function delete(mixed $key): bool
{
if (!isset($this->instances[$key])) {
return false;
}
unset($this->instances[$key]);
return true;
}
/**
* Get all stored keys
*/
public function keys(): array
{
return array_keys($this->instances);
}
/**
* Get all stored values
* @return array<T>
*/
public function values(): array
{
return array_values($this->instances);
}
/**
* Get the required type for this store
* @return class-string<T>
*/
public function getRequiredType(): string
{
return $this->requiredType;
}
}