dbfb5f8b37
Signed-off-by: Sebastian Krupinski <krupinski01@gmail.com>
630 lines
22 KiB
PHP
630 lines
22 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
/**
|
|
* SPDX-FileCopyrightText: Sebastian Krupinski <krupinski01@gmail.com>
|
|
* SPDX-License-Identifier: AGPL-3.0-or-later
|
|
*/
|
|
|
|
namespace KTXM\ProviderJmapc\Providers\Document;
|
|
|
|
use Generator;
|
|
use KTXF\Documents\Collection\CollectionBaseInterface;
|
|
use KTXF\Documents\Collection\CollectionMutableInterface;
|
|
use KTXF\Documents\Collection\CollectionPropertiesBaseInterface;
|
|
use KTXF\Documents\Service\ServiceBaseInterface;
|
|
use KTXF\Documents\Service\ServiceCollectionMutableInterface;
|
|
use KTXF\Documents\Service\ServiceConfigurableInterface;
|
|
use KTXF\Documents\Service\ServiceMutableInterface;
|
|
use KTXF\Documents\Service\ServiceNodeListInterface;
|
|
use KTXF\Resource\Delta\Delta;
|
|
use KTXF\Resource\Exceptions\InvalidParameterException;
|
|
use KTXF\Resource\Filter\Filter;
|
|
use KTXF\Resource\Filter\IFilter;
|
|
use KTXF\Resource\Identifier\CollectionIdentifierInterface;
|
|
use KTXF\Resource\Identifier\EntityIdentifierInterface;
|
|
use KTXF\Resource\Provider\ResourceServiceIdentityInterface;
|
|
use KTXF\Resource\Provider\ResourceServiceLocationInterface;
|
|
use KTXF\Resource\Range\IRange;
|
|
use KTXF\Resource\Range\Range;
|
|
use KTXF\Resource\Range\RangeAnchorType;
|
|
use KTXF\Resource\Range\RangeTally;
|
|
use KTXF\Resource\Range\RangeType;
|
|
use KTXF\Resource\Sort\ISort;
|
|
use KTXF\Resource\Sort\Sort;
|
|
use KTXM\ProviderJmapc\Providers\ServiceIdentityBasic;
|
|
use KTXM\ProviderJmapc\Providers\ServiceLocation;
|
|
use KTXM\ProviderJmapc\Service\Remote\RemoteFilesService;
|
|
use KTXM\ProviderJmapc\Service\Remote\RemoteService;
|
|
|
|
class Service implements ServiceBaseInterface, ServiceMutableInterface, ServiceConfigurableInterface, ServiceCollectionMutableInterface, ServiceNodeListInterface
|
|
{
|
|
public const JSON_TYPE = ServiceBaseInterface::JSON_TYPE;
|
|
|
|
private const PROVIDER_IDENTIFIER = 'jmap';
|
|
|
|
private ?string $serviceTenantId = null;
|
|
private ?string $serviceUserId = null;
|
|
private ?string $serviceIdentifier = null;
|
|
private ?string $serviceLabel = null;
|
|
private bool $serviceEnabled = false;
|
|
private bool $serviceDebug = false;
|
|
private ?ServiceLocation $location = null;
|
|
private ?ServiceIdentityBasic $identity = null;
|
|
private array $auxiliary = [];
|
|
|
|
private array $serviceAbilities = [
|
|
self::CAPABILITY_COLLECTION_LIST => true,
|
|
self::CAPABILITY_COLLECTION_LIST_FILTER => [
|
|
self::CAPABILITY_COLLECTION_FILTER_LABEL => 's:100:256:256',
|
|
],
|
|
self::CAPABILITY_COLLECTION_LIST_SORT => [
|
|
self::CAPABILITY_COLLECTION_SORT_LABEL,
|
|
],
|
|
self::CAPABILITY_COLLECTION_EXTANT => true,
|
|
self::CAPABILITY_COLLECTION_FETCH => true,
|
|
self::CAPABILITY_COLLECTION_CREATE => true,
|
|
self::CAPABILITY_COLLECTION_UPDATE => true,
|
|
self::CAPABILITY_COLLECTION_DELETE => true,
|
|
self::CAPABILITY_ENTITY_LIST => true,
|
|
self::CAPABILITY_ENTITY_LIST_FILTER => [
|
|
self::CAPABILITY_ENTITY_FILTER_ALL => 's:200:256:256',
|
|
],
|
|
self::CAPABILITY_ENTITY_LIST_SORT => [
|
|
self::CAPABILITY_ENTITY_SORT_LABEL
|
|
],
|
|
self::CAPABILITY_ENTITY_LIST_RANGE => [
|
|
'tally' => ['absolute', 'relative']
|
|
],
|
|
self::CAPABILITY_ENTITY_DELTA => true,
|
|
self::CAPABILITY_ENTITY_EXTANT => true,
|
|
self::CAPABILITY_ENTITY_FETCH => true,
|
|
self::CAPABILITY_NODE_LIST => true,
|
|
self::CAPABILITY_NODE_LIST_FILTER => [
|
|
self::CAPABILITY_NODE_FILTER_LABEL => 's:100:256:256',
|
|
],
|
|
self::CAPABILITY_NODE_LIST_SORT => [
|
|
self::CAPABILITY_NODE_SORT_LABEL,
|
|
self::CAPABILITY_NODE_SORT_SIZE,
|
|
self::CAPABILITY_NODE_SORT_CREATED_ON,
|
|
self::CAPABILITY_NODE_SORT_MODIFIED_ON,
|
|
],
|
|
self::CAPABILITY_NODE_LIST_RANGE => [
|
|
self::CAPABILITY_NODE_RANGE_TALLY => [
|
|
self::CAPABILITY_NODE_RANGE_TALLY_ABSOLUTE,
|
|
self::CAPABILITY_NODE_RANGE_TALLY_RELATIVE,
|
|
],
|
|
],
|
|
];
|
|
|
|
private readonly RemoteFilesService $remoteService;
|
|
|
|
public function __construct(
|
|
) {}
|
|
|
|
private function initialize(): void {
|
|
if (!isset($this->remoteService)) {
|
|
$client = RemoteService::freshClient($this);
|
|
$this->remoteService = RemoteService::documentsService($client);
|
|
}
|
|
}
|
|
|
|
public function toStore(): array {
|
|
return array_filter([
|
|
'tid' => $this->serviceTenantId,
|
|
'uid' => $this->serviceUserId,
|
|
'sid' => $this->serviceIdentifier,
|
|
'label' => $this->serviceLabel,
|
|
'enabled' => $this->serviceEnabled,
|
|
'debug' => $this->serviceDebug,
|
|
'location' => $this->location?->toStore(),
|
|
'identity' => $this->identity?->toStore(),
|
|
'auxiliary' => $this->auxiliary,
|
|
], fn($v) => $v !== null);
|
|
}
|
|
|
|
public function fromStore(array $data): static {
|
|
$this->serviceTenantId = $data['tid'] ?? null;
|
|
$this->serviceUserId = $data['uid'] ?? null;
|
|
$this->serviceIdentifier = $data['sid'];
|
|
$this->serviceLabel = $data['label'] ?? '';
|
|
$this->serviceEnabled = $data['enabled'] ?? false;
|
|
$this->serviceDebug = $data['debug'] ?? false;
|
|
|
|
if (isset($data['location'])) {
|
|
$this->location = (new ServiceLocation())->fromStore($data['location']);
|
|
}
|
|
|
|
if (isset($data['identity'])) {
|
|
$this->identity = (new ServiceIdentityBasic())->fromStore($data['identity']);
|
|
}
|
|
if (isset($data['auxiliary']) && is_array($data['auxiliary'])) {
|
|
$this->auxiliary = $data['auxiliary'];
|
|
}
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function jsonSerialize(): array {
|
|
return array_filter([
|
|
self::PROPERTY_TYPE => self::JSON_TYPE,
|
|
self::PROPERTY_PROVIDER => self::PROVIDER_IDENTIFIER,
|
|
self::PROPERTY_IDENTIFIER => $this->serviceIdentifier,
|
|
self::PROPERTY_LABEL => $this->serviceLabel,
|
|
self::PROPERTY_ENABLED => $this->serviceEnabled,
|
|
self::PROPERTY_CAPABILITIES => $this->serviceAbilities,
|
|
self::PROPERTY_LOCATION => $this->location?->jsonSerialize(),
|
|
self::PROPERTY_IDENTITY => $this->identity?->jsonSerialize(),
|
|
self::PROPERTY_AUXILIARY => $this->auxiliary,
|
|
], fn($v) => $v !== null);
|
|
}
|
|
|
|
public function jsonDeserialize(array|string $data): static {
|
|
if (is_string($data)) {
|
|
$data = json_decode($data, true, 512, JSON_THROW_ON_ERROR);
|
|
}
|
|
|
|
if (isset($data[self::PROPERTY_LABEL])) {
|
|
$this->setLabel($data[self::PROPERTY_LABEL]);
|
|
}
|
|
if (isset($data[self::PROPERTY_ENABLED])) {
|
|
$this->setEnabled($data[self::PROPERTY_ENABLED]);
|
|
}
|
|
if (isset($data[self::PROPERTY_LOCATION])) {
|
|
$this->setLocation($this->freshLocation(null, $data[self::PROPERTY_LOCATION]));
|
|
}
|
|
if (isset($data[self::PROPERTY_IDENTITY])) {
|
|
$this->setIdentity($this->freshIdentity(null, $data[self::PROPERTY_IDENTITY]));
|
|
}
|
|
if (isset($data[self::PROPERTY_AUXILIARY]) && is_array($data[self::PROPERTY_AUXILIARY])) {
|
|
$this->setAuxiliary($data[self::PROPERTY_AUXILIARY]);
|
|
}
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function capable(string $value): bool
|
|
{
|
|
return isset($this->serviceAbilities[$value]);
|
|
}
|
|
|
|
public function capabilities(): array {
|
|
return $this->serviceAbilities;
|
|
}
|
|
|
|
public function provider(): string
|
|
{
|
|
return self::PROVIDER_IDENTIFIER;
|
|
}
|
|
|
|
public function identifier(): string|int
|
|
{
|
|
return $this->serviceIdentifier;
|
|
}
|
|
|
|
public function getLabel(): string|null
|
|
{
|
|
return $this->serviceLabel;
|
|
}
|
|
|
|
public function setLabel(string $label): static
|
|
{
|
|
$this->serviceLabel = $label;
|
|
return $this;
|
|
}
|
|
|
|
public function getEnabled(): bool
|
|
{
|
|
return $this->serviceEnabled;
|
|
}
|
|
|
|
public function setEnabled(bool $enabled): static
|
|
{
|
|
$this->serviceEnabled = $enabled;
|
|
return $this;
|
|
}
|
|
|
|
public function getLocation(): ServiceLocation
|
|
{
|
|
return $this->location;
|
|
}
|
|
|
|
public function setLocation(ResourceServiceLocationInterface $location): static
|
|
{
|
|
$this->location = $location;
|
|
return $this;
|
|
}
|
|
|
|
public function freshLocation(string|null $type = null, array $data = []): ServiceLocation
|
|
{
|
|
$location = new ServiceLocation();
|
|
$location->jsonDeserialize($data);
|
|
return $location;
|
|
}
|
|
|
|
public function getIdentity(): ServiceIdentityBasic
|
|
{
|
|
return $this->identity;
|
|
}
|
|
|
|
public function setIdentity(ResourceServiceIdentityInterface $identity): static
|
|
{
|
|
$this->identity = $identity;
|
|
return $this;
|
|
}
|
|
|
|
public function freshIdentity(string|null $type, array $data = []): ServiceIdentityBasic
|
|
{
|
|
$identity = new ServiceIdentityBasic();
|
|
$identity->jsonDeserialize($data);
|
|
return $identity;
|
|
}
|
|
|
|
public function getDebug(): bool
|
|
{
|
|
return $this->serviceDebug;
|
|
}
|
|
|
|
public function setDebug(bool $debug): static
|
|
{
|
|
$this->serviceDebug = $debug;
|
|
return $this;
|
|
}
|
|
|
|
public function getAuxiliary(): array
|
|
{
|
|
return $this->auxiliary;
|
|
}
|
|
|
|
public function setAuxiliary(array $auxiliary): static
|
|
{
|
|
$this->auxiliary = $auxiliary;
|
|
return $this;
|
|
}
|
|
|
|
// Collection operations
|
|
|
|
public function collectionList(string|int|null $location, ?IFilter $filter = null, ?ISort $sort = null): array
|
|
{
|
|
$this->initialize();
|
|
|
|
$collections = $this->remoteService->collectionList($location, $filter, $sort);
|
|
|
|
foreach ($collections as &$collection) {
|
|
if (is_array($collection) && isset($collection['id'])) {
|
|
$object = new CollectionResource(provider: $this->provider(), service: $this->identifier());
|
|
$object->fromJmap($collection);
|
|
$collection = $object;
|
|
}
|
|
}
|
|
|
|
return $collections;
|
|
}
|
|
|
|
public function collectionListFilter(): Filter
|
|
{
|
|
return new Filter($this->serviceAbilities[self::CAPABILITY_COLLECTION_LIST_FILTER] ?? []);
|
|
}
|
|
|
|
public function collectionListSort(): Sort
|
|
{
|
|
return new Sort($this->serviceAbilities[self::CAPABILITY_COLLECTION_LIST_SORT] ?? []);
|
|
}
|
|
|
|
public function collectionExtant(string|int|null $location, string|int ...$identifiers): array
|
|
{
|
|
$this->initialize();
|
|
|
|
return $this->remoteService->collectionExtant($location, ...$identifiers);
|
|
}
|
|
|
|
public function collectionFetch(string|int|null $identifier): ?CollectionBaseInterface
|
|
{
|
|
$this->initialize();
|
|
|
|
$collection = $this->remoteService->collectionFetch($identifier);
|
|
|
|
if (is_array($collection) && isset($collection['id'])) {
|
|
$object = new CollectionResource(provider: $this->provider(), service: $this->identifier());
|
|
$object->fromJmap($collection);
|
|
$collection = $object;
|
|
}
|
|
|
|
return $collection;
|
|
}
|
|
|
|
public function collectionFresh(): CollectionMutableInterface
|
|
{
|
|
return new CollectionResource(provider: $this->provider(), service: $this->identifier());
|
|
}
|
|
|
|
/**
|
|
* Convert incoming collection properties into a native CollectionResource
|
|
*/
|
|
private function collectionNative(CollectionPropertiesBaseInterface $properties, string|int|null $identifier = null): CollectionResource
|
|
{
|
|
if ($properties instanceof CollectionResource) {
|
|
return clone $properties;
|
|
}
|
|
$object = new CollectionResource(provider: $this->provider(), service: $this->identifier());
|
|
$object->jsonDeserialize(array_filter([
|
|
CollectionResource::PROPERTY_IDENTIFIER => $identifier,
|
|
CollectionResource::PROPERTY_PROPERTIES => $properties->jsonSerialize(),
|
|
], fn($v) => $v !== null));
|
|
return $object;
|
|
}
|
|
|
|
public function collectionCreate(CollectionIdentifierInterface|null $target, CollectionPropertiesBaseInterface $properties, array $options = []): CollectionBaseInterface
|
|
{
|
|
$this->initialize();
|
|
|
|
$collection = $this->collectionNative($properties);
|
|
|
|
$collection = $collection->toJmap();
|
|
$collection = $this->remoteService->collectionCreate($target?->collection(), $collection, $options);
|
|
|
|
$object = new CollectionResource(provider: $this->provider(), service: $this->identifier());
|
|
$object->fromJmap($collection);
|
|
|
|
return $object;
|
|
}
|
|
|
|
public function collectionUpdate(CollectionIdentifierInterface $target, CollectionPropertiesBaseInterface $properties): CollectionBaseInterface
|
|
{
|
|
$this->initialize();
|
|
|
|
$identifier = $target->collection();
|
|
$collection = $this->collectionNative($properties, $identifier);
|
|
|
|
$collection = $collection->toJmap();
|
|
$collection = $this->remoteService->collectionModify($identifier, $collection);
|
|
|
|
$object = new CollectionResource(provider: $this->provider(), service: $this->identifier());
|
|
$object->fromJmap($collection);
|
|
|
|
return $object;
|
|
}
|
|
|
|
public function collectionDelete(CollectionIdentifierInterface $target, bool $force = false): CollectionBaseInterface | true
|
|
{
|
|
$this->initialize();
|
|
|
|
if ($this->remoteService->collectionDestroy($target->collection(), $force, $force) === null) {
|
|
throw new \RuntimeException("Failed to delete collection: {$target->collection()}");
|
|
}
|
|
return true;
|
|
}
|
|
|
|
public function collectionMove(CollectionIdentifierInterface|null $target, CollectionIdentifierInterface $source): CollectionBaseInterface
|
|
{
|
|
// TODO: Implement collection move
|
|
$this->initialize();
|
|
$collection = new CollectionResource(provider: $this->provider(), service: $this->identifier());
|
|
return $collection;
|
|
}
|
|
|
|
public function collectionCopy(CollectionIdentifierInterface|null $target, CollectionIdentifierInterface $source): CollectionBaseInterface
|
|
{
|
|
// TODO: Implement collection copy
|
|
$this->initialize();
|
|
$collection = new CollectionResource(provider: $this->provider(), service: $this->identifier());
|
|
return $collection;
|
|
}
|
|
|
|
// Entity operations
|
|
|
|
public function entityListBulk(string|int|null $collection, ?IFilter $filter = null, ?ISort $sort = null, ?IRange $range = null, ?array $properties = null): array
|
|
{
|
|
$this->initialize();
|
|
|
|
$result = $this->remoteService->entityList($collection, $filter, $sort, $range, $properties);
|
|
|
|
$list = [];
|
|
foreach ($result['list'] as $index => $entry) {
|
|
if (is_array($entry) && isset($entry['id'])) {
|
|
$object = new EntityResource(provider: $this->provider(), service: $this->identifier());
|
|
$object->fromJmap($entry);
|
|
$list[$object->identifier()] = $object;
|
|
}
|
|
unset($result['list'][$index]);
|
|
}
|
|
|
|
return $list;
|
|
}
|
|
|
|
public function entityListStream(string|int|null $collection, ?IFilter $filter = null, ?ISort $sort = null, ?IRange $range = null, ?array $properties = null): Generator
|
|
{
|
|
$this->initialize();
|
|
|
|
$result = $this->remoteService->entityList($collection, $filter, $sort, $range, $properties);
|
|
|
|
foreach ($result['list'] as $index => $entry) {
|
|
if (is_array($entry) && isset($entry['id'])) {
|
|
$object = new EntityResource(provider: $this->provider(), service: $this->identifier());
|
|
$object->fromJmap($entry);
|
|
yield $object;
|
|
}
|
|
unset($result['list'][$index]);
|
|
}
|
|
}
|
|
|
|
public function entityListFilter(): Filter
|
|
{
|
|
return new Filter($this->serviceAbilities[self::CAPABILITY_ENTITY_LIST_FILTER] ?? []);
|
|
}
|
|
|
|
public function entityListSort(): Sort
|
|
{
|
|
return new Sort($this->serviceAbilities[self::CAPABILITY_ENTITY_LIST_SORT] ?? []);
|
|
}
|
|
|
|
public function entityListRange(RangeType $type): IRange
|
|
{
|
|
return new Range();
|
|
}
|
|
|
|
public function entityDelta(string|int|null $collection, string $signature): Delta
|
|
{
|
|
$this->initialize();
|
|
|
|
return $this->remoteService->entityDelta($collection, $signature, 'ids');
|
|
}
|
|
|
|
public function entityExtant(string|int|null $collection, string|int ...$identifiers): array
|
|
{
|
|
$this->initialize();
|
|
|
|
return $this->remoteService->entityExtant(...$identifiers);
|
|
}
|
|
|
|
public function entityFetchBulk(EntityIdentifierInterface ...$identifiers): array
|
|
{
|
|
$this->initialize();
|
|
|
|
$entityIds = array_map(fn($identifier) => $identifier->entity(), $identifiers);
|
|
$entities = $this->remoteService->entityFetch(...$entityIds);
|
|
|
|
$list = [];
|
|
foreach ($entities as $key => $entity) {
|
|
if (is_array($entity) && isset($entity['id'])) {
|
|
$object = new EntityResource(provider: $this->provider(), service: $this->identifier());
|
|
$object->fromJmap($entity);
|
|
$list[$object->identifier()] = $object;
|
|
} else {
|
|
$list[$key] = $entity;
|
|
}
|
|
}
|
|
|
|
return $list;
|
|
}
|
|
|
|
public function entityFetchStream(EntityIdentifierInterface ...$identifiers): Generator
|
|
{
|
|
yield from $this->entityFetchBulk(...$identifiers);
|
|
}
|
|
|
|
public function entityRead(EntityIdentifierInterface $target): ?string
|
|
{
|
|
return null;
|
|
}
|
|
|
|
public function entityReadStream(EntityIdentifierInterface $target)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
public function entityReadChunk(EntityIdentifierInterface $target, int $offset, int $length): ?string
|
|
{
|
|
return null;
|
|
}
|
|
|
|
// Node operations (unified collections + entities)
|
|
|
|
public function nodeList(string|int|null $location, ?IFilter $filter = null, ?ISort $sort = null, ?IRange $range = null): Generator
|
|
{
|
|
$this->initialize();
|
|
|
|
$result = $this->remoteService->nodeList($location !== null ? (string)$location : null);
|
|
|
|
// convert and filter, keeping folders and files apart so folders always come first
|
|
$label = null;
|
|
if ($filter !== null && isset($filter->conditions()[self::CAPABILITY_NODE_FILTER_LABEL])) {
|
|
$label = (string)$filter->conditions()[self::CAPABILITY_NODE_FILTER_LABEL]['value'];
|
|
}
|
|
$collections = [];
|
|
$entities = [];
|
|
foreach ($result['list'] as $index => $entry) {
|
|
unset($result['list'][$index]);
|
|
if (!is_array($entry) || !isset($entry['id'])) {
|
|
continue;
|
|
}
|
|
// folders carry no blob
|
|
$object = empty($entry['blobId'])
|
|
? new CollectionResource(provider: $this->provider(), service: $this->identifier())
|
|
: new EntityResource(provider: $this->provider(), service: $this->identifier());
|
|
$object->fromJmap($entry);
|
|
if ($label !== null && $label !== '' && mb_stripos($object->getProperties()->getLabel(), $label) === false) {
|
|
continue;
|
|
}
|
|
if ($object instanceof CollectionResource) {
|
|
$collections[] = $object;
|
|
} else {
|
|
$entities[] = $object;
|
|
}
|
|
}
|
|
|
|
// sort each group, defaulting to label ascending
|
|
$conditions = $sort?->conditions() ?? [];
|
|
if ($conditions === []) {
|
|
$conditions = [['attribute' => self::CAPABILITY_NODE_SORT_LABEL, 'direction' => true]];
|
|
}
|
|
$comparator = function (CollectionResource|EntityResource $a, CollectionResource|EntityResource $b) use ($conditions): int {
|
|
foreach ($conditions as $condition) {
|
|
$order = match ($condition['attribute']) {
|
|
self::CAPABILITY_NODE_SORT_LABEL => strnatcasecmp($a->getProperties()->getLabel(), $b->getProperties()->getLabel()),
|
|
self::CAPABILITY_NODE_SORT_SIZE => $this->nodeSize($a) <=> $this->nodeSize($b),
|
|
self::CAPABILITY_NODE_SORT_CREATED_ON => ($a->created()?->getTimestamp() ?? 0) <=> ($b->created()?->getTimestamp() ?? 0),
|
|
self::CAPABILITY_NODE_SORT_MODIFIED_ON => ($a->modified()?->getTimestamp() ?? 0) <=> ($b->modified()?->getTimestamp() ?? 0),
|
|
default => 0,
|
|
};
|
|
if ($order !== 0) {
|
|
return $condition['direction'] ? $order : -$order;
|
|
}
|
|
}
|
|
return strnatcasecmp($a->getProperties()->getLabel(), $b->getProperties()->getLabel());
|
|
};
|
|
usort($collections, $comparator);
|
|
usort($entities, $comparator);
|
|
$nodes = array_merge($collections, $entities);
|
|
unset($collections, $entities);
|
|
|
|
// apply range
|
|
if ($range instanceof RangeTally) {
|
|
$offset = 0;
|
|
if ($range->getAnchor() === RangeAnchorType::RELATIVE) {
|
|
// relative ranges start after the anchor node
|
|
foreach ($nodes as $index => $node) {
|
|
if ((string)$node->identifier() === (string)$range->getPosition()) {
|
|
$offset = $index + 1;
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
$offset = max(0, (int)$range->getPosition());
|
|
}
|
|
$nodes = array_slice($nodes, $offset, $range->getTally());
|
|
}
|
|
|
|
foreach ($nodes as $index => $node) {
|
|
unset($nodes[$index]);
|
|
yield $node->identifier() => $node;
|
|
}
|
|
}
|
|
|
|
public function nodeListFilter(): IFilter
|
|
{
|
|
return new Filter($this->serviceAbilities[self::CAPABILITY_NODE_LIST_FILTER] ?? []);
|
|
}
|
|
|
|
public function nodeListSort(): ISort
|
|
{
|
|
// sort attributes are matched by key
|
|
return new Sort(array_fill_keys($this->serviceAbilities[self::CAPABILITY_NODE_LIST_SORT] ?? [], true));
|
|
}
|
|
|
|
public function nodeListRange(RangeType $type): IRange
|
|
{
|
|
if ($type !== RangeType::TALLY) {
|
|
throw new InvalidParameterException("Invalid: Node range of type '{$type->value}' is not supported");
|
|
}
|
|
return new RangeTally();
|
|
}
|
|
|
|
private function nodeSize(CollectionResource|EntityResource $node): int
|
|
{
|
|
return $node instanceof EntityResource ? $node->getProperties()->size() : 0;
|
|
}
|
|
|
|
}
|