Merge pull request 'feat: entity move' (#42) from feat/entity-move into main
Some checks failed
Renovate / renovate (push) Failing after 1m16s
Some checks failed
Renovate / renovate (push) Failing after 1m16s
Reviewed-on: #42
This commit was merged in pull request #42.
This commit is contained in:
@@ -79,25 +79,23 @@ interface ServiceEntityMutableInterface extends ServiceBaseInterface {
|
||||
*
|
||||
* @since 2025.05.01
|
||||
*
|
||||
* @param string|int $sourceCollection Source collection identifier
|
||||
* @param string|int $targetCollection Target collection identifier
|
||||
* @param string|int ...$identifiers Entity identifiers to copy
|
||||
* @param string|int $target Target collection identifier
|
||||
* @param array<string|int,array<string|int>> $sources Source entities to move (collection identifier => [entity identifier])
|
||||
*
|
||||
* @return array<string|int,string|int> Map of source identifier => new identifier
|
||||
* @return array<string|int,bool> List of moved entity identifiers
|
||||
*/
|
||||
public function entityCopy(string|int $sourceCollection, string|int $targetCollection, string|int ...$identifiers): array;
|
||||
public function entityCopy(string|int $target, array $sources): array;
|
||||
|
||||
/**
|
||||
* Moves entities to another collection
|
||||
*
|
||||
* @since 2025.05.01
|
||||
*
|
||||
* @param string|int $sourceCollection Source collection identifier
|
||||
* @param string|int $targetCollection Target collection identifier
|
||||
* @param string|int ...$identifiers Entity identifiers to move
|
||||
* @param string|int $target Target collection identifier
|
||||
* @param array<string|int,array<string|int>> $sources Source entities to move (collection identifier => [entity identifier])
|
||||
*
|
||||
* @return array<string|int,bool> List of moved entity identifiers
|
||||
*/
|
||||
public function entityMove(string|int $sourceCollection, string|int $targetCollection, string|int ...$identifiers): array;
|
||||
public function entityMove(string|int $target, array $sources): array;
|
||||
|
||||
}
|
||||
|
||||
39
shared/lib/Resource/Identifier/CollectionIdentifier.php
Normal file
39
shared/lib/Resource/Identifier/CollectionIdentifier.php
Normal file
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: Sebastian Krupinski <krupinski01@gmail.com>
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace KTXF\Resource\Identifier;
|
||||
|
||||
/**
|
||||
* Collection-level resource identifier (depth 3)
|
||||
*
|
||||
* Format: {provider}:{service}:{collection}
|
||||
*/
|
||||
class CollectionIdentifier extends ServiceIdentifier implements CollectionIdentifierInterface {
|
||||
|
||||
public function __construct(
|
||||
string $provider,
|
||||
string $service,
|
||||
private readonly string $_collection,
|
||||
) {
|
||||
parent::__construct($provider, $service);
|
||||
}
|
||||
|
||||
public function collection(): string {
|
||||
return $this->_collection;
|
||||
}
|
||||
|
||||
public function depth(): int {
|
||||
return 3;
|
||||
}
|
||||
|
||||
public function __toString(): string {
|
||||
return parent::__toString() . self::SEPARATOR . $this->_collection;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: Sebastian Krupinski <krupinski01@gmail.com>
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace KTXF\Resource\Identifier;
|
||||
|
||||
/**
|
||||
* Collection-level identifier (provider + service + collection)
|
||||
*/
|
||||
interface CollectionIdentifierInterface extends ServiceIdentifierInterface {
|
||||
|
||||
/** The collection segment (e.g. "inbox") */
|
||||
public function collection(): string;
|
||||
|
||||
}
|
||||
40
shared/lib/Resource/Identifier/EntityIdentifier.php
Normal file
40
shared/lib/Resource/Identifier/EntityIdentifier.php
Normal file
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: Sebastian Krupinski <krupinski01@gmail.com>
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace KTXF\Resource\Identifier;
|
||||
|
||||
/**
|
||||
* Entity-level resource identifier (depth 4)
|
||||
*
|
||||
* Format: {provider}:{service}:{collection}:{entity}
|
||||
*/
|
||||
class EntityIdentifier extends CollectionIdentifier implements EntityIdentifierInterface {
|
||||
|
||||
public function __construct(
|
||||
string $provider,
|
||||
string $service,
|
||||
string $collection,
|
||||
private readonly string $_entity,
|
||||
) {
|
||||
parent::__construct($provider, $service, $collection);
|
||||
}
|
||||
|
||||
public function entity(): string {
|
||||
return $this->_entity;
|
||||
}
|
||||
|
||||
public function depth(): int {
|
||||
return 4;
|
||||
}
|
||||
|
||||
public function __toString(): string {
|
||||
return parent::__toString() . self::SEPARATOR . $this->_entity;
|
||||
}
|
||||
|
||||
}
|
||||
20
shared/lib/Resource/Identifier/EntityIdentifierInterface.php
Normal file
20
shared/lib/Resource/Identifier/EntityIdentifierInterface.php
Normal file
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: Sebastian Krupinski <krupinski01@gmail.com>
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace KTXF\Resource\Identifier;
|
||||
|
||||
/**
|
||||
* Entity-level identifier (provider + service + collection + entity)
|
||||
*/
|
||||
interface EntityIdentifierInterface extends CollectionIdentifierInterface {
|
||||
|
||||
/** The entity segment (e.g. "1001") */
|
||||
public function entity(): string;
|
||||
|
||||
}
|
||||
57
shared/lib/Resource/Identifier/ResourceIdentifier.php
Normal file
57
shared/lib/Resource/Identifier/ResourceIdentifier.php
Normal file
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: Sebastian Krupinski <krupinski01@gmail.com>
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace KTXF\Resource\Identifier;
|
||||
|
||||
/**
|
||||
* Provider-level resource identifier (depth 1)
|
||||
*
|
||||
* Format: {provider}
|
||||
*/
|
||||
class ResourceIdentifier implements ResourceIdentifierInterface {
|
||||
|
||||
public const SEPARATOR = ':';
|
||||
|
||||
public function __construct(
|
||||
private readonly string $provider,
|
||||
) {
|
||||
}
|
||||
|
||||
public function provider(): string {
|
||||
return $this->provider;
|
||||
}
|
||||
|
||||
public function depth(): int {
|
||||
return 1;
|
||||
}
|
||||
|
||||
public function __toString(): string {
|
||||
return $this->provider;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a colon-separated identifier string and return the appropriate level class
|
||||
*
|
||||
* @return ResourceIdentifier|ServiceIdentifier|CollectionIdentifier|EntityIdentifier
|
||||
*/
|
||||
public static function fromString(string $identifier): ResourceIdentifierInterface {
|
||||
$parts = explode(self::SEPARATOR, $identifier, 4);
|
||||
if (count($parts) < 1 || $parts[0] === '') {
|
||||
throw new \InvalidArgumentException("Invalid resource identifier: {$identifier}");
|
||||
}
|
||||
|
||||
return match (count($parts)) {
|
||||
4 => new EntityIdentifier($parts[0], $parts[1], $parts[2], $parts[3]),
|
||||
3 => new CollectionIdentifier($parts[0], $parts[1], $parts[2]),
|
||||
2 => new ServiceIdentifier($parts[0], $parts[1]),
|
||||
default => new self($parts[0]),
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: Sebastian Krupinski <krupinski01@gmail.com>
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace KTXF\Resource\Identifier;
|
||||
|
||||
/**
|
||||
* Top-level identifier for resources (provider level)
|
||||
*/
|
||||
interface ResourceIdentifierInterface extends \Stringable {
|
||||
|
||||
/** The provider segment (e.g. "imap") */
|
||||
public function provider(): string;
|
||||
|
||||
/** Number of segments present (1–4) */
|
||||
public function depth(): int;
|
||||
|
||||
/** Canonical string form: provider[:service[:collection[:entity]]] */
|
||||
public function __toString(): string;
|
||||
|
||||
}
|
||||
154
shared/lib/Resource/Identifier/ResourceIdentifiers.php
Normal file
154
shared/lib/Resource/Identifier/ResourceIdentifiers.php
Normal file
@@ -0,0 +1,154 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: Sebastian Krupinski <krupinski01@gmail.com>
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace KTXF\Resource\Identifier;
|
||||
|
||||
/**
|
||||
* Typed collection of resource identifiers
|
||||
*
|
||||
* Accepts an array of identifier strings (e.g. ["imap:account1:inbox:1001", "imap:account1:sent"])
|
||||
* and provides filtering/search helpers.
|
||||
*/
|
||||
class ResourceIdentifiers implements ResourceIdentifiersInterface {
|
||||
|
||||
/** @var ResourceIdentifierInterface[] */
|
||||
private array $identifiers = [];
|
||||
|
||||
/** Create a collection from an array of identifier strings */
|
||||
public static function fromArray(array $strings): static {
|
||||
$collection = new static();
|
||||
foreach ($strings as $string) {
|
||||
if (!is_string($string)) {
|
||||
throw new \InvalidArgumentException('Each identifier must be a string');
|
||||
}
|
||||
$collection->add(ResourceIdentifier::fromString($string));
|
||||
}
|
||||
return $collection;
|
||||
}
|
||||
|
||||
public function add(ResourceIdentifierInterface $identifier): void {
|
||||
$this->identifiers[] = $identifier;
|
||||
}
|
||||
|
||||
/** @return ResourceIdentifierInterface[] */
|
||||
public function all(): array {
|
||||
return $this->identifiers;
|
||||
}
|
||||
|
||||
public function count(): int {
|
||||
return count($this->identifiers);
|
||||
}
|
||||
|
||||
public function getIterator(): \ArrayIterator {
|
||||
return new \ArrayIterator($this->identifiers);
|
||||
}
|
||||
|
||||
/** Filter identifiers by depth level (1–4) */
|
||||
public function byDepth(int $depth): static {
|
||||
$filtered = new static();
|
||||
foreach ($this->identifiers as $id) {
|
||||
if ($id->depth() === $depth) {
|
||||
$filtered->add($id);
|
||||
}
|
||||
}
|
||||
return $filtered;
|
||||
}
|
||||
|
||||
/** Filter identifiers by provider */
|
||||
public function byProvider(string $provider): static {
|
||||
$filtered = new static();
|
||||
foreach ($this->identifiers as $id) {
|
||||
if ($id->provider() === $provider) {
|
||||
$filtered->add($id);
|
||||
}
|
||||
}
|
||||
return $filtered;
|
||||
}
|
||||
|
||||
/** Filter identifiers by service (only identifiers with depth >= 2) */
|
||||
public function byService(string $service): static {
|
||||
$filtered = new static();
|
||||
foreach ($this->identifiers as $id) {
|
||||
if ($id instanceof ServiceIdentifierInterface && $id->service() === $service) {
|
||||
$filtered->add($id);
|
||||
}
|
||||
}
|
||||
return $filtered;
|
||||
}
|
||||
|
||||
/** Filter identifiers by collection (only identifiers with depth >= 3) */
|
||||
public function byCollection(string $collection): static {
|
||||
$filtered = new static();
|
||||
foreach ($this->identifiers as $id) {
|
||||
if ($id instanceof CollectionIdentifierInterface && $id->collection() === $collection) {
|
||||
$filtered->add($id);
|
||||
}
|
||||
}
|
||||
return $filtered;
|
||||
}
|
||||
|
||||
/** Filter identifiers by entity (only identifiers with depth == 4) */
|
||||
public function byEntity(string $entity): static {
|
||||
$filtered = new static();
|
||||
foreach ($this->identifiers as $id) {
|
||||
if ($id instanceof EntityIdentifierInterface && $id->entity() === $entity) {
|
||||
$filtered->add($id);
|
||||
}
|
||||
}
|
||||
return $filtered;
|
||||
}
|
||||
|
||||
/** Get unique provider names */
|
||||
public function providers(): array {
|
||||
$values = [];
|
||||
foreach ($this->identifiers as $id) {
|
||||
$values[$id->provider()] = true;
|
||||
}
|
||||
return array_keys($values);
|
||||
}
|
||||
|
||||
/** Get unique service names */
|
||||
public function services(): array {
|
||||
$values = [];
|
||||
foreach ($this->identifiers as $id) {
|
||||
if ($id instanceof ServiceIdentifierInterface) {
|
||||
$values[$id->service()] = true;
|
||||
}
|
||||
}
|
||||
return array_keys($values);
|
||||
}
|
||||
|
||||
/** Get unique collection names */
|
||||
public function collections(): array {
|
||||
$values = [];
|
||||
foreach ($this->identifiers as $id) {
|
||||
if ($id instanceof CollectionIdentifierInterface) {
|
||||
$values[$id->collection()] = true;
|
||||
}
|
||||
}
|
||||
return array_keys($values);
|
||||
}
|
||||
|
||||
/** Get unique entity names */
|
||||
public function entities(): array {
|
||||
$values = [];
|
||||
foreach ($this->identifiers as $id) {
|
||||
if ($id instanceof EntityIdentifierInterface) {
|
||||
$values[$id->entity()] = true;
|
||||
}
|
||||
}
|
||||
return array_keys($values);
|
||||
}
|
||||
|
||||
/** Check if the collection is empty */
|
||||
public function isEmpty(): bool {
|
||||
return count($this->identifiers) === 0;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: Sebastian Krupinski <krupinski01@gmail.com>
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace KTXF\Resource\Identifier;
|
||||
|
||||
/**
|
||||
* A typed collection of resource identifiers with search and filter capabilities
|
||||
*/
|
||||
interface ResourceIdentifiersInterface extends \Countable, \IteratorAggregate {
|
||||
|
||||
/** Add an identifier to the collection */
|
||||
public function add(ResourceIdentifierInterface $identifier): void;
|
||||
|
||||
/** Get all identifiers */
|
||||
public function all(): array;
|
||||
|
||||
/** Filter identifiers by depth level (1–4) */
|
||||
public function byDepth(int $depth): static;
|
||||
|
||||
/** Filter identifiers by provider */
|
||||
public function byProvider(string $provider): static;
|
||||
|
||||
/** Filter identifiers by service (requires depth >= 2) */
|
||||
public function byService(string $service): static;
|
||||
|
||||
/** Filter identifiers by collection (requires depth >= 3) */
|
||||
public function byCollection(string $collection): static;
|
||||
|
||||
/** Filter identifiers by entity (requires depth == 4) */
|
||||
public function byEntity(string $entity): static;
|
||||
|
||||
/** Get unique provider names */
|
||||
public function providers(): array;
|
||||
|
||||
/** Get unique service names */
|
||||
public function services(): array;
|
||||
|
||||
/** Get unique collection names */
|
||||
public function collections(): array;
|
||||
|
||||
/** Get unique entity names */
|
||||
public function entities(): array;
|
||||
|
||||
}
|
||||
38
shared/lib/Resource/Identifier/ServiceIdentifier.php
Normal file
38
shared/lib/Resource/Identifier/ServiceIdentifier.php
Normal file
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: Sebastian Krupinski <krupinski01@gmail.com>
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace KTXF\Resource\Identifier;
|
||||
|
||||
/**
|
||||
* Service-level resource identifier (depth 2)
|
||||
*
|
||||
* Format: {provider}:{service}
|
||||
*/
|
||||
class ServiceIdentifier extends ResourceIdentifier implements ServiceIdentifierInterface {
|
||||
|
||||
public function __construct(
|
||||
string $provider,
|
||||
private readonly string $_service,
|
||||
) {
|
||||
parent::__construct($provider);
|
||||
}
|
||||
|
||||
public function service(): string {
|
||||
return $this->_service;
|
||||
}
|
||||
|
||||
public function depth(): int {
|
||||
return 2;
|
||||
}
|
||||
|
||||
public function __toString(): string {
|
||||
return parent::__toString() . self::SEPARATOR . $this->_service;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: Sebastian Krupinski <krupinski01@gmail.com>
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace KTXF\Resource\Identifier;
|
||||
|
||||
/**
|
||||
* Service-level identifier (provider + service)
|
||||
*/
|
||||
interface ServiceIdentifierInterface extends ResourceIdentifierInterface {
|
||||
|
||||
/** The service segment (e.g. "account1") */
|
||||
public function service(): string;
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user