*/ 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 $data * @param class-string|string|null $typeValue * @param class-string|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); } }