22
33namespace Lkrms \Concept ;
44
5+ use Lkrms \Concern \ResolvesServiceLists ;
56use Lkrms \Container \Event \GlobalContainerSetEvent ;
67use Lkrms \Container \Container ;
7- use Lkrms \Contract \IFacade ;
8- use Lkrms \Contract \ReceivesFacade ;
8+ use Lkrms \Contract \FacadeAwareInterface ;
9+ use Lkrms \Contract \FacadeInterface ;
10+ use Lkrms \Contract \IContainer ;
911use Lkrms \Contract \Unloadable ;
1012use Lkrms \Facade \Event ;
1113use Lkrms \Support \EventDispatcher ;
14+ use Lkrms \Utility \Get ;
1215use LogicException ;
1316
1417/**
1518 * Base class for facades
1619 *
17- * @template TClass of object
18- * @implements IFacade<TClass>
20+ * @template TService of object
21+ *
22+ * @implements FacadeInterface<TService>
1923 */
20- abstract class Facade implements IFacade
24+ abstract class Facade implements FacadeInterface
2125{
26+ /** @use ResolvesServiceLists<TService> */
27+ use ResolvesServiceLists;
28+
2229 /**
23- * Get the name of the underlying class
30+ * Get the facade's underlying class, or an array that maps its underlying
31+ * class to compatible implementations
32+ *
33+ * At least one of the values returned should be an instantiable class that
34+ * is guaranteed to exist.
2435 *
25- * @return class-string<TClass >
36+ * @return class-string<TService>|array<class-string<TService>,class-string<TService>|array<class-string<TService>> >
2637 */
27- abstract protected static function getServiceName (): string ;
38+ abstract protected static function getService () ;
2839
2940 /**
30- * @var array<string,object >
41+ * @var array<class- string<static>,TService >
3142 */
32- private static $ Instances = [];
43+ private static array $ Instances = [];
3344
3445 /**
35- * @var array<string,int>
46+ * @var array<class- string<static> ,int>
3647 */
37- private static $ ListenerIds = [];
48+ private static array $ ListenerIds = [];
3849
3950 /**
40- * @return TClass
51+ * @internal
4152 */
42- private static function _load ()
53+ final public static function isLoaded (): bool
4354 {
44- $ service = static ::getServiceName ();
45-
46- $ container = Container::maybeGetGlobalContainer ();
47- if ($ container ) {
48- $ instance = $ container
49- ->singletonIf ($ service )
50- ->get ($ service , func_get_args ());
51- } else {
52- $ instance = new $ service (...func_get_args ());
53- }
55+ return isset (self ::$ Instances [static ::class]);
56+ }
5457
55- if ($ instance instanceof ReceivesFacade) {
56- $ instance ->setFacade (static ::class);
58+ /**
59+ * @internal
60+ */
61+ final public static function load (?object $ instance = null ): void
62+ {
63+ if (isset (self ::$ Instances [static ::class])) {
64+ throw new LogicException (sprintf ('Already loaded: %s ' , static ::class));
5765 }
5866
59- $ dispatcher = $ instance instanceof EventDispatcher
60- ? $ instance
61- : Event::getInstance ();
62- $ id = $ dispatcher ->listen (
63- function (GlobalContainerSetEvent $ event ) use ($ service , $ instance ): void {
64- $ container = $ event ->container ();
65- if ($ container ) {
66- $ container ->instanceIf ($ service , $ instance );
67- }
68- }
69- );
70- self ::$ ListenerIds [static ::class] = $ id ;
71-
72- return self ::$ Instances [static ::class] = $ instance ;
67+ self ::$ Instances [static ::class] = self ::doLoad ($ instance );
7368 }
7469
7570 /**
7671 * @internal
7772 */
78- final public static function isLoaded ( ): bool
73+ final public static function swap ( object $ instance ): void
7974 {
80- return isset (self ::$ Instances [static ::class]);
75+ self ::unload ();
76+ self ::$ Instances [static ::class] = self ::doLoad ($ instance );
8177 }
8278
8379 /**
84- * @internal
85- * @return TClass
80+ * Remove the underlying instances of all facades
8681 */
87- final public static function load ()
82+ final public static function unloadAll (): void
8883 {
89- if ( isset (self ::$ Instances[ static ::class]) ) {
90- throw new LogicException ( static ::class . ' already loaded ' );
84+ foreach ( array_keys (self ::$ Instances) as $ class ) {
85+ $ class :: unload ( );
9186 }
92-
93- return self ::_load (...func_get_args ());
9487 }
9588
9689 /**
@@ -104,37 +97,46 @@ final public static function unload(): void
10497 unset(self ::$ ListenerIds [static ::class]);
10598 }
10699
100+ $ instance = self ::$ Instances [static ::class] ?? null ;
101+ if (!$ instance ) {
102+ return ;
103+ }
104+
107105 $ container = Container::maybeGetGlobalContainer ();
108106 if ($ container ) {
109- $ container ->unbind (static ::getServiceName ());
107+ $ serviceName = self ::getServiceName ();
108+ if (
109+ $ container ->hasInstance ($ serviceName ) &&
110+ $ container ->get ($ serviceName ) === $ instance
111+ ) {
112+ $ container ->unbind ($ serviceName );
113+ }
110114 }
111115
112- $ instance = self ::$ Instances [static ::class] ?? null ;
113- if ($ instance ) {
114- if ($ instance instanceof Unloadable) {
115- $ instance ->unload ();
116- }
117- unset(self ::$ Instances [static ::class]);
116+ if ($ instance instanceof FacadeAwareInterface) {
117+ $ instance = $ instance ->withoutFacade (static ::class, true );
118118 }
119- }
120119
121- /**
122- * Clear the underlying instances of all facades
123- */
124- final public static function unloadAll (): void
125- {
126- foreach (array_keys (self ::$ Instances ) as $ class ) {
127- $ class ::unload ();
120+ if ($ instance instanceof Unloadable) {
121+ $ instance ->unload ();
128122 }
123+
124+ unset(self ::$ Instances [static ::class]);
129125 }
130126
131127 /**
132128 * @internal
133- * @return TClass
134129 */
135130 final public static function getInstance ()
136131 {
137- return self ::$ Instances [static ::class] ?? self ::_load ();
132+ $ instance = self ::$ Instances [static ::class]
133+ ??= self ::doLoad ();
134+
135+ if ($ instance instanceof FacadeAwareInterface) {
136+ return $ instance ->withoutFacade (static ::class, false );
137+ }
138+
139+ return $ instance ;
138140 }
139141
140142 /**
@@ -144,6 +146,109 @@ final public static function getInstance()
144146 */
145147 final public static function __callStatic (string $ name , array $ arguments )
146148 {
147- return (self ::$ Instances [static ::class] ?? self ::_load ())->$ name (...$ arguments );
149+ return (self ::$ Instances [static ::class]
150+ ??= self ::doLoad ())->$ name (...$ arguments );
151+ }
152+
153+ /**
154+ * @param TService|null $instance
155+ * @return TService
156+ */
157+ private static function doLoad ($ instance = null )
158+ {
159+ $ serviceName = self ::getServiceName ();
160+
161+ if ($ instance !== null && (
162+ !is_object ($ instance ) || !is_a ($ instance , $ serviceName )
163+ )) {
164+ throw new LogicException (sprintf (
165+ '%s does not inherit %s ' ,
166+ Get::type ($ instance ),
167+ $ serviceName ,
168+ ));
169+ }
170+
171+ $ container = Container::maybeGetGlobalContainer ();
172+
173+ $ instance ??= $ container
174+ ? self ::getInstanceFromContainer ($ container , $ serviceName )
175+ : self ::createInstance ();
176+
177+ if ($ container ) {
178+ $ container ->instanceIf ($ serviceName , $ instance );
179+ }
180+
181+ $ dispatcher = $ instance instanceof EventDispatcher
182+ ? $ instance
183+ : Event::getInstance ();
184+
185+ $ id = $ dispatcher ->listen (
186+ static function (GlobalContainerSetEvent $ event ) use ($ serviceName , $ instance ): void {
187+ $ container = $ event ->container ();
188+ if ($ container ) {
189+ $ container ->instanceIf ($ serviceName , $ instance );
190+ }
191+ }
192+ );
193+ self ::$ ListenerIds [static ::class] = $ id ;
194+
195+ if ($ instance instanceof FacadeAwareInterface) {
196+ $ instance = $ instance ->withFacade (static ::class);
197+ }
198+
199+ return $ instance ;
200+ }
201+
202+ /**
203+ * @return TService
204+ */
205+ private static function getInstanceFromContainer (
206+ IContainer $ container ,
207+ string $ serviceName
208+ ) {
209+ // If one of the services returned by the facade has been bound to the
210+ // container, resolve it to an instance
211+ foreach (self ::getServiceList () as $ service ) {
212+ if ($ container ->has ($ service )) {
213+ $ instance = $ container ->getAs ($ service , $ serviceName );
214+ if (!is_a ($ instance , $ serviceName )) {
215+ throw new LogicException (sprintf (
216+ '%s does not inherit %s: %s::getService() ' ,
217+ get_class ($ instance ),
218+ $ serviceName ,
219+ static ::class,
220+ ));
221+ }
222+ return $ instance ;
223+ }
224+ }
225+
226+ // Otherwise, use the container to resolve the first instantiable class
227+ $ service = self ::getInstantiableService ();
228+ if ($ service !== null ) {
229+ return $ container ->getAs ($ service , $ serviceName );
230+ }
231+
232+ throw new LogicException (sprintf (
233+ 'Service not bound to container: %s::getService() ' ,
234+ static ::class,
235+ ));
236+ }
237+
238+ /**
239+ * @return TService
240+ */
241+ private static function createInstance ()
242+ {
243+ // Create an instance of the first instantiable class
244+ $ service = self ::getInstantiableService ();
245+ if ($ service !== null ) {
246+ return new $ service ();
247+ }
248+
249+ throw new LogicException (sprintf (
250+ 'Service not instantiable: %s::getService() ' ,
251+ static ::class,
252+ ));
148253 }
149254}
0 commit comments