1: <?php
2: /**
3: * This file contains the implementation of the class ObjectFactory
4: * for creation of the objects.
5: *
6: * @package Factory
7: *
8: * @author Oleg Schildt
9: */
10:
11: namespace SmartFactory;
12:
13: /**
14: * Class for creation of the objects.
15: *
16: * The class ObjectFactory is an auxiliary class that empowers the factory methods. It provides the methods
17: * for binding of class implementations to the interfaces and for creation of objects for the requested interface.
18: *
19: * @author Oleg Schildt
20: */
21: class ObjectFactory
22: {
23: /**
24: * Internal array for storing the singleton instances for re-using.
25: *
26: * @var array
27: *
28: * @author Oleg Schildt
29: */
30: static protected array $singletons = [];
31:
32: /**
33: * Internal array for storing the mapping between interfaces/classes and the bound classes.
34: *
35: * @var array
36: *
37: * @author Oleg Schildt
38: */
39: static protected array $itable = [];
40:
41: /**
42: * Binds a class to an interface or a parent class.
43: *
44: * The key point of this approach is the definition common interfaces
45: * and implementation of them in classes. When an object that supports an
46: * interface is requested, an instance of the corresponding bound class
47: * is created. When you want to change the class, you need just bind
48: * the new class to the interface.
49: *
50: * You can also bind a class to itself and request it by own name in factory
51: * method. And later, you can implement a derived class and change the binding
52: * without affecting the code in the business logic.
53: *
54: * @param object|string $interface_or_class
55: * Name of the class/interface as string or the class/interface.
56: *
57: * @param object|string $class
58: * The class which instance should be created if the object is requested.
59: *
60: * @param ?callable $init_function
61: * The optional initialization function. You can provide it to do some
62: * custom initialization. The signature of
63: * this function is:
64: *
65: * ```php
66: * function (object $instance) : void;
67: * ```
68: *
69: * - $instance - the created instance.
70: *
71: * Example:
72: *
73: * ```php
74: * ObjectFactory::bindClass(ILanguageManager::class, LanguageManager::class, function($instance) {
75: * $instance->detectLanguage();
76: * });
77: *
78: * ObjectFactory::bindClass(IRecordsetManager::class, RecordsetManager::class, function($instance) {
79: * $instance->setDBWorker(dbworker());
80: * });
81: * ```
82: *
83: * @return void
84: *
85: * @throws \Exception
86: * It might throw the following exceptions in the case of any errors:
87: *
88: * - if the interface or bound class is not specified.
89: * - if the interface or class does not exist.
90: * - if the bound class is empty.
91: * - if the bound class does not implement the corresponding interface.
92: * - if the bound class is not instantiable.
93: * - if the check of the classes and interfaces fails.
94: *
95: * @author Oleg Schildt
96: */
97: static public function bindClass(object|string $interface_or_class, object|string $class, ?callable $init_function = null): void
98: {
99: self::bindClassContext("default", $interface_or_class, $class, $init_function);
100: } // bindClass
101:
102: /**
103: * Binds a class to an interface or a parent class.
104: *
105: * The key point of this approach is the definition common interfaces
106: * and implementation of them in classes. When an object that supports an
107: * interface is requested, an instance of the corresponding bound class
108: * is created. When you want to change the class, you need just bind
109: * the new class to the interface.
110: *
111: * You can also bind a class to itself and request it by own name in factory
112: * method. And later, you can implement a derived class and change the binding
113: * without affecting the code in the business logic.
114: *
115: * @param string $context
116: * The context of binding. You can introduce a different context, and bind another implementation.
117: * Then, you can request another instance specifying the context explicitly.
118: *
119: * @param object|string $interface_or_class
120: * Name of the class/interface as string or the class/interface.
121: *
122: * @param object|string $class
123: * The class which instance should be created if the object is requested.
124: *
125: * @param ?callable $init_function
126: * The optional initialization function. You can provide it to do some
127: * custom initialization. The signature of
128: * this function is:
129: *
130: * ```php
131: * function (object $instance) : void;
132: * ```
133: *
134: * - $instance - the created instance.
135: *
136: * Example:
137: *
138: * ```php
139: * ObjectFactory::bindClassContext("console", ILanguageManager::class, LanguageManager::class, function($instance) {
140: * $instance->detectLanguage();
141: * });
142: *
143: * ObjectFactory::bindClassContext("console", IRecordsetManager::class, RecordsetManager::class, function($instance) {
144: * $instance->setDBWorker(dbworker());
145: * });
146: * ```
147: *
148: * @return void
149: *
150: * @throws \Exception
151: * It might throw the following exceptions in the case of any errors:
152: *
153: * - if the interface or bound class is not specified.
154: * - if the interface or class does not exist.
155: * - if the bound class is empty.
156: * - if the bound class does not implement the corresponding interface.
157: * - if the bound class is not instantiable.
158: * - if the check of the classes and interfaces fails.
159: *
160: * @author Oleg Schildt
161: */
162: static public function bindClassContext(string $context, object|string $interface_or_class, object|string $class, ?callable $init_function = null): void
163: {
164: if (empty($class)) {
165: throw new \Exception("Bound class is empty!");
166: }
167:
168: if (empty($interface_or_class)) {
169: throw new \Exception("Bound interface or class is empty!");
170: }
171:
172: if (!interface_exists($interface_or_class) && !class_exists($interface_or_class)) {
173: throw new \Exception(sprintf("The interface or class '%s' does not exist!", $interface_or_class));
174: }
175:
176: if (!class_exists($class)) {
177: throw new \Exception(sprintf("The class '%s' does not exist!", $class));
178: }
179:
180: $ic = new \ReflectionClass($interface_or_class);
181: $c = new \ReflectionClass($class);
182:
183: if (!$c->isInstantiable()) {
184: throw new \Exception(sprintf("The class '%s' is not instantiable!", $c->getName()));
185: }
186:
187: if ($c != $ic) {
188: if (!$c->isSubclassOf($ic)) {
189: throw new \Exception(sprintf("The class '%s' does not implement the interface '%s'!", $c->getName(), $ic->getName()));
190: }
191: }
192:
193: $f = null;
194: if ($init_function !== null) {
195: if (!is_callable($init_function)) {
196: throw new \Exception(sprintf("'%s' is not a function!", $init_function));
197: }
198:
199: $f = new \ReflectionFunction($init_function);
200: }
201:
202: self::$itable[$context][$ic->getName()] = ["class" => $c, "init_function" => $f];
203: } // bindClassContext
204:
205: /**
206: * Сreates an object that support the interface $interface_or_class.
207: *
208: * @param object|string $interface_or_class
209: * Name of the class/interface which instance should be created.
210: *
211: * @param bool $singleton
212: * If the parameter is true, it ensures that only one instance of this object exists.
213: * The singleton is a usual patter for the action objects like SessionManager, EventManager,
214: * DBWorker etc. It makes no sense to produce many instances of such classes,
215: * it wastes the computer resources and might cause errors.
216: *
217: * @param string $context
218: * The context of binding. You can introduce a different context, and bind another implementation.
219: * Then, you can request another instance specifying the context explicitly.
220: *
221: * @return object
222: * Returns object of the class bound to the interface.
223: *
224: * @throws \Exception
225: * It might throw the following exceptions in the case of any errors:
226: *
227: * - if the interface or class is not specified.
228: * - if the interface or class does not exist.
229: * - if the check of the classes and interfaces fails.
230: *
231: * @used_by \SmartFactory\instance()
232: * @used_by \SmartFactory\singleton()
233: *
234: * @author Oleg Schildt
235: */
236: static public function getInstance(object|string $interface_or_class, bool $singleton, string $context = "default"): object
237: {
238: if (empty($interface_or_class)) {
239: throw new \Exception("Class or interface is not specified!");
240: }
241:
242: if (!interface_exists($interface_or_class) && !class_exists($interface_or_class)) {
243: throw new \Exception(sprintf("The interface or class '%s' does not exist!", $interface_or_class));
244: }
245:
246: $class = new \ReflectionClass($interface_or_class);
247:
248: $class_name = $class->getName();
249:
250: if (empty(self::$itable[$context][$class_name])) {
251: throw new \Exception(sprintf("The interface or class '%s' has no bound class in the context '$context'!", $class_name));
252: }
253:
254: // if not singleton, we create a new instance every time it is requested
255: if (!$singleton) {
256: $instance = self::$itable[$context][$class_name]["class"]->newInstance();
257:
258: if (!empty(self::$itable[$context][$class_name]["init_function"])) {
259: self::$itable[$context][$class_name]["init_function"]->invoke($instance);
260: }
261:
262: return $instance;
263: }
264:
265: // if singleton, we create an instance only if it does not exist yet
266: if (empty(self::$singletons[$context][$class_name])) {
267: self::$singletons[$context][$class_name] = self::$itable[$context][$class_name]["class"]->newInstance();
268:
269: if (!empty(self::$itable[$context][$class_name]["init_function"])) {
270: self::$itable[$context][$class_name]["init_function"]->invoke(self::$singletons[$context][$class_name]);
271: }
272: }
273:
274: return self::$singletons[$context][$class_name];
275: } // getInstance
276: } // ObjectFactory