1: <?php declare(strict_types=1);
2:
3: namespace Salient\Sync;
4:
5: use Salient\Contract\Container\ContainerInterface;
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\Facade\Sync;
11: use Salient\Sync\Reflection\SyncProviderReflection;
12: use Salient\Utility\AbstractUtility;
13: use Salient\Utility\Arr;
14: use Salient\Utility\Get;
15: use Salient\Utility\Regex;
16: use LogicException;
17: use ReflectionClass;
18:
19: final class SyncUtil extends AbstractUtility
20: {
21: /**
22: * Check if a sync operation is CREATE_LIST, READ_LIST, UPDATE_LIST or
23: * DELETE_LIST
24: *
25: * @param SyncOperation::* $operation
26: * @return ($operation is SyncOperation::*_LIST ? true : false)
27: */
28: public static function isListOperation(int $operation): bool
29: {
30: return [
31: SyncOperation::CREATE_LIST => true,
32: SyncOperation::READ_LIST => true,
33: SyncOperation::UPDATE_LIST => true,
34: SyncOperation::DELETE_LIST => true,
35: ][$operation] ?? false;
36: }
37:
38: /**
39: * Check if a sync operation is CREATE, UPDATE, DELETE, CREATE_LIST,
40: * UPDATE_LIST or DELETE_LIST
41: *
42: * @param SyncOperation::* $operation
43: * @return ($operation is SyncOperation::READ* ? false : true)
44: */
45: public static function isWriteOperation(int $operation): bool
46: {
47: return [
48: SyncOperation::CREATE => true,
49: SyncOperation::UPDATE => true,
50: SyncOperation::DELETE => true,
51: SyncOperation::CREATE_LIST => true,
52: SyncOperation::UPDATE_LIST => true,
53: SyncOperation::DELETE_LIST => true,
54: ][$operation] ?? false;
55: }
56:
57: /**
58: * With a sync entity type and any non-abstract parents bound to it in a
59: * service container, get the first that is serviced by a provider
60: *
61: * @template T of SyncEntityInterface
62: *
63: * @param class-string<T> $entityType
64: * @return class-string<T>
65: * @throws LogicException if the provider does not service the entity type.
66: */
67: public static function getServicedEntityType(
68: string $entityType,
69: SyncProviderInterface $provider,
70: ContainerInterface $container
71: ): string {
72: $provider = new SyncProviderReflection($provider);
73: if ($provider->isSyncEntityProvider($entityType)) {
74: return $entityType;
75: }
76:
77: $entity = new ReflectionClass($entityType);
78: do {
79: $entity = $entity->getParentClass();
80: if (
81: !$entity
82: || $entity->isAbstract()
83: ) {
84: throw new LogicException(sprintf(
85: '%s does not service %s',
86: $provider->name,
87: $entityType,
88: ));
89: }
90: if (
91: is_a($container->getName($entity->name), $entityType, true)
92: && $provider->isSyncEntityProvider($entity)
93: ) {
94: break;
95: }
96: } while (true);
97:
98: /** @var class-string<T> */
99: return $entity->name;
100: }
101:
102: /**
103: * Get the name of a sync entity type's provider interface
104: *
105: * @param class-string<SyncEntityInterface> $entityType
106: * @return class-string<SyncProviderInterface>
107: */
108: public static function getEntityTypeProvider(
109: string $entityType,
110: ?SyncStoreInterface $store = null
111: ): string {
112: if ($store) {
113: $helper = $store->getNamespaceHelper($entityType);
114: if ($helper) {
115: return $helper->getEntityTypeProvider($entityType);
116: }
117: }
118:
119: /** @var class-string<SyncProviderInterface> */
120: return Arr::implode('\\', [
121: Get::namespace($entityType),
122: 'Provider',
123: Get::basename($entityType) . 'Provider',
124: ]);
125: }
126:
127: /**
128: * Get the names of sync entity types serviced by a provider interface
129: *
130: * @param class-string<SyncProviderInterface> $provider
131: * @return array<class-string<SyncEntityInterface>>
132: */
133: public static function getProviderEntityTypes(
134: string $provider,
135: ?SyncStoreInterface $store = null
136: ): array {
137: if ($store) {
138: $helper = $store->getNamespaceHelper($provider);
139: if ($helper) {
140: return $helper->getProviderEntityTypes($provider);
141: }
142: }
143:
144: if (Regex::match(
145: '/^\\\\?(?<namespace>(?:[^\\\\]++\\\\)*)Provider\\\\(?<class>[^\\\\]+)Provider$/',
146: $provider,
147: $matches,
148: )) {
149: /** @var array<class-string<SyncEntityInterface>> */
150: return [$matches['namespace'] . $matches['class']];
151: }
152:
153: return [];
154: }
155:
156: /**
157: * Get the canonical URI of a sync entity type
158: *
159: * @param class-string<SyncEntityInterface> $entityType
160: */
161: public static function getEntityTypeUri(
162: string $entityType,
163: bool $compact = true,
164: ?SyncStoreInterface $store = null
165: ): string {
166: if ($store) {
167: return $store->getEntityTypeUri($entityType, $compact);
168: }
169: return '/' . str_replace('\\', '/', ltrim($entityType, '\\'));
170: }
171:
172: /**
173: * Get an entity store, creating one if necessary
174: *
175: * If a container with a shared {@see SyncStoreInterface} instance is given,
176: * it is returned. Otherwise, {@see Sync::getInstance()} is returned if
177: * loaded, or a new {@see SyncStore} instance is created.
178: */
179: public static function getStore(?ContainerInterface $container = null): SyncStoreInterface
180: {
181: if ($container && $container->hasInstance(SyncStoreInterface::class)) {
182: return $container->get(SyncStoreInterface::class);
183: }
184: if (Sync::isLoaded()) {
185: return Sync::getInstance();
186: }
187: return new SyncStore();
188: }
189: }
190: