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