Files
server/shared/lib/Utile/Collection/CollectionAbstract.php
2026-02-10 18:46:11 -05:00

137 lines
3.9 KiB
PHP

<?php
declare(strict_types=1);
namespace KTXF\Utile\Collection;
/**
* @template T
* @extends \ArrayObject<int, T>
*/
class CollectionAbstract extends \ArrayObject {
protected const TYPE_STRING = 'string';
protected const TYPE_INT = 'int';
protected const TYPE_FLOAT = 'float';
protected const TYPE_BOOL = 'bool';
protected const TYPE_ARRAY = 'array';
protected const TYPE_DATE = 'date';
protected bool $associative = false;
protected string $typeKey = 'int';
protected string $typeValue = 'string';
/**
* @param array<T> $data
* @param class-string<T>|string|null $typeValue
* @param class-string<TKey>|string|null $typeKey
*/
public function __construct(array $data = [], string $typeValue, string|null $typeKey = null) {
// Ensure that all data entries are of the specified type
$this->typeValue = $typeValue;
if ($typeKey !== null) {
$this->typeKey = $typeKey;
$this->associative = true;
}
foreach ($data as $key => $value) {
if ($this->associative && !$this->validateKey($key)) {
throw new \InvalidArgumentException('Type error: element key ' . $key . ' is not of type ' . $this->typeKey);
}
if (!$this->validateValue($value)) {
throw new \InvalidArgumentException('Type error: element value at index ' . $key . ' is not of type ' . $this->typeValue);
}
}
if (!$this->associative) {
parent::__construct(array_values($data));
} else {
parent::__construct($data);
}
}
private function validateValue($value): bool {
// Check if the value is of the specified type
return match ($this->typeValue) {
self::TYPE_STRING => is_string($value),
self::TYPE_INT, 'integer' => is_int($value),
self::TYPE_FLOAT => is_float($value),
self::TYPE_BOOL, 'boolean' => is_bool($value),
self::TYPE_ARRAY => is_array($value),
self::TYPE_DATE => $value instanceof \DateTimeInterface,
default => $value instanceof $this->typeValue
};
}
protected function validateKey($key): bool {
// Check if the key is of the specified type
return match ($this->typeKey) {
self::TYPE_STRING => is_string($key),
default => is_int($key),
};
}
public function add($value, string|int|null $key = null): void {
$this->offsetSet($key, $value);
}
public function remove(string|int $key): void {
$this->offsetUnset($key);
}
public function extant(string|int $key): bool {
return $this->offsetExists($key);
}
#[\Override]
public function append(mixed $value): void {
if ($this->associative) {
throw new \LogicException('Cannot append to an associative collection. Use add() or offsetSet() instead.');
}
// ensure that the value is of the specified type before appending
if (!$this->validateValue($value)) {
throw new \InvalidArgumentException('Type error: value is not of type ' . $this->typeValue);
}
parent::append($value);
}
#[\Override]
public function offsetSet(mixed $key, mixed $value): void {
if ($this->associative) {
if ($key === null) {
throw new \LogicException('Logic error: Key cannot be null for associative collections');
}
if (!$this->validateKey($key)) {
throw new \InvalidArgumentException('Type error: key is not of type ' . $this->typeKey);
}
} else {
if ($key !== null) {
throw new \LogicException('Logic error: Key must be null for non-associative collections');
}
}
if (!$this->validateValue($value)) {
throw new \InvalidArgumentException('Type error: value is not of type ' . $this->typeValue);
}
parent::offsetSet($key, $value);
}
#[\Override]
public function offsetUnset(mixed $key): void
{
if (!$this->validateKey($key)) {
throw new \InvalidArgumentException('Type error: key is not of type ' . $this->typeKey);
}
parent::offsetUnset($key);
}
#[\Override]
public function offsetExists(mixed $key): bool
{
if (!$this->validateKey($key)) {
throw new \InvalidArgumentException('Type error: key is not of type ' . $this->typeKey);
}
return parent::offsetExists($key);
}
}