1: <?php declare(strict_types=1);
2:
3: namespace Salient\Contract\Sync;
4:
5: use Salient\Contract\Core\Exception\MethodNotImplementedException;
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 MethodNotImplementedException} 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: * @template TEntity of SyncEntityInterface
199: *
200: * @param class-string<TEntity> $entityType
201: * @param int|string $entityId
202: * @param bool|null $offline - `null` (default) or `true`: allow the entity
203: * to be retrieved from the local entity store
204: * - `false`: do not retrieve the entity from the local entity store; return
205: * `null` if it has not been applied to the store in the current run
206: * @return TEntity|null
207: */
208: public function getEntity(
209: int $providerId,
210: string $entityType,
211: $entityId,
212: ?bool $offline = null
213: ): ?SyncEntityInterface;
214:
215: /**
216: * Register a deferred entity with the entity store
217: *
218: * If a matching entity has already been applied to the entity store, the
219: * deferred entity is resolved immediately, otherwise it is queued for
220: * retrieval from the provider.
221: *
222: * @template TEntity of SyncEntityInterface
223: *
224: * @param class-string<TEntity> $entityType
225: * @param int|string $entityId
226: * @param DeferredEntityInterface<TEntity> $entity
227: * @return $this
228: */
229: public function deferEntity(
230: int $providerId,
231: string $entityType,
232: $entityId,
233: DeferredEntityInterface $entity
234: );
235:
236: /**
237: * Register a deferred relationship with the entity store
238: *
239: * @template TEntity of SyncEntityInterface
240: *
241: * @param class-string<TEntity> $entityType
242: * @param class-string<SyncEntityInterface> $forEntityType
243: * @param int|string $forEntityId
244: * @param DeferredRelationshipInterface<TEntity> $relationship
245: * @return $this
246: */
247: public function deferRelationship(
248: int $providerId,
249: string $entityType,
250: string $forEntityType,
251: string $forEntityProperty,
252: $forEntityId,
253: DeferredRelationshipInterface $relationship
254: );
255:
256: /**
257: * Get a checkpoint to delineate between deferred entities and relationships
258: * that have already been registered, and any subsequent deferrals
259: *
260: * The value returned by this method can be used to limit the scope of
261: * {@see SyncStoreInterface::resolveDeferrals()},
262: * {@see SyncStoreInterface::resolveDeferredEntities()} and
263: * {@see SyncStoreInterface::resolveDeferredRelationships()}, e.g. to
264: * entities and relationships deferred during a particular operation.
265: */
266: public function getDeferralCheckpoint(): int;
267:
268: /**
269: * Resolve deferred entities and relationships recursively until no
270: * deferrals remain
271: *
272: * @param class-string<SyncEntityInterface>|null $entityType
273: * @return SyncEntityInterface[]
274: */
275: public function resolveDeferrals(
276: ?int $fromCheckpoint = null,
277: ?string $entityType = null,
278: ?int $providerId = null
279: ): array;
280:
281: /**
282: * Resolve deferred entities
283: *
284: * @param class-string<SyncEntityInterface>|null $entityType
285: * @return SyncEntityInterface[]
286: */
287: public function resolveDeferredEntities(
288: ?int $fromCheckpoint = null,
289: ?string $entityType = null,
290: ?int $providerId = null
291: ): array;
292:
293: /**
294: * Resolve deferred relationships
295: *
296: * @param class-string<SyncEntityInterface>|null $entityType
297: * @param class-string<SyncEntityInterface>|null $forEntityType
298: * @return SyncEntityInterface[][]
299: */
300: public function resolveDeferredRelationships(
301: ?int $fromCheckpoint = null,
302: ?string $entityType = null,
303: ?string $forEntityType = null,
304: ?string $forEntityProperty = null,
305: ?int $providerId = null
306: ): array;
307:
308: /**
309: * Check if a run of sync operations has started
310: */
311: public function runHasStarted(): bool;
312:
313: /**
314: * Get the run ID of the current run
315: *
316: * @throws LogicException if a run of sync operations has not started.
317: */
318: public function getRunId(): int;
319:
320: /**
321: * Get the UUID of the current run in hexadecimal form
322: *
323: * @throws LogicException if a run of sync operations has not started.
324: */
325: public function getRunUuid(): string;
326:
327: /**
328: * Get the UUID of the current run in raw binary form
329: *
330: * @throws LogicException if a run of sync operations has not started.
331: */
332: public function getBinaryRunUuid(): string;
333:
334: /**
335: * Register a non-fatal sync operation error with the entity store
336: *
337: * @return $this
338: */
339: public function recordError(SyncErrorInterface $error, bool $deduplicate = false);
340:
341: /**
342: * Get sync operation errors recorded so far
343: */
344: public function getErrors(): SyncErrorCollectionInterface;
345: }
346: