1: <?php declare(strict_types=1);
2:
3: namespace Salient\Sync\Support;
4:
5: use Salient\Contract\Sync\DeferredEntityInterface;
6: use Salient\Contract\Sync\LinkType;
7: use Salient\Contract\Sync\SyncContextInterface;
8: use Salient\Contract\Sync\SyncEntityInterface;
9: use Salient\Contract\Sync\SyncProviderInterface;
10: use Salient\Contract\Sync\SyncStoreInterface;
11: use Closure;
12: use LogicException;
13:
14: /**
15: * The promise of a sync entity that hasn't been retrieved yet
16: *
17: * @template TEntity of SyncEntityInterface
18: *
19: * @mixin TEntity
20: *
21: * @implements DeferredEntityInterface<TEntity>
22: */
23: final class DeferredEntity implements DeferredEntityInterface
24: {
25: private SyncProviderInterface $Provider;
26: private ?SyncContextInterface $Context;
27: /** @var class-string<TEntity> */
28: private string $Entity;
29: /** @var int|string */
30: private $EntityId;
31: /** @var TEntity|static|null */
32: private $Replace = null;
33: /** @var (Closure(TEntity): void)|null */
34: private ?Closure $Callback = null;
35: /** @var TEntity|null */
36: private ?SyncEntityInterface $Resolved = null;
37:
38: /**
39: * Creates a new DeferredEntity object
40: *
41: * @param class-string<TEntity> $entity
42: * @param int|string $entityId
43: * @param TEntity|static|null $replace
44: * @param (Closure(TEntity): void)|null $callback
45: */
46: private function __construct(
47: SyncProviderInterface $provider,
48: ?SyncContextInterface $context,
49: string $entity,
50: $entityId,
51: &$replace,
52: ?Closure $callback = null
53: ) {
54: $this->Provider = $provider;
55: $this->Context = $context;
56: $this->Entity = $entity;
57: $this->EntityId = $entityId;
58:
59: if ($callback) {
60: $this->Callback = $callback;
61: } else {
62: $this->Replace = &$replace;
63: $this->Replace = $this;
64: }
65:
66: $this
67: ->getStore()
68: ->registerEntityType($entity)
69: ->deferEntity(
70: $this->Provider->getProviderId(),
71: $entity,
72: $entityId,
73: $this,
74: );
75: }
76:
77: /**
78: * @inheritDoc
79: */
80: public function toLink(int $type = LinkType::DEFAULT, bool $compact = true): array
81: {
82: switch ($type) {
83: case LinkType::DEFAULT:
84: case LinkType::FRIENDLY:
85: return [
86: '@type' => $this->getTypeUri($compact),
87: '@id' => $this->EntityId,
88: ];
89:
90: case LinkType::COMPACT:
91: return [
92: '@id' => $this->getUri($compact),
93: ];
94:
95: default:
96: throw new LogicException("Invalid link type: $type");
97: }
98: }
99:
100: /**
101: * @inheritDoc
102: */
103: public function getUri(bool $compact = true): string
104: {
105: return sprintf('%s/%s', $this->getTypeUri($compact), $this->EntityId);
106: }
107:
108: private function getTypeUri(bool $compact): string
109: {
110: return $this->getStore()->getEntityTypeUri($this->Entity, $compact);
111: }
112:
113: /**
114: * @inheritDoc
115: */
116: public function resolve(): SyncEntityInterface
117: {
118: if ($this->Resolved !== null) {
119: return $this->Resolved;
120: }
121:
122: return $this
123: ->Provider
124: ->with($this->Entity, $this->Context)
125: ->get($this->EntityId);
126: }
127:
128: /**
129: * @inheritDoc
130: */
131: public function replace(SyncEntityInterface $entity): void
132: {
133: if ($this->Resolved !== null) {
134: // @codeCoverageIgnoreStart
135: throw new LogicException('Entity already resolved');
136: // @codeCoverageIgnoreEnd
137: }
138:
139: $this->Resolved = $entity;
140:
141: if ($this->Callback) {
142: ($this->Callback)($entity);
143: return;
144: }
145:
146: $this->Replace = $entity;
147: unset($this->Replace);
148: }
149:
150: /**
151: * @inheritDoc
152: */
153: public static function defer(
154: SyncProviderInterface $provider,
155: ?SyncContextInterface $context,
156: string $entity,
157: $entityId,
158: &$replace = null,
159: ?Closure $callback = null
160: ): void {
161: new self(
162: $provider,
163: $context,
164: $entity,
165: $entityId,
166: $replace,
167: $callback,
168: );
169: }
170:
171: /**
172: * @inheritDoc
173: */
174: public static function deferList(
175: SyncProviderInterface $provider,
176: ?SyncContextInterface $context,
177: string $entity,
178: array $entityIds,
179: &$replace = null,
180: ?Closure $callback = null
181: ): void {
182: if ($callback) {
183: foreach ($entityIds as $entityId) {
184: /** @disregard P1008 */
185: new self(
186: $provider,
187: $context,
188: $entity,
189: $entityId,
190: $null,
191: $callback,
192: );
193: }
194: return;
195: }
196:
197: $list = [];
198: $i = 0;
199: foreach ($entityIds as $entityId) {
200: new self(
201: $provider,
202: $context,
203: $entity,
204: $entityId,
205: $list[$i++],
206: );
207: }
208: $replace = $list;
209: }
210:
211: /**
212: * @inheritDoc
213: */
214: public function getContext(): ?SyncContextInterface
215: {
216: return $this->Context;
217: }
218:
219: private function getStore(): SyncStoreInterface
220: {
221: return $this->Provider->getStore();
222: }
223:
224: /**
225: * @param mixed $value
226: */
227: public function __set(string $name, $value): void
228: {
229: $entity = $this->resolve();
230: $entity->{$name} = $value;
231: }
232:
233: /**
234: * @return mixed
235: */
236: public function __get(string $name)
237: {
238: $entity = $this->resolve();
239: return $entity->{$name};
240: }
241:
242: public function __isset(string $name): bool
243: {
244: $entity = $this->resolve();
245: return isset($entity->{$name});
246: }
247:
248: public function __unset(string $name): void
249: {
250: $entity = $this->resolve();
251: unset($entity->{$name});
252: }
253: }
254: