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