1: <?php declare(strict_types=1);
2:
3: namespace Salient\Contract\Sync;
4:
5: use Salient\Contract\Core\Provider\ProviderContextInterface;
6: use Salient\Contract\Sync\Exception\InvalidFilterSignatureExceptionInterface;
7: use DateTimeInterface;
8:
9: /**
10: * The context within which sync entities are instantiated by a provider
11: *
12: * @extends ProviderContextInterface<SyncProviderInterface,SyncEntityInterface>
13: */
14: interface SyncContextInterface extends ProviderContextInterface
15: {
16: /**
17: * @param bool $detectRecursion If `true`, check if the context has already
18: * been propagated for `$entity` and return the result via
19: * {@see SyncContextInterface::recursionDetected()}.
20: */
21: public function pushEntity($entity, bool $detectRecursion = false);
22:
23: /**
24: * Check if recursion was detected during the last call to pushEntity()
25: *
26: * @phpstan-assert-if-true !null $this->getLastEntity()
27: */
28: public function recursionDetected(): bool;
29:
30: /**
31: * Check if a sync operation has been applied to the context
32: *
33: * @phpstan-assert-if-true !null $this->getEntityType()
34: * @phpstan-assert-if-true !null $this->getOperation()
35: */
36: public function hasOperation(): bool;
37:
38: /**
39: * Get the sync operation applied to the context
40: *
41: * @return SyncOperation::*|null
42: */
43: public function getOperation(): ?int;
44:
45: /**
46: * Check if the context has an unclaimed filter applied via non-mandatory
47: * sync operation arguments
48: *
49: * @param string|null $key If `null`, check if the context has any unclaimed
50: * filters.
51: */
52: public function hasFilter(?string $key = null): bool;
53:
54: /**
55: * Get the value of an unclaimed filter applied to the context via
56: * non-mandatory sync operation arguments
57: *
58: * If `$orValue` is `true` and the context has a value for `$key`, it is
59: * returned if there is no matching filter, otherwise `null` is returned.
60: *
61: * @return (int|string|float|bool|null)[]|int|string|float|bool|null
62: */
63: public function getFilter(string $key, bool $orValue = true);
64:
65: /**
66: * Claim the value of an unclaimed filter applied via non-mandatory sync
67: * operation arguments, removing it from the context
68: *
69: * This method deliberately breaks the context's immutability contract.
70: *
71: * If `$orValue` is `true` and the context has a value for `$key`, it is
72: * returned if there is no matching filter, otherwise `null` is returned.
73: *
74: * @return (int|string|float|bool|null)[]|int|string|float|bool|null
75: */
76: public function claimFilter(string $key, bool $orValue = true);
77:
78: /**
79: * Get unclaimed filters applied to the context via non-mandatory sync
80: * operation arguments
81: *
82: * @return array<string,(int|string|float|bool|null)[]|int|string|float|bool|null>
83: */
84: public function getFilters(): array;
85:
86: /**
87: * Get an instance with the given sync operation and filters derived from
88: * its non-mandatory arguments
89: *
90: * An exception is thrown if non-mandatory arguments in `$args` don't match
91: * one of the following signatures.
92: *
93: * 1. An associative array (`fn(..., array<string,mixed> $filters)`)
94: *
95: * - Keys are trimmed
96: * - Keys that contain letters or numbers, optionally with inner
97: * whitespace, underscores or hyphens, are converted to snake_case
98: *
99: * 2. A list of identifiers (`fn(..., int ...$ids)` or `fn(..., string ...$ids)`)
100: *
101: * - Becomes `[ 'id' => $ids ]`
102: *
103: * 3. A list of entities (`fn(..., SyncEntityInterface ...$entities)`)
104: *
105: * - Grouped by snake_case {@see ServiceAwareInterface::getService()}
106: * short names
107: * - Example: `[ 'faculty' => [42, 71], 'faculty_user' => [101] ]`
108: *
109: * 4. No arguments (`fn(...)`)
110: *
111: * - Becomes `[]`
112: *
113: * In all cases:
114: *
115: * - {@see SyncEntityInterface} objects are replaced with their identifiers
116: * - An exception is thrown if any {@see SyncEntityInterface} objects do not
117: * have an identifier ({@see SyncEntityInterface::getId()} returns `null`)
118: * or do not have the same provider as the context
119: * - {@see DateTimeInterface} instances are formatted by the provider's date
120: * formatter.
121: * - The result is surfaced via {@see SyncContextInterface::hasFilter()},
122: * {@see SyncContextInterface::getFilter()},
123: * {@see SyncContextInterface::claimFilter()} and
124: * {@see SyncContextInterface::getFilters()}.
125: *
126: * {@see SyncContextInterface::claimFilter()} should generally be used to
127: * prevent sync operation failures caused by unclaimed filters.
128: *
129: * @param SyncOperation::* $operation
130: * @param class-string<SyncEntityInterface> $entityType
131: * @param mixed ...$args Sync operation arguments, not including the
132: * {@see SyncContextInterface} argument.
133: * @return static
134: * @throws InvalidFilterSignatureExceptionInterface
135: */
136: public function withOperation(int $operation, string $entityType, ...$args);
137:
138: /**
139: * Get the deferral policy applied to the context
140: *
141: * @return DeferralPolicy::*
142: */
143: public function getDeferralPolicy(): int;
144:
145: /**
146: * Get an instance with the given deferral policy
147: *
148: * @param DeferralPolicy::* $policy
149: * @return static
150: */
151: public function withDeferralPolicy(int $policy);
152:
153: /**
154: * Get the hydration policy applied to the context, optionally scoped by
155: * sync entity type
156: *
157: * @param class-string<SyncEntityInterface>|null $entityType
158: * @return HydrationPolicy::*
159: */
160: public function getHydrationPolicy(?string $entityType): int;
161:
162: /**
163: * Get an instance with the given hydration policy, optionally scoped by
164: * sync entity type and/or depth
165: *
166: * @param HydrationPolicy::* $policy
167: * @param class-string<SyncEntityInterface>|null $entityType Limit the scope
168: * of the change to an entity type.
169: * @param array<int<1,max>>|int<1,max>|null $depth Limit the scope of the
170: * change to entities at a given `$depth` from the current context.
171: * @return static
172: */
173: public function withHydrationPolicy(
174: int $policy,
175: ?string $entityType = null,
176: $depth = null
177: );
178:
179: /**
180: * Get the offline mode applied to the context
181: *
182: * @return bool|null - `null` (default): entities are returned from the
183: * local entity store if possible, otherwise they are retrieved from the
184: * provider.
185: * - `true`: entities are returned from the local entity store without
186: * falling back to retrieval from the provider.
187: * - `false`: entities are retrieved from the provider without consulting
188: * the local entity store.
189: */
190: public function getOffline(): ?bool;
191:
192: /**
193: * Get an instance with the given offline mode
194: *
195: * @param bool|null $offline - `null` (default): return entities from the
196: * local entity store if possible, otherwise retrieve them from the
197: * provider.
198: * - `true`: return entities from the local entity store without falling
199: * back to retrieval from the provider.
200: * - `false`: retrieve entities from the provider without consulting the
201: * local entity store.
202: * @return static
203: */
204: public function withOffline(?bool $offline);
205: }
206: