1 <?php
2
3 namespace Docotory;
4
5 use ArrayAccess;
6
7 /**
8 * Base factory class. It can produce many type of a product, you can also bind them on the fly.
9 *
10 * @author Krisan Alfa Timur <krisanalfa@docotel.co.id>
11 */
12 abstract class Factory implements ArrayAccess
13 {
14 /**
15 * Resolved types stack
16 *
17 * @var array
18 */
19 protected static $resolved = [];
20
21 /**
22 * Registered custom creator
23 *
24 * @var array
25 */
26 protected static $customCreator = [];
27
28 /**
29 * Register new custom creator
30 *
31 * @param string $type
32 * @param callable $callable
33 *
34 * @return void
35 */
36 public static function extend($type, callable $callable)
37 {
38 static::$customCreator[$type] = $callable;
39 }
40
41 /**
42 * Register a custom creator if it has not been registered yet
43 *
44 * @param string $type
45 * @param callable $callable
46 *
47 * @return void
48 */
49 public static function extendIf($type, callable $callable)
50 {
51 if (! static::hasCustomCreator($type)) {
52 static::extend($type, $callable);
53 }
54 }
55
56 /**
57 * Register a new instance of a given type
58 *
59 * @param string $type
60 * @param mixed $instance
61 *
62 * @return void
63 */
64 public static function instance($type, $instance)
65 {
66 static::$resolved[$type] = $instance;
67 }
68
69 /**
70 * Determine if a custom creator has been registered or not
71 *
72 * @param string $type
73 *
74 * @return boolean
75 */
76 public static function hasCustomCreator($type)
77 {
78 return isset(static::$customCreator[$type]);
79 }
80
81 /**
82 * Determine if a type has been resolved
83 *
84 * @param string $type
85 *
86 * @return boolean
87 */
88 public static function resolved($type)
89 {
90 return isset(static::$resolved[$type]);
91 }
92
93 /**
94 * Call custom creator
95 *
96 * @param string $type
97 *
98 * @return mixed
99 */
100 protected static function callCustomCreator($type)
101 {
102 return call_user_func_array(static::$customCreator[$type], [container()]);
103 }
104
105 /**
106 * Get type
107 *
108 * @param string $type
109 *
110 * @throws ResolvingTypeException
111 *
112 * @return mixed
113 */
114 public function __get($type)
115 {
116 if (static::resolved($type)) {
117 return static::$resolved[$type];
118 }
119
120 if (static::hasCustomCreator($type)) {
121 $instance = static::callCustomCreator($type);
122 } else {
123 if (!method_exists($this, $methodName = 'create'.ucfirst($type))) {
124 throw new ResolvingTypeException("Type [$type] is not supported!");
125 }
126
127 $instance = $this->{$methodName}();
128 }
129
130 return static::$resolved[$type] = $instance;
131 }
132
133 /**
134 * Set instance of a type
135 *
136 * @param string $type
137 * @param mixed $instance
138 */
139 public function __set($type, $instance)
140 {
141 static::instance($type, $instance);
142 }
143
144 /**
145 * Determine if an item exists at an offset.
146 *
147 * @param mixed $type
148 *
149 * @return bool
150 */
151 public function offsetExists($type)
152 {
153 return static::resolved($type);
154 }
155
156 /**
157 * Get an item at a given offset.
158 *
159 * @param mixed $type
160 *
161 * @return mixed
162 */
163 public function offsetGet($type)
164 {
165 return static::$resolved[$type];
166 }
167
168 /**
169 * Set the item at a given offset.
170 *
171 * @param mixed $type
172 * @param mixed $instance
173 */
174 public function offsetSet($type, $instance)
175 {
176 static::instance($type, $instance);
177 }
178
179 /**
180 * Unset the item at a given offset.
181 *
182 * @param string $type
183 */
184 public function offsetUnset($type)
185 {
186 unset(static::$resolved[$type]);
187 }
188 }
189