Files
server/shared/lib/Resource/Selector/SelectorAbstract.php
2026-02-10 18:46:11 -05:00

130 lines
3.4 KiB
PHP

<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: Sebastian Krupinski <krupinski01@gmail.com>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace KTXF\Resource\Selector;
use JsonSerializable;
use KTXF\Json\JsonDeserializable;
/**
* Abstract base class for all selector types
* Provides common functionality for hierarchical selectors
*/
abstract class SelectorAbstract extends \ArrayObject implements JsonSerializable, JsonDeserializable {
protected const TYPE_STRING = 'string';
protected const TYPE_INT = 'int';
protected const TYPE_BOOL = 'bool';
/** @var array<mixed> Allowed key types: 'string', 'int' */
protected array $keyTypes = [];
/** @var array<mixed> Allowed scalar value types: 'string', 'int', 'bool' */
protected array $valueTypes = [];
/** @var class-string selector class for nested structures */
protected string $nestedSelector = SelectorAbstract::class;
/** @var string Human-readable name for this selector type */
protected string $selectorName = 'Selector';
/**
* Serialize to JSON-compatible array
*
* @return array
*/
public function jsonSerialize(): array {
$result = [];
foreach ($this as $key => $value) {
if ($value instanceof JsonSerializable) {
$result[$key] = $value->jsonSerialize();
} else {
$result[$key] = $value;
}
}
return $result;
}
/**
* Deserialize from JSON-compatible array
* @param array $data
* @return void
* @throws \InvalidArgumentException
*/
public function jsonDeserialize(array|string $data): static {
if (is_string($data)) {
$data = json_decode($data, true);
}
foreach ($data as $key => $value) {
if ($this->nestedSelector !== null && is_array($value)) {
$selector = new $this->nestedSelector();
$selector->jsonDeserialize($value);
$this->offsetSet($key, $selector);
} else {
$this->offsetSet($key, $value);
}
}
return $this;
}
/**
* Validate if a key is of the correct type for this selector
*
* @param mixed $key
* @return bool
*/
protected function validateKey(mixed $key): bool {
return in_array(gettype($key), $this->keyTypes, true);
}
/**
* Validate if a value is of the correct type for this selector
*
* @param mixed $value
* @return bool
*/
protected function validateValue(mixed $value): bool {
if ($this->nestedSelector !== null && $value instanceof $this->nestedSelector) {
return true;
}
return in_array(gettype($value), $this->valueTypes, true);
}
/**
* Override offsetSet to enforce type checking
*
* @param mixed $key
* @param mixed $value
* @return void
* @throws \InvalidArgumentException
*/
#[\Override]
public function offsetSet($key, $value): void {
if (!$this->validateKey($key)) {
throw new \InvalidArgumentException("{$this->selectorName} keys must be one of [" . implode(', ', $this->keyTypes) . "], got " . gettype($key));
}
if (!$this->validateValue($value)) {
throw new \InvalidArgumentException("{$this->selectorName} values must be one of [" . implode(', ', $this->valueTypes) . "], got " . gettype($value));
}
parent::offsetSet($key, $value);
}
/**
* Get all identifiers (keys or values depending on selector type)
*
* @return array
*/
public function identifiers(): array {
return array_keys($this->getArrayCopy());
}
}