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