1: | <?php declare(strict_types=1); |
2: | |
3: | namespace Salient\Sync\Reflection; |
4: | |
5: | use Salient\Contract\Sync\SyncContextInterface; |
6: | use Salient\Contract\Sync\SyncEntityInterface; |
7: | use Salient\Contract\Sync\SyncOperation; |
8: | use Salient\Contract\Sync\SyncProviderInterface; |
9: | use Salient\Contract\Sync\SyncStoreInterface; |
10: | use Salient\Sync\SyncUtil; |
11: | use Salient\Utility\Str; |
12: | use Closure; |
13: | use ReflectionClass; |
14: | use ReflectionException; |
15: | |
16: | |
17: | |
18: | |
19: | |
20: | |
21: | class ReflectionSyncProvider extends ReflectionClass |
22: | { |
23: | use SyncReflectionTrait; |
24: | |
25: | |
26: | private static array $Interfaces = []; |
27: | |
28: | private static array $EntityTypes = []; |
29: | |
30: | private static array $EntityTypeBasenames = []; |
31: | |
32: | private static array $Methods = []; |
33: | |
34: | private static array $MagicMethods = []; |
35: | |
36: | private static array $Closures = []; |
37: | private SyncStoreInterface $Store; |
38: | |
39: | |
40: | |
41: | |
42: | public function __construct($provider, ?SyncStoreInterface $store = null) |
43: | { |
44: | $this->assertImplements($provider, SyncProviderInterface::class); |
45: | |
46: | $this->Store = $store |
47: | ?? ($provider instanceof SyncProviderInterface ? $provider->getStore() : null) |
48: | ?? SyncUtil::getStore(); |
49: | |
50: | parent::__construct($provider); |
51: | } |
52: | |
53: | |
54: | |
55: | |
56: | |
57: | |
58: | public function getSyncProviderInterfaces(): array |
59: | { |
60: | return self::$Interfaces[$this->name] |
61: | ??= array_keys($this->getSyncProviderReflectionInterfaces()); |
62: | } |
63: | |
64: | |
65: | |
66: | |
67: | |
68: | |
69: | public function getSyncProviderReflectionInterfaces(): array |
70: | { |
71: | foreach ($this->getInterfaces() as $name => $interface) { |
72: | if ($interface->isSubclassOf(SyncProviderInterface::class)) { |
73: | |
74: | $interfaces[$name] = $interface; |
75: | } |
76: | } |
77: | return $interfaces ?? []; |
78: | } |
79: | |
80: | |
81: | |
82: | |
83: | |
84: | |
85: | public function getSyncProviderEntityTypes(): array |
86: | { |
87: | return self::$EntityTypes[$this->name] |
88: | ??= array_keys($this->getSyncProviderReflectionEntities()); |
89: | } |
90: | |
91: | |
92: | |
93: | |
94: | |
95: | |
96: | |
97: | public function getSyncProviderEntityTypeBasenames(): array |
98: | { |
99: | return self::$EntityTypeBasenames[$this->name] |
100: | ??= $this->doGetSyncProviderEntityTypeBasenames(); |
101: | } |
102: | |
103: | |
104: | |
105: | |
106: | private function doGetSyncProviderEntityTypeBasenames(): array |
107: | { |
108: | foreach ($this->getSyncProviderReflectionEntities() as $name => $entity) { |
109: | $basename = Str::kebab($entity->getShortName()); |
110: | if (isset($basenames[$basename])) { |
111: | $basenames[$basename] = false; |
112: | continue; |
113: | } |
114: | $basenames[$basename] = $name; |
115: | } |
116: | return array_filter($basenames ?? []); |
117: | } |
118: | |
119: | |
120: | |
121: | |
122: | |
123: | |
124: | public function getSyncProviderReflectionEntities(): array |
125: | { |
126: | foreach ($this->getSyncProviderInterfaces() as $interface) { |
127: | foreach (SyncUtil::getProviderEntityTypes($interface, $this->Store) as $entityType) { |
128: | $entity = new ReflectionSyncEntity($entityType); |
129: | $entities[$entity->name] = $entity; |
130: | } |
131: | } |
132: | return $entities ?? []; |
133: | } |
134: | |
135: | |
136: | |
137: | |
138: | |
139: | |
140: | |
141: | |
142: | public function isSyncEntityProvider($entity): bool |
143: | { |
144: | if ($entity instanceof ReflectionSyncEntity) { |
145: | $entity = $entity->name; |
146: | } elseif ($entity instanceof SyncEntityInterface) { |
147: | $entity = get_class($entity); |
148: | } |
149: | |
150: | $interface = SyncUtil::getEntityTypeProvider($entity, $this->Store); |
151: | |
152: | return interface_exists($interface) |
153: | && $this->implementsInterface($interface); |
154: | } |
155: | |
156: | |
157: | |
158: | |
159: | |
160: | |
161: | |
162: | |
163: | |
164: | |
165: | |
166: | |
167: | public function getSyncOperationClosure(int $operation, $entity, SyncProviderInterface $provider): ?Closure |
168: | { |
169: | if (!$entity instanceof ReflectionSyncEntity) { |
170: | $entity = new ReflectionSyncEntity($entity); |
171: | } |
172: | |
173: | $closure = self::$Closures[$this->name][$entity->name][$operation] ?? null; |
174: | if ($closure === null) { |
175: | $method = $this->getSyncOperationMethod($operation, $entity); |
176: | if ($method !== null) { |
177: | $closure = fn(...$args) => $this->$method(...$args); |
178: | } |
179: | self::$Closures[$this->name][$entity->name][$operation] = $closure ?? false; |
180: | } |
181: | |
182: | return $closure |
183: | ? $closure->bindTo($provider) |
184: | : null; |
185: | } |
186: | |
187: | |
188: | |
189: | |
190: | |
191: | |
192: | |
193: | |
194: | public function getSyncOperationMethod(int $operation, $entity): ?string |
195: | { |
196: | if (!$entity instanceof ReflectionSyncEntity) { |
197: | $entity = new ReflectionSyncEntity($entity); |
198: | } |
199: | |
200: | if (SyncUtil::isListOperation($operation)) { |
201: | $plural = $entity->getPluralName(); |
202: | if ($plural !== null) { |
203: | $methods[] = [ |
204: | SyncOperation::CREATE_LIST => 'create', |
205: | SyncOperation::READ_LIST => 'get', |
206: | SyncOperation::UPDATE_LIST => 'update', |
207: | SyncOperation::DELETE_LIST => 'delete', |
208: | ][$operation] . Str::lower($plural); |
209: | } |
210: | } |
211: | |
212: | $name = Str::lower($entity->getShortName()); |
213: | switch ($operation) { |
214: | case SyncOperation::CREATE: |
215: | $methods[] = 'create' . $name; |
216: | $methods[] = 'create_' . $name; |
217: | break; |
218: | |
219: | case SyncOperation::READ: |
220: | $methods[] = 'get' . $name; |
221: | $methods[] = 'get_' . $name; |
222: | break; |
223: | |
224: | case SyncOperation::UPDATE: |
225: | $methods[] = 'update' . $name; |
226: | $methods[] = 'update_' . $name; |
227: | break; |
228: | |
229: | case SyncOperation::DELETE: |
230: | $methods[] = 'delete' . $name; |
231: | $methods[] = 'delete_' . $name; |
232: | break; |
233: | |
234: | case SyncOperation::CREATE_LIST: |
235: | $methods[] = 'createlist_' . $name; |
236: | break; |
237: | |
238: | case SyncOperation::READ_LIST: |
239: | $methods[] = 'getlist_' . $name; |
240: | break; |
241: | |
242: | case SyncOperation::UPDATE_LIST: |
243: | $methods[] = 'updatelist_' . $name; |
244: | break; |
245: | |
246: | case SyncOperation::DELETE_LIST: |
247: | $methods[] = 'deletelist_' . $name; |
248: | break; |
249: | } |
250: | |
251: | $methods = array_intersect_key( |
252: | $this->getSyncOperationMethods(), |
253: | array_flip($methods), |
254: | ); |
255: | |
256: | if (!$methods) { |
257: | return null; |
258: | } |
259: | |
260: | if (count($methods) > 1) { |
261: | throw new ReflectionException(sprintf( |
262: | '%s has multiple implementations of one operation: %s()', |
263: | $this->name, |
264: | implode('(), ', array_keys($methods)), |
265: | )); |
266: | } |
267: | |
268: | [, $methodEntity] = reset($methods); |
269: | $method = key($methods); |
270: | if ($methodEntity !== $entity->name) { |
271: | throw new ReflectionException(sprintf( |
272: | '%s::%s() does not operate on %s', |
273: | $this->name, |
274: | $method, |
275: | $entity->name, |
276: | )); |
277: | } |
278: | |
279: | return $method; |
280: | } |
281: | |
282: | |
283: | |
284: | |
285: | |
286: | |
287: | public function getSyncOperationMethods(): array |
288: | { |
289: | return self::$Methods[$this->name] |
290: | ??= $this->filterUniqueSyncOperationMethods(true); |
291: | } |
292: | |
293: | |
294: | |
295: | |
296: | |
297: | |
298: | |
299: | public function getSyncOperationMagicMethods(): array |
300: | { |
301: | return self::$MagicMethods[$this->name] |
302: | ??= $this->filterUniqueSyncOperationMethods(false); |
303: | } |
304: | |
305: | |
306: | |
307: | |
308: | private function filterUniqueSyncOperationMethods(bool $visible): array |
309: | { |
310: | foreach ($this->getUniqueSyncOperationMethods() as $method => $operation) { |
311: | if (!($visible xor ( |
312: | $this->hasMethod($method) |
313: | && $this->getMethod($method)->isPublic() |
314: | ))) { |
315: | $methods[$method] = $operation; |
316: | } |
317: | } |
318: | return $methods ?? []; |
319: | } |
320: | |
321: | |
322: | |
323: | |
324: | private function getUniqueSyncOperationMethods(): array |
325: | { |
326: | foreach ($this->getPossibleSyncOperationMethods() as $method => $operation) { |
327: | if (isset($methods[$method])) { |
328: | $methods[$method] = false; |
329: | continue; |
330: | } |
331: | $methods[$method] = $operation; |
332: | } |
333: | return array_filter($methods ?? []); |
334: | } |
335: | |
336: | |
337: | |
338: | |
339: | private function getPossibleSyncOperationMethods(): iterable |
340: | { |
341: | foreach ($this->getSyncProviderReflectionEntities() as $entity) { |
342: | $plural = $entity->getPluralName(); |
343: | if ($plural !== null) { |
344: | $plural = Str::lower($plural); |
345: | yield from [ |
346: | 'create' . $plural => [SyncOperation::CREATE_LIST, $entity->name], |
347: | 'get' . $plural => [SyncOperation::READ_LIST, $entity->name], |
348: | 'update' . $plural => [SyncOperation::UPDATE_LIST, $entity->name], |
349: | 'delete' . $plural => [SyncOperation::DELETE_LIST, $entity->name], |
350: | ]; |
351: | } |
352: | |
353: | $name = Str::lower($entity->getShortName()); |
354: | yield from [ |
355: | 'create' . $name => [SyncOperation::CREATE, $entity->name], |
356: | 'create_' . $name => [SyncOperation::CREATE, $entity->name], |
357: | 'get' . $name => [SyncOperation::READ, $entity->name], |
358: | 'get_' . $name => [SyncOperation::READ, $entity->name], |
359: | 'update' . $name => [SyncOperation::UPDATE, $entity->name], |
360: | 'update_' . $name => [SyncOperation::UPDATE, $entity->name], |
361: | 'delete' . $name => [SyncOperation::DELETE, $entity->name], |
362: | 'delete_' . $name => [SyncOperation::DELETE, $entity->name], |
363: | 'createlist_' . $name => [SyncOperation::CREATE_LIST, $entity->name], |
364: | 'getlist_' . $name => [SyncOperation::READ_LIST, $entity->name], |
365: | 'updatelist_' . $name => [SyncOperation::UPDATE_LIST, $entity->name], |
366: | 'deletelist_' . $name => [SyncOperation::DELETE_LIST, $entity->name], |
367: | ]; |
368: | } |
369: | } |
370: | } |
371: | |