1: | <?php declare(strict_types=1); |
2: | |
3: | namespace Salient\Core\Facade; |
4: | |
5: | use Salient\Container\Container; |
6: | use Salient\Contract\Container\BeforeGlobalContainerSetEventInterface; |
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: | return $instance->$name(...$arguments); |
109: | } |
110: | |
111: | |
112: | |
113: | |
114: | |
115: | private static function doLoad(?object $instance = null): object |
116: | { |
117: | [$name] = self::getNormalisedService(); |
118: | |
119: | if ($instance && !is_a($instance, $name)) { |
120: | throw new LogicException(sprintf( |
121: | '%s does not inherit %s', |
122: | get_class($instance), |
123: | $name, |
124: | )); |
125: | } |
126: | |
127: | $containerExists = class_exists(Container::class); |
128: | $container = $containerExists && Container::hasGlobalContainer() |
129: | ? Container::getGlobalContainer() |
130: | : null; |
131: | |
132: | $instance ??= $container |
133: | ? self::getInstanceFromContainer($container) |
134: | : self::createInstance() ?? ( |
135: | $containerExists |
136: | ? self::getInstanceFromContainer(new Container()) |
137: | : null |
138: | ); |
139: | |
140: | if (!$instance) { |
141: | throw new LogicException(sprintf( |
142: | 'Service not instantiable: %s::getService()', |
143: | static::class, |
144: | )); |
145: | } |
146: | |
147: | |
148: | |
149: | $dispatcher = static::class === Event::class |
150: | ? $instance |
151: | : Event::getInstance(); |
152: | |
153: | |
154: | if (static::class === App::class) { |
155: | |
156: | if ($containerExists && !$container) { |
157: | Container::setGlobalContainer($instance); |
158: | } |
159: | |
160: | $listenerId = $dispatcher->listen( |
161: | static function (BeforeGlobalContainerSetEventInterface $event): void { |
162: | if ($container = $event->getContainer()) { |
163: | App::swap($container); |
164: | } |
165: | } |
166: | ); |
167: | } else { |
168: | if ($container && !$container->hasInstance($name)) { |
169: | $container->instance($name, $instance); |
170: | } |
171: | |
172: | $listenerId = $dispatcher->listen( |
173: | static function (BeforeGlobalContainerSetEventInterface $event) use ($name, $instance): void { |
174: | if (($container = $event->getContainer()) && !$container->hasInstance($name)) { |
175: | $container->instance($name, $instance); |
176: | } |
177: | } |
178: | ); |
179: | } |
180: | |
181: | self::$ListenerIds[static::class] = $listenerId; |
182: | |
183: | if ($instance instanceof FacadeAwareInterface) { |
184: | $instance = $instance->withFacade(static::class); |
185: | } |
186: | |
187: | return $instance; |
188: | } |
189: | |
190: | |
191: | |
192: | |
193: | private static function getInstanceFromContainer(ContainerInterface $container): ?object |
194: | { |
195: | [$name, $list] = self::getNormalisedService(); |
196: | |
197: | foreach (Arr::extend([$name], ...$list) as $service) { |
198: | if ($container->has($service)) { |
199: | $instance = $service === $name |
200: | ? $container->get($name) |
201: | : $container->getAs($service, $name); |
202: | if (!is_a($instance, $name)) { |
203: | throw new LogicException(sprintf( |
204: | '%s does not inherit %s', |
205: | get_class($instance), |
206: | $name, |
207: | )); |
208: | } |
209: | return $instance; |
210: | } |
211: | } |
212: | |
213: | $service = self::getInstantiableService(); |
214: | return $service !== null |
215: | ? $container->getAs($service, $name) |
216: | : null; |
217: | } |
218: | |
219: | |
220: | |
221: | |
222: | private static function createInstance(): ?object |
223: | { |
224: | $service = self::getInstantiableService(); |
225: | return $service !== null |
226: | ? new $service() |
227: | : null; |
228: | } |
229: | |
230: | private static function doUnload(): void |
231: | { |
232: | |
233: | if (static::class === Event::class) { |
234: | $loaded = array_keys(self::$Instances); |
235: | if ($loaded && $loaded !== [Event::class]) { |
236: | throw new LogicException(sprintf( |
237: | '%s cannot be unloaded before other facades', |
238: | Event::class, |
239: | )); |
240: | } |
241: | } |
242: | |
243: | $id = self::$ListenerIds[static::class] ?? null; |
244: | if ($id !== null) { |
245: | Event::removeListener($id); |
246: | unset(self::$ListenerIds[static::class]); |
247: | } |
248: | |
249: | $instance = self::$Instances[static::class] ?? null; |
250: | if (!$instance) { |
251: | return; |
252: | } |
253: | |
254: | |
255: | if (static::class !== App::class) { |
256: | $container = class_exists(Container::class) |
257: | && Container::hasGlobalContainer() |
258: | ? Container::getGlobalContainer() |
259: | : null; |
260: | |
261: | if ($container) { |
262: | [$name] = self::getNormalisedService(); |
263: | if ( |
264: | $container->hasInstance($name) |
265: | && $container->get($name) === $instance |
266: | ) { |
267: | $container->removeInstance($name); |
268: | } |
269: | } |
270: | } |
271: | |
272: | if ($instance instanceof FacadeAwareInterface) { |
273: | $instance = $instance->withoutFacade(static::class, true); |
274: | } |
275: | |
276: | if ($instance instanceof Unloadable) { |
277: | $instance->unload(); |
278: | } |
279: | |
280: | unset(self::$Instances[static::class]); |
281: | } |
282: | } |
283: | |