1: | <?php declare(strict_types=1); |
2: | |
3: | namespace Salient\Sync\Support; |
4: | |
5: | use Salient\Contract\Container\ContainerInterface; |
6: | use Salient\Contract\Iterator\FluentIteratorInterface; |
7: | use Salient\Contract\Sync\DeferralPolicy; |
8: | use Salient\Contract\Sync\HydrationPolicy; |
9: | use Salient\Contract\Sync\SyncContextInterface; |
10: | use Salient\Contract\Sync\SyncDefinitionInterface; |
11: | use Salient\Contract\Sync\SyncEntityInterface; |
12: | use Salient\Contract\Sync\SyncEntityProviderInterface; |
13: | use Salient\Contract\Sync\SyncEntityResolverInterface; |
14: | use Salient\Contract\Sync\SyncOperation; |
15: | use Salient\Contract\Sync\SyncProviderInterface; |
16: | use Salient\Contract\Sync\SyncStoreInterface; |
17: | use Salient\Iterator\IterableIterator; |
18: | use Salient\Sync\Exception\SyncOperationNotImplementedException; |
19: | use Salient\Sync\SyncUtil; |
20: | use Generator; |
21: | use LogicException; |
22: | |
23: | |
24: | |
25: | |
26: | |
27: | |
28: | |
29: | |
30: | |
31: | |
32: | |
33: | |
34: | |
35: | |
36: | |
37: | |
38: | |
39: | |
40: | |
41: | |
42: | |
43: | |
44: | |
45: | |
46: | final class SyncEntityProvider implements SyncEntityProviderInterface |
47: | { |
48: | |
49: | private string $Entity; |
50: | |
51: | private SyncProviderInterface $Provider; |
52: | |
53: | private SyncDefinitionInterface $Definition; |
54: | private SyncContextInterface $Context; |
55: | private SyncStoreInterface $Store; |
56: | |
57: | |
58: | |
59: | |
60: | |
61: | public function __construct( |
62: | ContainerInterface $container, |
63: | SyncProviderInterface $provider, |
64: | string $entity, |
65: | ?SyncContextInterface $context = null |
66: | ) { |
67: | if (!is_a($entity, SyncEntityInterface::class, true)) { |
68: | throw new LogicException(sprintf( |
69: | '%s does not implement %s', |
70: | $entity, |
71: | SyncEntityInterface::class, |
72: | )); |
73: | } |
74: | |
75: | if ($context && $context->getProvider() !== $provider) { |
76: | throw new LogicException(sprintf( |
77: | 'Context has a different provider (%s, expected %s)', |
78: | $context->getProvider()->getName(), |
79: | $provider->getName(), |
80: | )); |
81: | } |
82: | |
83: | $entity = SyncUtil::getServicedEntityType($entity, $provider, $container); |
84: | |
85: | $this->Entity = $entity; |
86: | $this->Provider = $provider; |
87: | $this->Definition = $provider->getDefinition($entity); |
88: | $this->Context = ($context ?? $provider->getContext())->withContainer($container); |
89: | $this->Store = $provider->getStore(); |
90: | } |
91: | |
92: | |
93: | |
94: | |
95: | public function getProvider(): SyncProviderInterface |
96: | { |
97: | return $this->Provider; |
98: | } |
99: | |
100: | |
101: | |
102: | |
103: | public function entity(): string |
104: | { |
105: | return $this->Entity; |
106: | } |
107: | |
108: | |
109: | |
110: | |
111: | |
112: | |
113: | |
114: | |
115: | |
116: | |
117: | |
118: | private function _run(int $operation, ...$args) |
119: | { |
120: | $closure = |
121: | $this |
122: | ->Definition |
123: | ->getOperationClosure($operation); |
124: | |
125: | if (!$closure) { |
126: | throw new SyncOperationNotImplementedException( |
127: | $this->Provider, |
128: | $this->Entity, |
129: | $operation |
130: | ); |
131: | } |
132: | |
133: | return $closure( |
134: | $this->Context->withOperation($operation, $this->Entity, ...$args), |
135: | ...$args |
136: | ); |
137: | } |
138: | |
139: | |
140: | |
141: | |
142: | public function run(int $operation, ...$args) |
143: | { |
144: | $fromCheckpoint = $this->Store->getDeferralCheckpoint(); |
145: | $deferralPolicy = $this->Context->getDeferralPolicy(); |
146: | |
147: | if (!SyncUtil::isListOperation($operation)) { |
148: | $result = $this->_run($operation, ...$args); |
149: | |
150: | if ($deferralPolicy === DeferralPolicy::RESOLVE_LATE) { |
151: | $this->Store->resolveDeferrals($fromCheckpoint); |
152: | } |
153: | |
154: | return $result; |
155: | } |
156: | |
157: | switch ($deferralPolicy) { |
158: | case DeferralPolicy::DO_NOT_RESOLVE: |
159: | case DeferralPolicy::RESOLVE_EARLY: |
160: | $result = $this->_run($operation, ...$args); |
161: | break; |
162: | |
163: | case DeferralPolicy::RESOLVE_LATE: |
164: | $result = $this->resolveDeferredEntitiesAfterRun( |
165: | $fromCheckpoint, |
166: | $operation, |
167: | ...$args, |
168: | ); |
169: | break; |
170: | |
171: | default: |
172: | throw new LogicException(sprintf( |
173: | 'Invalid deferral policy: %d', |
174: | $deferralPolicy, |
175: | )); |
176: | } |
177: | |
178: | if (!$result instanceof FluentIteratorInterface) { |
179: | return new IterableIterator($result); |
180: | } |
181: | |
182: | return $result; |
183: | } |
184: | |
185: | |
186: | |
187: | |
188: | |
189: | |
190: | private function resolveDeferredEntitiesAfterRun(int $fromCheckpoint, int $operation, ...$args): Generator |
191: | { |
192: | yield from $this->_run($operation, ...$args); |
193: | $this->Store->resolveDeferrals($fromCheckpoint); |
194: | } |
195: | |
196: | |
197: | |
198: | |
199: | |
200: | |
201: | |
202: | |
203: | |
204: | |
205: | |
206: | |
207: | |
208: | |
209: | |
210: | |
211: | |
212: | |
213: | |
214: | |
215: | |
216: | |
217: | |
218: | public function create($entity, ...$args): SyncEntityInterface |
219: | { |
220: | return $this->run(SyncOperation::CREATE, $entity, ...$args); |
221: | } |
222: | |
223: | |
224: | |
225: | |
226: | |
227: | |
228: | |
229: | |
230: | |
231: | |
232: | |
233: | |
234: | |
235: | |
236: | |
237: | |
238: | |
239: | |
240: | |
241: | |
242: | |
243: | |
244: | |
245: | |
246: | |
247: | public function get($id, ...$args): SyncEntityInterface |
248: | { |
249: | $offline = $this->Context->getOffline(); |
250: | if ($offline !== false) { |
251: | if ($id === null) { |
252: | throw new LogicException('$id cannot be null when working offline'); |
253: | } |
254: | $entity = $this->Store->registerEntityType($this->Entity)->getEntity( |
255: | $this->Provider->getProviderId(), |
256: | $this->Entity, |
257: | $id, |
258: | $offline, |
259: | ); |
260: | if ($entity) { |
261: | return $entity; |
262: | } |
263: | } |
264: | |
265: | return $this->run(SyncOperation::READ, $id, ...$args); |
266: | } |
267: | |
268: | |
269: | |
270: | |
271: | |
272: | |
273: | |
274: | |
275: | |
276: | |
277: | |
278: | |
279: | |
280: | |
281: | |
282: | |
283: | |
284: | |
285: | |
286: | |
287: | |
288: | |
289: | |
290: | public function update($entity, ...$args): SyncEntityInterface |
291: | { |
292: | return $this->run(SyncOperation::UPDATE, $entity, ...$args); |
293: | } |
294: | |
295: | |
296: | |
297: | |
298: | |
299: | |
300: | |
301: | |
302: | |
303: | |
304: | |
305: | |
306: | |
307: | |
308: | |
309: | |
310: | |
311: | |
312: | |
313: | |
314: | |
315: | |
316: | |
317: | |
318: | |
319: | |
320: | public function delete($entity, ...$args): SyncEntityInterface |
321: | { |
322: | return $this->run(SyncOperation::DELETE, $entity, ...$args); |
323: | } |
324: | |
325: | |
326: | |
327: | |
328: | |
329: | |
330: | |
331: | |
332: | |
333: | |
334: | |
335: | |
336: | |
337: | |
338: | |
339: | |
340: | |
341: | |
342: | |
343: | |
344: | |
345: | |
346: | |
347: | |
348: | |
349: | public function createList(iterable $entities, ...$args): FluentIteratorInterface |
350: | { |
351: | return $this->run(SyncOperation::CREATE_LIST, $entities, ...$args); |
352: | } |
353: | |
354: | |
355: | |
356: | |
357: | |
358: | |
359: | |
360: | |
361: | |
362: | |
363: | |
364: | |
365: | |
366: | |
367: | |
368: | |
369: | |
370: | |
371: | |
372: | public function getList(...$args): FluentIteratorInterface |
373: | { |
374: | return $this->run(SyncOperation::READ_LIST, ...$args); |
375: | } |
376: | |
377: | |
378: | |
379: | |
380: | |
381: | |
382: | |
383: | |
384: | |
385: | |
386: | |
387: | |
388: | |
389: | |
390: | |
391: | |
392: | |
393: | |
394: | |
395: | |
396: | |
397: | |
398: | |
399: | |
400: | |
401: | public function updateList(iterable $entities, ...$args): FluentIteratorInterface |
402: | { |
403: | return $this->run(SyncOperation::UPDATE_LIST, $entities, ...$args); |
404: | } |
405: | |
406: | |
407: | |
408: | |
409: | |
410: | |
411: | |
412: | |
413: | |
414: | |
415: | |
416: | |
417: | |
418: | |
419: | |
420: | |
421: | |
422: | |
423: | |
424: | |
425: | |
426: | |
427: | |
428: | |
429: | |
430: | |
431: | |
432: | |
433: | public function deleteList(iterable $entities, ...$args): FluentIteratorInterface |
434: | { |
435: | return $this->run(SyncOperation::DELETE_LIST, $entities, ...$args); |
436: | } |
437: | |
438: | public function runA(int $operation, ...$args): array |
439: | { |
440: | |
441: | if (!SyncUtil::isListOperation($operation)) { |
442: | throw new LogicException('Not a *_LIST operation: ' . $operation); |
443: | } |
444: | |
445: | $fromCheckpoint = $this->Store->getDeferralCheckpoint(); |
446: | $deferralPolicy = $this->Context->getDeferralPolicy(); |
447: | |
448: | $result = $this->_run($operation, ...$args); |
449: | if (!is_array($result)) { |
450: | $result = iterator_to_array($result, false); |
451: | } |
452: | |
453: | if ($deferralPolicy === DeferralPolicy::RESOLVE_LATE) { |
454: | $this->Store->resolveDeferrals($fromCheckpoint); |
455: | } |
456: | |
457: | return $result; |
458: | } |
459: | |
460: | public function createListA(iterable $entities, ...$args): array |
461: | { |
462: | return $this->runA(SyncOperation::CREATE_LIST, $entities, ...$args); |
463: | } |
464: | |
465: | public function getListA(...$args): array |
466: | { |
467: | return $this->runA(SyncOperation::READ_LIST, ...$args); |
468: | } |
469: | |
470: | public function updateListA(iterable $entities, ...$args): array |
471: | { |
472: | return $this->runA(SyncOperation::UPDATE_LIST, $entities, ...$args); |
473: | } |
474: | |
475: | public function deleteListA(iterable $entities, ...$args): array |
476: | { |
477: | return $this->runA(SyncOperation::DELETE_LIST, $entities, ...$args); |
478: | } |
479: | |
480: | |
481: | |
482: | |
483: | public function online() |
484: | { |
485: | $this->Context = $this->Context->withOffline(false); |
486: | return $this; |
487: | } |
488: | |
489: | |
490: | |
491: | |
492: | public function offline() |
493: | { |
494: | $this->Context = $this->Context->withOffline(true); |
495: | return $this; |
496: | } |
497: | |
498: | |
499: | |
500: | |
501: | public function offlineFirst() |
502: | { |
503: | $this->Context = $this->Context->withOffline(null); |
504: | return $this; |
505: | } |
506: | |
507: | |
508: | |
509: | |
510: | public function doNotResolve() |
511: | { |
512: | $this->Context = $this->Context->withDeferralPolicy( |
513: | DeferralPolicy::DO_NOT_RESOLVE, |
514: | ); |
515: | return $this; |
516: | } |
517: | |
518: | |
519: | |
520: | |
521: | public function resolveEarly() |
522: | { |
523: | $this->Context = $this->Context->withDeferralPolicy( |
524: | DeferralPolicy::RESOLVE_EARLY, |
525: | ); |
526: | return $this; |
527: | } |
528: | |
529: | |
530: | |
531: | |
532: | public function resolveLate() |
533: | { |
534: | $this->Context = $this->Context->withDeferralPolicy( |
535: | DeferralPolicy::RESOLVE_LATE, |
536: | ); |
537: | return $this; |
538: | } |
539: | |
540: | |
541: | |
542: | |
543: | public function doNotHydrate() |
544: | { |
545: | $this->Context = $this->Context->withHydrationPolicy( |
546: | HydrationPolicy::SUPPRESS, |
547: | ); |
548: | |
549: | return $this; |
550: | } |
551: | |
552: | |
553: | |
554: | |
555: | public function hydrate( |
556: | int $policy = HydrationPolicy::EAGER, |
557: | ?string $entity = null, |
558: | $depth = null |
559: | ) { |
560: | $this->Context = $this->Context->withHydrationPolicy( |
561: | $policy, |
562: | $entity, |
563: | $depth, |
564: | ); |
565: | return $this; |
566: | } |
567: | |
568: | |
569: | |
570: | |
571: | public function getResolver( |
572: | $nameProperty = null, |
573: | int $flags = SyncEntityProvider::ALGORITHM_SAME, |
574: | $uncertaintyThreshold = null, |
575: | $weightProperty = null, |
576: | bool $requireOneMatch = false |
577: | ): SyncEntityResolverInterface { |
578: | return is_string($nameProperty) |
579: | && $flags === self::ALGORITHM_SAME |
580: | && $weightProperty === null |
581: | && !$requireOneMatch |
582: | ? new SyncEntityResolver($this, $nameProperty) |
583: | : new SyncEntityFuzzyResolver( |
584: | $this, |
585: | $nameProperty, |
586: | $flags, |
587: | $uncertaintyThreshold, |
588: | $weightProperty, |
589: | $requireOneMatch, |
590: | ); |
591: | } |
592: | } |
593: | |