1: <?php declare(strict_types=1);
2:
3: namespace Salient\Contract\Sync;
4:
5: use Salient\Contract\Core\MethodNotImplementedExceptionInterface;
6: use Salient\Contract\Core\ProviderInterface;
7: use Salient\Contract\Sync\Exception\HeartbeatCheckFailedExceptionInterface;
8: use Salient\Contract\Sync\Exception\UnreachableBackendExceptionInterface;
9: use InvalidArgumentException;
10: use LogicException;
11:
12: interface SyncStoreInterface
13: {
14: /**
15: * Register a namespace for sync entities and their provider interfaces
16: *
17: * A prefix can only be associated with one namespace per entity store and
18: * cannot be changed without resetting its backing database.
19: *
20: * If `$prefix` was registered on a previous run, its URI and PHP namespace
21: * are updated if they differ. This is by design and is intended to
22: * facilitate refactoring.
23: *
24: * If `$resolver` is `null`, the entity store assumes sync entities in
25: * `$namespace` are serviced by provider interfaces called
26: * `<entity-namespace>\Provider\<entity>Provider`, e.g. `Acme\Sync\User`
27: * entities would be serviced by `Acme\Sync\Provider\UserProvider`. Provide
28: * a {@see SyncClassResolverInterface} to modify this behaviour.
29: *
30: * @param string $prefix A short alternative to `$uri`. Case-insensitive.
31: * Must be unique to the entity store. Must be a scheme name compliant with
32: * Section 3.1 of \[RFC3986], i.e. a match for the regular expression
33: * `^[a-zA-Z][a-zA-Z0-9+.-]*$`.
34: * @param string $uri A globally unique namespace URI.
35: * @param string $namespace The PHP namespace that contains sync entity
36: * classes and their respective provider interfaces.
37: * @return $this
38: * @throws LogicException if the prefix is already registered.
39: * @throws InvalidArgumentException if the prefix is invalid.
40: */
41: public function registerNamespace(
42: string $prefix,
43: string $uri,
44: string $namespace,
45: ?SyncClassResolverInterface $resolver = null
46: );
47:
48: /**
49: * Get a class resolver for a sync entity or provider interface, or null if
50: * it is not in a namespace with a registered resolver
51: *
52: * @param class-string<SyncEntityInterface|SyncProviderInterface> $class
53: */
54: public function getClassResolver(string $class): ?SyncClassResolverInterface;
55:
56: /**
57: * Get a stable value that uniquely identifies a sync provider with the
58: * entity store
59: */
60: public function getProviderSignature(SyncProviderInterface $provider): string;
61:
62: /**
63: * Register a sync provider with the entity store
64: *
65: * @return $this
66: * @throws LogicException if the provider is already registered.
67: */
68: public function registerProvider(SyncProviderInterface $provider);
69:
70: /**
71: * Get the provider ID of a registered sync provider
72: *
73: * @throws LogicException if the provider is not registered.
74: */
75: public function getProviderId(SyncProviderInterface $provider): int;
76:
77: /**
78: * Get a sync provider if it is registered with the entity store
79: */
80: public function getProvider(string $signature): ?SyncProviderInterface;
81:
82: /**
83: * Throw an exception if a sync provider's backend is unreachable
84: *
85: * If no providers are given, every provider registered with the entity
86: * store is checked.
87: *
88: * If the same provider is given multiple times, it is only checked once.
89: *
90: * {@see MethodNotImplementedExceptionInterface} exceptions thrown by
91: * {@see ProviderInterface::checkHeartbeat()} are caught and ignored.
92: *
93: * @return $this
94: * @throws HeartbeatCheckFailedExceptionInterface if one or more providers
95: * throw an {@see UnreachableBackendExceptionInterface} exception.
96: */
97: public function checkProviderHeartbeats(
98: int $ttl = 300,
99: bool $failEarly = true,
100: SyncProviderInterface ...$providers
101: );
102:
103: /**
104: * Register a sync entity type with the entity store if it is not already
105: * registered
106: *
107: * `$entity` is case-sensitive and must exactly match the declared name of
108: * the sync entity class.
109: *
110: * @param class-string<SyncEntityInterface> $entity
111: * @return $this
112: */
113: public function registerEntity(string $entity);
114:
115: /**
116: * Get the entity ID of a registered sync entity type
117: *
118: * @param class-string<SyncEntityInterface> $entity
119: * @throws LogicException if the entity type is not registered.
120: */
121: public function getEntityId(string $entity): int;
122:
123: /**
124: * Get the canonical URI of a sync entity, or null if it is not in a
125: * registered namespace
126: *
127: * @param class-string<SyncEntityInterface> $entity
128: */
129: public function getEntityUri(string $entity, bool $compact = true): ?string;
130:
131: /**
132: * Get the prefix of a sync entity's namespace, or null if it is not in a
133: * registered namespace
134: *
135: * @param class-string<SyncEntityInterface> $entity
136: */
137: public function getEntityPrefix(string $entity): ?string;
138:
139: /**
140: * Apply a sync entity retrieved from a provider to the entity store,
141: * resolving any matching deferred entities
142: *
143: * @param class-string<SyncEntityInterface> $entityType
144: * @param int|string $entityId
145: * @return $this
146: * @throws LogicException if the entity has already been applied to the
147: * entity store or retrieved via {@see SyncStoreInterface::getEntity()} in
148: * the current run.
149: */
150: public function setEntity(
151: int $providerId,
152: string $entityType,
153: $entityId,
154: SyncEntityInterface $entity
155: );
156:
157: /**
158: * Get a sync entity from the entity store if it is available
159: *
160: * @param class-string<SyncEntityInterface> $entityType
161: * @param int|string $entityId
162: * @param bool|null $offline - `null` (default) or `true`: allow the entity
163: * to be retrieved from the local entity store
164: * - `false`: do not retrieve the entity from the local entity store; return
165: * `null` if it has not been applied to the store in the current run
166: */
167: public function getEntity(
168: int $providerId,
169: string $entityType,
170: $entityId,
171: ?bool $offline = null
172: ): ?SyncEntityInterface;
173:
174: /**
175: * Register a deferred entity with the entity store
176: *
177: * If a matching entity has already been applied to the entity store, the
178: * deferred entity is resolved immediately, otherwise it is queued for
179: * retrieval from the provider.
180: *
181: * @template TEntity of SyncEntityInterface
182: *
183: * @param class-string<TEntity> $entityType
184: * @param int|string $entityId
185: * @param DeferredEntityInterface<TEntity> $entity
186: * @return $this
187: */
188: public function deferEntity(
189: int $providerId,
190: string $entityType,
191: $entityId,
192: DeferredEntityInterface $entity
193: );
194:
195: /**
196: * Register a deferred relationship with the entity store
197: *
198: * @template TEntity of SyncEntityInterface
199: *
200: * @param class-string<TEntity> $entityType
201: * @param class-string<SyncEntityInterface> $forEntityType
202: * @param int|string $forEntityId
203: * @param DeferredRelationshipInterface<TEntity> $relationship
204: * @return $this
205: */
206: public function deferRelationship(
207: int $providerId,
208: string $entityType,
209: string $forEntityType,
210: string $forEntityProperty,
211: $forEntityId,
212: DeferredRelationshipInterface $relationship
213: );
214:
215: /**
216: * Get a checkpoint to delineate between deferred entities and relationships
217: * that have already been registered, and any subsequent deferrals
218: *
219: * The value returned by this method can be used to limit the scope of
220: * {@see SyncStoreInterface::resolveDeferrals()},
221: * {@see SyncStoreInterface::resolveDeferredEntities()} and
222: * {@see SyncStoreInterface::resolveDeferredRelationships()}, e.g. to
223: * entities and relationships deferred during a particular operation.
224: */
225: public function getDeferralCheckpoint(): int;
226:
227: /**
228: * Resolve deferred entities and relationships recursively until no
229: * deferrals remain
230: *
231: * @param class-string<SyncEntityInterface>|null $entityType
232: * @return SyncEntityInterface[]
233: */
234: public function resolveDeferrals(
235: ?int $fromCheckpoint = null,
236: ?string $entityType = null,
237: ?int $providerId = null
238: ): array;
239:
240: /**
241: * Resolve deferred entities
242: *
243: * @param class-string<SyncEntityInterface>|null $entityType
244: * @return SyncEntityInterface[]
245: */
246: public function resolveDeferredEntities(
247: ?int $fromCheckpoint = null,
248: ?string $entityType = null,
249: ?int $providerId = null
250: ): array;
251:
252: /**
253: * Resolve deferred relationships
254: *
255: * @param class-string<SyncEntityInterface>|null $entityType
256: * @param class-string<SyncEntityInterface>|null $forEntityType
257: * @return SyncEntityInterface[][]
258: */
259: public function resolveDeferredRelationships(
260: ?int $fromCheckpoint = null,
261: ?string $entityType = null,
262: ?string $forEntityType = null,
263: ?string $forEntityProperty = null,
264: ?int $providerId = null
265: ): array;
266:
267: /**
268: * Check if a run of sync operations has started
269: */
270: public function runHasStarted(): bool;
271:
272: /**
273: * Get the run ID of the current run
274: *
275: * @throws LogicException if a run of sync operations has not started.
276: */
277: public function getRunId(): int;
278:
279: /**
280: * Get the UUID of the current run in hexadecimal form
281: *
282: * @throws LogicException if a run of sync operations has not started.
283: */
284: public function getRunUuid(): string;
285:
286: /**
287: * Get the UUID of the current run in raw binary form
288: *
289: * @throws LogicException if a run of sync operations has not started.
290: */
291: public function getBinaryRunUuid(): string;
292:
293: /**
294: * Register a non-fatal sync operation error with the entity store
295: *
296: * @return $this
297: */
298: public function recordError(SyncErrorInterface $error, bool $deduplicate = false);
299:
300: /**
301: * Get sync operation errors recorded so far
302: */
303: public function getErrors(): SyncErrorCollectionInterface;
304: }
305: