1: <?php declare(strict_types=1);
2:
3: namespace Salient\Sync\Support;
4:
5: use Salient\Contract\Sync\DeferredRelationshipInterface;
6: use Salient\Contract\Sync\SyncContextInterface;
7: use Salient\Contract\Sync\SyncEntityInterface;
8: use Salient\Contract\Sync\SyncProviderInterface;
9: use Salient\Contract\Sync\SyncStoreInterface;
10: use Salient\Utility\Get;
11: use ArrayIterator;
12: use Closure;
13: use LogicException;
14: use Traversable;
15:
16: /**
17: * The promise of a sync entity relationship that hasn't been retrieved yet
18: *
19: * @template TEntity of SyncEntityInterface
20: *
21: * @implements DeferredRelationshipInterface<TEntity>
22: */
23: final class DeferredRelationship implements DeferredRelationshipInterface
24: {
25: private SyncProviderInterface $Provider;
26: private ?SyncContextInterface $Context;
27: /** @var class-string<TEntity> */
28: private string $Entity;
29: /** @var class-string<SyncEntityInterface> */
30: private string $ForEntity;
31: /** @phpstan-ignore property.onlyWritten */
32: private string $ForEntityProperty;
33: /** @var int|string */
34: private $ForEntityId;
35: /** @var array<string,mixed>|null */
36: private ?array $Filter;
37: /** @var TEntity[]|static|null */
38: private $Replace = null;
39: /** @var (Closure(TEntity[]): void)|null */
40: private ?Closure $Callback = null;
41: /** @var TEntity[]|null */
42: private ?array $Resolved = null;
43:
44: /**
45: * Creates a new DeferredRelationship object
46: *
47: * @param class-string<TEntity> $entity
48: * @param class-string<SyncEntityInterface> $forEntity
49: * @param int|string $forEntityId
50: * @param array<string,mixed>|null $filter
51: * @param TEntity[]|static|null $replace
52: * @param (Closure(TEntity[]): void)|null $callback
53: */
54: private function __construct(
55: SyncProviderInterface $provider,
56: ?SyncContextInterface $context,
57: string $entity,
58: string $forEntity,
59: string $forEntityProperty,
60: $forEntityId,
61: ?array $filter,
62: &$replace,
63: ?Closure $callback = null
64: ) {
65: $this->Provider = $provider;
66: $this->Context = $context;
67: $this->Entity = $entity;
68: $this->ForEntity = $forEntity;
69: $this->ForEntityProperty = $forEntityProperty;
70: $this->ForEntityId = $forEntityId;
71: $this->Filter = $filter;
72:
73: if ($callback) {
74: $this->Callback = $callback;
75: } else {
76: $this->Replace = &$replace;
77: $this->Replace = $this;
78: }
79:
80: $this
81: ->getStore()
82: ->registerEntity($entity)
83: ->registerEntity($forEntity)
84: ->deferRelationship(
85: $this->Provider->getProviderId(),
86: $entity,
87: $forEntity,
88: $forEntityProperty,
89: $forEntityId,
90: $this,
91: );
92: }
93:
94: /**
95: * @inheritDoc
96: */
97: public function getIterator(): Traversable
98: {
99: return new ArrayIterator($this->resolve());
100: }
101:
102: /**
103: * @inheritDoc
104: */
105: public function resolve(): array
106: {
107: if ($this->Resolved !== null) {
108: return $this->Resolved;
109: }
110:
111: $entities = $this
112: ->Provider
113: ->with($this->Entity, $this->Context)
114: ->getListA(
115: $this->Filter ?? [
116: Get::basename($this->ForEntity) => $this->ForEntityId,
117: ],
118: );
119:
120: $this->apply($entities);
121: return $entities;
122: }
123:
124: /**
125: * @inheritDoc
126: */
127: public function replace(array $entities): void
128: {
129: if ($this->Resolved !== null) {
130: // @codeCoverageIgnoreStart
131: throw new LogicException('Relationship already resolved');
132: // @codeCoverageIgnoreEnd
133: }
134:
135: $this->apply($entities);
136: }
137:
138: /**
139: * @param TEntity[] $entities
140: */
141: private function apply(array $entities): void
142: {
143: $this->Resolved = $entities;
144:
145: if ($this->Callback) {
146: ($this->Callback)($entities);
147: return;
148: }
149:
150: $this->Replace = $entities;
151: unset($this->Replace);
152: }
153:
154: /**
155: * @inheritDoc
156: */
157: public static function defer(
158: SyncProviderInterface $provider,
159: ?SyncContextInterface $context,
160: string $entity,
161: string $forEntity,
162: string $forEntityProperty,
163: $forEntityId,
164: ?array $filter = null,
165: &$replace = null,
166: ?Closure $callback = null
167: ): void {
168: new self(
169: $provider,
170: $context,
171: $entity,
172: $forEntity,
173: $forEntityProperty,
174: $forEntityId,
175: $filter,
176: $replace,
177: $callback,
178: );
179: }
180:
181: /**
182: * @inheritDoc
183: */
184: public function getContext(): ?SyncContextInterface
185: {
186: return $this->Context;
187: }
188:
189: private function getStore(): SyncStoreInterface
190: {
191: return $this->Provider->getStore();
192: }
193: }
194: