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