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: |