1: | <?php declare(strict_types=1); |
2: | |
3: | namespace Salient\Core; |
4: | |
5: | use Salient\Container\Container; |
6: | use Salient\Contract\Container\BeforeGlobalContainerSetEventInterface; |
7: | use Salient\Contract\Container\ContainerInterface; |
8: | use Salient\Contract\Core\FacadeAwareInterface; |
9: | use Salient\Contract\Core\FacadeInterface; |
10: | use Salient\Contract\Core\Instantiable; |
11: | use Salient\Contract\Core\Unloadable; |
12: | use Salient\Core\Concern\HasUnderlyingService; |
13: | use Salient\Core\Facade\App; |
14: | use Salient\Core\Facade\Event; |
15: | use Salient\Utility\Get; |
16: | use LogicException; |
17: | |
18: | |
19: | |
20: | |
21: | |
22: | |
23: | |
24: | |
25: | |
26: | |
27: | abstract class AbstractFacade implements FacadeInterface |
28: | { |
29: | |
30: | use HasUnderlyingService; |
31: | |
32: | |
33: | private static array $Instances = []; |
34: | |
35: | private static array $ListenerIds = []; |
36: | |
37: | |
38: | |
39: | |
40: | |
41: | |
42: | |
43: | |
44: | |
45: | |
46: | abstract protected static function getService(); |
47: | |
48: | |
49: | |
50: | |
51: | final public static function isLoaded(): bool |
52: | { |
53: | return isset(self::$Instances[static::class]); |
54: | } |
55: | |
56: | |
57: | |
58: | |
59: | final public static function load(?object $instance = null): void |
60: | { |
61: | if (isset(self::$Instances[static::class])) { |
62: | throw new LogicException(sprintf('Already loaded: %s', static::class)); |
63: | } |
64: | |
65: | self::$Instances[static::class] = self::doLoad($instance); |
66: | } |
67: | |
68: | |
69: | |
70: | |
71: | final public static function swap(object $instance): void |
72: | { |
73: | self::doUnload(); |
74: | self::$Instances[static::class] = self::doLoad($instance); |
75: | } |
76: | |
77: | |
78: | |
79: | |
80: | final public static function unload(): void |
81: | { |
82: | self::doUnload(); |
83: | } |
84: | |
85: | |
86: | |
87: | |
88: | final public static function unloadAll(): void |
89: | { |
90: | foreach (array_keys(self::$Instances) as $class) { |
91: | |
92: | if ($class === Event::class) { |
93: | continue; |
94: | } |
95: | |
96: | $class::doUnload(); |
97: | } |
98: | |
99: | Event::doUnload(); |
100: | |
101: | self::unloadAllServiceLists(); |
102: | } |
103: | |
104: | |
105: | |
106: | |
107: | final public static function getInstance(): object |
108: | { |
109: | $instance = self::$Instances[static::class] |
110: | ??= self::doLoad(); |
111: | |
112: | if ($instance instanceof FacadeAwareInterface) { |
113: | return $instance->withoutFacade(static::class, false); |
114: | } |
115: | |
116: | return $instance; |
117: | } |
118: | |
119: | |
120: | |
121: | |
122: | |
123: | final public static function __callStatic(string $name, array $arguments) |
124: | { |
125: | return (self::$Instances[static::class] |
126: | ??= self::doLoad())->$name(...$arguments); |
127: | } |
128: | |
129: | |
130: | |
131: | |
132: | |
133: | private static function doLoad(?object $instance = null): object |
134: | { |
135: | $serviceName = self::getServiceName(); |
136: | |
137: | if ($instance !== null && ( |
138: | !is_object($instance) || !is_a($instance, $serviceName) |
139: | )) { |
140: | throw new LogicException(sprintf( |
141: | '%s does not inherit %s', |
142: | Get::type($instance), |
143: | $serviceName, |
144: | )); |
145: | } |
146: | |
147: | $container = Container::hasGlobalContainer() |
148: | ? Container::getGlobalContainer() |
149: | : null; |
150: | |
151: | $instance ??= $container |
152: | ? self::getInstanceFromContainer($container, $serviceName) |
153: | : self::createInstance(); |
154: | |
155: | $dispatcher = |
156: | |
157: | $instance instanceof EventDispatcher |
158: | |
159: | && static::class === Event::class |
160: | ? $instance |
161: | : Event::getInstance(); |
162: | |
163: | if ( |
164: | |
165: | $instance instanceof ContainerInterface |
166: | |
167: | && static::class === App::class |
168: | ) { |
169: | if (!$container) { |
170: | Container::setGlobalContainer($instance); |
171: | } |
172: | |
173: | $listenerId = $dispatcher->listen( |
174: | static function (BeforeGlobalContainerSetEventInterface $event): void { |
175: | if ($container = $event->getContainer()) { |
176: | App::swap($container); |
177: | } |
178: | } |
179: | ); |
180: | } else { |
181: | if ($container && !$container->hasInstance($serviceName)) { |
182: | $container->instance($serviceName, $instance); |
183: | } |
184: | |
185: | $listenerId = $dispatcher->listen( |
186: | static function (BeforeGlobalContainerSetEventInterface $event) use ($serviceName, $instance): void { |
187: | if (($container = $event->getContainer()) && !$container->hasInstance($serviceName)) { |
188: | $container->instance($serviceName, $instance); |
189: | } |
190: | } |
191: | ); |
192: | } |
193: | self::$ListenerIds[static::class] = $listenerId; |
194: | |
195: | if ($instance instanceof FacadeAwareInterface) { |
196: | $instance = $instance->withFacade(static::class); |
197: | } |
198: | |
199: | return $instance; |
200: | } |
201: | |
202: | |
203: | |
204: | |
205: | |
206: | private static function getInstanceFromContainer( |
207: | ContainerInterface $container, |
208: | string $serviceName |
209: | ): object { |
210: | |
211: | |
212: | foreach (self::getServiceList() as $service) { |
213: | if ($container->has($service)) { |
214: | $instance = $container->getAs($service, $serviceName); |
215: | if (!is_a($instance, $serviceName)) { |
216: | throw new LogicException(sprintf( |
217: | '%s does not inherit %s: %s::getService()', |
218: | get_class($instance), |
219: | $serviceName, |
220: | static::class, |
221: | )); |
222: | } |
223: | return $instance; |
224: | } |
225: | } |
226: | |
227: | |
228: | $service = self::getInstantiableService(); |
229: | if ($service !== null) { |
230: | return $container->getAs($service, $serviceName); |
231: | } |
232: | |
233: | throw new LogicException(sprintf( |
234: | 'Service not bound to container: %s::getService()', |
235: | static::class, |
236: | )); |
237: | } |
238: | |
239: | |
240: | |
241: | |
242: | private static function createInstance(): object |
243: | { |
244: | |
245: | $service = self::getInstantiableService(); |
246: | if ($service !== null) { |
247: | return new $service(); |
248: | } |
249: | |
250: | throw new LogicException(sprintf( |
251: | 'Service not instantiable: %s::getService()', |
252: | static::class, |
253: | )); |
254: | } |
255: | |
256: | private static function doUnload(): void |
257: | { |
258: | |
259: | if (static::class === Event::class) { |
260: | $loaded = array_keys(self::$Instances); |
261: | if ($loaded && $loaded !== [Event::class]) { |
262: | throw new LogicException(sprintf( |
263: | '%s cannot be unloaded before other facades', |
264: | Event::class, |
265: | )); |
266: | } |
267: | } |
268: | |
269: | $id = self::$ListenerIds[static::class] ?? null; |
270: | if ($id !== null) { |
271: | Event::removeListener($id); |
272: | unset(self::$ListenerIds[static::class]); |
273: | } |
274: | |
275: | $instance = self::$Instances[static::class] ?? null; |
276: | if (!$instance) { |
277: | return; |
278: | } |
279: | |
280: | if (static::class !== App::class) { |
281: | $container = Container::hasGlobalContainer() |
282: | ? Container::getGlobalContainer() |
283: | : null; |
284: | |
285: | if ($container) { |
286: | $serviceName = self::getServiceName(); |
287: | if ( |
288: | $container->hasInstance($serviceName) |
289: | && $container->get($serviceName) === $instance |
290: | ) { |
291: | $container->removeInstance($serviceName); |
292: | } |
293: | } |
294: | } |
295: | |
296: | if ($instance instanceof FacadeAwareInterface) { |
297: | $instance = $instance->withoutFacade(static::class, true); |
298: | } |
299: | |
300: | if ($instance instanceof Unloadable) { |
301: | $instance->unload(); |
302: | } |
303: | |
304: | unset(self::$Instances[static::class]); |
305: | |
306: | self::unloadServiceList(); |
307: | } |
308: | } |
309: | |