Initial Version

This commit is contained in:
root
2025-12-21 10:09:54 -05:00
commit 4ae6befc7b
422 changed files with 47225 additions and 0 deletions

View File

@@ -0,0 +1,22 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: Sebastian Krupinski <krupinski01@gmail.com>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace KTXF\Resource\Selector;
/**
* Collection-level selector
*/
class CollectionSelector extends SelectorAbstract {
protected array $keyTypes = ['string', 'integer'];
protected array $valueTypes = ['boolean', EntitySelector::class, 'string'];
protected string $nestedSelector = EntitySelector::class;
protected string $selectorName = 'CollectionSelector';
}

View File

@@ -0,0 +1,46 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: Sebastian Krupinski <krupinski01@gmail.com>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace KTXF\Resource\Selector;
/**
* Entity-level selector (leaf node)
*/
class EntitySelector extends SelectorAbstract {
protected array $keyTypes = ['string', 'integer'];
protected array $valueTypes = ['boolean', 'array', 'string', 'integer'];
protected string $selectorName = 'EntitySelector';
public function append($value): void {
if (!is_string($value) && !is_int($value)) {
throw new \InvalidArgumentException('EntitySelector values must be string or int');
}
parent::append($value);
}
public function offsetSet($key, $value): void {
if ($key !== null && !is_int($key)) {
throw new \InvalidArgumentException('EntitySelector does not support associative keys');
}
if (!is_string($value) && !is_int($value)) {
throw new \InvalidArgumentException('EntitySelector values must be string or int');
}
parent::offsetSet($key, $value);
}
/**
* Get all entity identifiers
* @return array<string|int>
*/
public function identifiers(): array {
return $this->getArrayCopy();
}
}

View File

@@ -0,0 +1,129 @@
<?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());
}
}

View File

@@ -0,0 +1,22 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: Sebastian Krupinski <krupinski01@gmail.com>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace KTXF\Resource\Selector;
/**
* Service-level selector
*/
class ServiceSelector extends SelectorAbstract {
protected array $keyTypes = ['string', 'int'];
protected array $valueTypes = ['boolean', CollectionSelector::class];
protected string $nestedSelector = CollectionSelector::class;
protected string $selectorName = 'CollectionSelector';
}

View File

@@ -0,0 +1,22 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: Sebastian Krupinski <krupinski01@gmail.com>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace KTXF\Resource\Selector;
/**
* Top-level selector for sources
*/
class SourceSelector extends SelectorAbstract {
protected array $keyTypes = ['string'];
protected array $valueTypes = ['boolean', ServiceSelector::class];
protected string $nestedSelector = ServiceSelector::class;
protected string $selectorName = 'ProviderSelector';
}