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: * @template TReplace of TEntity[]|static|null
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 TReplace $replace
52: * @param (Closure(TEntity[]): void)|null $callback
53: * @param-out ($callback is null ? TEntity[]|static : TReplace) $replace
54: */
55: private function __construct(
56: SyncProviderInterface $provider,
57: ?SyncContextInterface $context,
58: string $entity,
59: string $forEntity,
60: string $forEntityProperty,
61: $forEntityId,
62: ?array $filter,
63: &$replace,
64: ?Closure $callback = null
65: ) {
66: $this->Provider = $provider;
67: $this->Context = $context;
68: $this->Entity = $entity;
69: $this->ForEntity = $forEntity;
70: $this->ForEntityProperty = $forEntityProperty;
71: $this->ForEntityId = $forEntityId;
72: $this->Filter = $filter;
73:
74: if ($callback) {
75: $this->Callback = $callback;
76: } else {
77: $this->Replace = &$replace;
78: $this->Replace = $this;
79: }
80:
81: $this
82: ->getStore()
83: ->registerEntityType($entity)
84: ->registerEntityType($forEntity)
85: ->deferRelationship(
86: $this->Provider->getProviderId(),
87: $entity,
88: $forEntity,
89: $forEntityProperty,
90: $forEntityId,
91: $this,
92: );
93: }
94:
95: /**
96: * @inheritDoc
97: */
98: public function getIterator(): Traversable
99: {
100: return new ArrayIterator($this->resolve());
101: }
102:
103: /**
104: * @inheritDoc
105: */
106: public function resolve(): array
107: {
108: if ($this->Resolved !== null) {
109: return $this->Resolved;
110: }
111:
112: $entities = $this
113: ->Provider
114: ->with($this->Entity, $this->Context)
115: ->getListA(
116: $this->Filter ?? [
117: Get::basename($this->ForEntity) => $this->ForEntityId,
118: ],
119: );
120:
121: $this->apply($entities);
122: return $entities;
123: }
124:
125: /**
126: * @inheritDoc
127: */
128: public function replace(array $entities): void
129: {
130: if ($this->Resolved !== null) {
131: // @codeCoverageIgnoreStart
132: throw new LogicException('Relationship already resolved');
133: // @codeCoverageIgnoreEnd
134: }
135:
136: $this->apply($entities);
137: }
138:
139: /**
140: * @param TEntity[] $entities
141: */
142: private function apply(array $entities): void
143: {
144: $this->Resolved = $entities;
145:
146: if ($this->Callback) {
147: ($this->Callback)($entities);
148: return;
149: }
150:
151: $this->Replace = $entities;
152: unset($this->Replace);
153: }
154:
155: /**
156: * @inheritDoc
157: */
158: public static function defer(
159: SyncProviderInterface $provider,
160: ?SyncContextInterface $context,
161: string $entity,
162: string $forEntity,
163: string $forEntityProperty,
164: $forEntityId,
165: ?array $filter = null,
166: &$replace = null,
167: ?Closure $callback = null
168: ): void {
169: new self(
170: $provider,
171: $context,
172: $entity,
173: $forEntity,
174: $forEntityProperty,
175: $forEntityId,
176: $filter,
177: $replace,
178: $callback,
179: );
180: }
181:
182: /**
183: * @inheritDoc
184: */
185: public function getContext(): ?SyncContextInterface
186: {
187: return $this->Context;
188: }
189:
190: private function getStore(): SyncStoreInterface
191: {
192: return $this->Provider->getStore();
193: }
194: }
195: