1: <?php
2:
3: 4: 5:
6: class Papi_Container implements ArrayAccess {
7:
8: 9: 10: 11: 12:
13: protected $classes = [];
14:
15: 16: 17: 18: 19:
20: protected $keys = [];
21:
22: 23: 24: 25: 26:
27: protected $values = [];
28:
29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39:
40: public function bind( $id, $value = null, $singleton = false ) {
41: if ( is_string( $id ) && $this->is_singleton( $id ) ) {
42: throw new Exception( sprintf(
43: 'Identifier `%s` is a singleton and cannot be rebind',
44: $id
45: ) );
46: }
47:
48: if ( is_object( $id ) && get_class( $id ) !== false ) {
49: $value = $id;
50: $id = $this->get_class_prefix( get_class( $id ), false );
51: $this->classes[$id] = true;
52: }
53:
54: if ( $value instanceof Closure ) {
55: $closure = $value;
56: } else {
57: $closure = $this->get_closure( $value, $singleton );
58: }
59:
60: $this->values[$id] = compact( 'closure', 'singleton' );
61: $this->keys[$id] = true;
62:
63: return $value;
64: }
65:
66: 67: 68: 69: 70: 71: 72: 73:
74: protected function call_closure( $closure, array $parameters = [] ) {
75: if ( $closure instanceof Closure ) {
76: $rc = new ReflectionFunction( $closure );
77: $args = $rc->getParameters();
78: $params = $parameters;
79: $classes = [
80: $this->get_class_prefix( get_class( $this ) ),
81: get_class( $this ),
82: get_parent_class( $this )
83: ];
84:
85: foreach ( $args as $index => $arg ) {
86: if ( $arg->getClass() === null ) {
87: continue;
88: }
89:
90: if ( in_array( $arg->getClass()->name, $classes ) ) {
91: $parameters[$index] = $this;
92: } else if ( $this->exists( $arg->getClass()->name ) ) {
93: $parameters[$index] = $this->make( $arg->getClass()->name );
94: }
95: }
96:
97: if ( ! empty( $args ) && empty( $parameters ) ) {
98: $parameters[0] = $this;
99: }
100:
101: if ( count( $args ) > count( $parameters ) ) {
102: $parameters = array_merge( $parameters, $params );
103: }
104:
105: return $this->call_closure(
106: call_user_func_array( $closure, $parameters ),
107: $parameters
108: );
109: }
110:
111: return $closure;
112: }
113:
114: 115: 116: 117: 118: 119: 120:
121: public function exists( $id ) {
122: return isset( $this->keys[$this->get_class_prefix( $id )] );
123: }
124:
125: 126: 127: 128: 129: 130: 131: 132:
133: protected function get_closure( $value, $singleton = false ) {
134: return function () use ( $value, $singleton ) {
135: return $value;
136: };
137: }
138:
139: 140: 141: 142: 143: 144: 145: 146:
147: protected function get_class_prefix( $id, $check = true ) {
148: if ( strpos( $id, '\\' ) !== false && $id[0] !== '\\' ) {
149: $class = '\\' . $id;
150:
151: if ( $check ) {
152: return isset( $this->classes[$class] ) ? $class : $id;
153: }
154:
155: return $class;
156: }
157:
158: return $id;
159: }
160:
161: 162: 163: 164: 165: 166: 167: 168: 169:
170: public function is_singleton( $id ) {
171: if ( ! is_string( $id ) ) {
172: throw new InvalidArgumentException( 'Invalid argument. Must be string.' );
173: }
174:
175: if ( ! $this->exists( $id ) ) {
176: return false;
177: }
178:
179: $id = $this->get_class_prefix( $id );
180:
181: return $this->values[$id]['singleton'] === true;
182: }
183:
184: 185: 186: 187: 188: 189: 190: 191: 192: 193:
194: public function make( $id, array $parameters = [] ) {
195: if ( ! $this->exists( $id ) ) {
196: throw new Exception( sprintf(
197: 'Identifier `%s` is not defined',
198: $id
199: ) );
200: }
201:
202: $id = $this->get_class_prefix( $id );
203: $value = $this->values[$id];
204: $closure = $value['closure'];
205:
206: return $this->call_closure( $closure, $parameters );
207: }
208:
209: 210: 211: 212: 213:
214: public function remove( $id ) {
215: $id = $this->get_class_prefix( $id );
216: unset( $this->keys[$id], $this->values[$id] );
217: }
218:
219: 220: 221: 222: 223: 224: 225: 226:
227: public function singleton( $id, $value = null ) {
228: return $this->bind( $id, $value, true );
229: }
230:
231: 232: 233: 234: 235: 236: 237: 238: 239:
240: public function offsetExists( $id ) {
241: return $this->exists( $id );
242: }
243:
244: 245: 246: 247: 248: 249: 250: 251: 252:
253: public function offsetGet( $id ) {
254: return $this->make( $id );
255: }
256:
257: 258: 259: 260: 261: 262: 263: 264:
265: public function offsetSet( $id, $value ) {
266: $this->bind( $id, $value );
267: }
268:
269: 270: 271: 272: 273: 274: 275:
276: public function offsetUnset( $id ) {
277: $this->remove( $id );
278: }
279: }
280: