prototype::_destroy()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 12
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 2.3755

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 9
c 1
b 0
f 0
nc 2
nop 1
dl 0
loc 12
ccs 6
cts 11
cp 0.5455
crap 2.3755
rs 9.9666
1
<?php
2
/*
3
 * This file is part of the Ariadne Component Library.
4
 *
5
 * (c) Muze <[email protected]>
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 */
10
namespace arc;
11
12
/**
13
 * Methods to create, extend and observe prototype objects in PHP. Also adds a memoize function,
14
 * which is useful when using a prototype object as a Dependency Injection container.
15
 * @package arc
16
 */
17
final class prototype
18
{
19
20
    /**
21
     * @var \SplObjectStorage contains a list of frozen objects and the observer
22
     */
23
    private static $frozen = null;
24
25
    /**
26
     * @var \SplObjectStorage contains a list of frozen objects and the observer
27
     */
28
    private static $sealed = null;
29
30
    /**
31
     * @var \SplObjectStorage contains a list of objects made unextensible and the observer
32
     */
33
    private static $notExtensible = null;
34
35
    /**
36
     * @var \SplObjectStorage contains a list of all 'child' instances for each prototype
37
     */
38
    private static $instances = null;
39
40
    /**
41
     * @var \SplObjectStorage contains a list of all observers for each prototype
42
     */
43
    private static $observers = null;
44
45
    /**
46
     * Returns a new \arc\prototype\Prototype object with the given properties. The 
47
     * properties array may contain closures, these will be available as methods on 
48
     * the new Prototype object.
49
     * @param array $properties List of properties and methods
50
     * @return \arc\prototype\Prototype
51
     */
52 32
    public static function create(array $properties = []) :prototype\Prototype
53
    {
54 32
        return new prototype\Prototype($properties);
55
    }
56
57
    /**
58
     * Returns a new \arc\prototype\Prototype object with the given object as its
59
     * prototype and the given properties and methods set.
60
     * @param \arc\prototype\Prototype $prototype The prototype for this object
61
     * @param array $properties List of properties and methods
62
     * @return \arc\prototype\Prototype
63
     */
64 10
    public static function extend(prototype\Prototype $prototype, array $properties = []) :prototype\Prototype
65
    {
66 10
        if (!isset(self::$instances)) {
67 2
            self::$instances = new \SplObjectStorage();
68
        };
69 10
        if (!isset(self::$instances[$prototype])) {
70 10
            self::$instances[$prototype] = [];
71
        }
72 10
        $properties['prototype'] = $prototype;
73 10
        $instance = new prototype\Prototype($properties);
74 10
        $list = self::$instances[$prototype];
75 10
        array_push($list,$instance);
76 10
        self::$instances[$prototype] = $list;
77 10
        return $instance;
78
    }
79
80
    /**
81
     * Helper method to remove cache information when a prototype is no longer needed.
82
     * @param \arc\prototype\Prototype $obj The object to be removed
83
     */
84 12
    public static function _destroy(prototype\Prototype $obj) :void
85
    {
86 12
        unset(self::$notExtensible[$obj]);
87 12
        unset(self::$sealed[$obj]);
88 12
        unset(self::$frozen[$obj]);
89 12
        unset(self::$observers[$obj]);
90 12
        if ( isset($obj->prototype) ) {
91
            $p = self::$instances[$obj->prototype];
92
            $p = array_filter($p, function($o) use ($obj) {
93
                return $o != $obj; // remove references to $obj
94
            });
95
            self::$instances[$obj->prototype] = $p;
96
        }
97 12
    }
98
99
    /**
100
     * Returns a new \arc\prototype\Prototype with the given prototype set. In addition 
101
     * all properties on the extra objects passed to this method will be copied to the 
102
     * new Prototype object. For any property that is set on multiple objects, the value 
103
     * of the property in the later object overwrites values from other objects.
104
     * @param \arc\prototype\Prototype $prototype the prototype for the new object
105
     * @param \arc\prototype\Prototype ...$object the objects whose properties will be assigned
106
     */
107 2
    public static function assign(prototype\Prototype $prototype, prototype\Prototype ...$objects) :prototype\Prototype
108
    {
109 2
        $properties = [];
110 2
        foreach ($objects as $obj) {
111 2
            $properties = $obj->properties + $properties;
112
        }
113 2
        return self::extend($prototype, $properties);
114
    }
115
116
    /**
117
     * This makes changes to the given Prototype object impossible.
118
     * The object becomes immutable. Any attempt to change the object will silently fail.
119
     * @param \arc\prototype\Prototype $prototype the object to freeze
120
     */
121 2
    public static function freeze(prototype\Prototype $prototype) :void
122
    {
123 2
        if (!isset(self::$frozen)) {
124 2
            self::$frozen = new \SplObjectStorage();
125
        }
126 2
        self::seal($prototype);
127 2
        self::$frozen[$prototype] = true;
128 2
    }
129
130
    /**
131
     * This prevents reconfiguring an object or adding new properties.
132
     * @param \arc\prototype\Prototype $prototype the object to freeze
133
     */
134 2
    public static function seal(prototype\Prototype $prototype) :void
135
    {
136 2
        if (!isset(self::$sealed)) {
137 2
            self::$sealed = new \SplObjectStorage();
138
        }
139 2
        self::preventExtensions($prototype);
140 2
        self::$sealed[$prototype] = true;
141 2
    }
142
143
    /**
144
     * Returns a list of keys of all the properties in the given prototype
145
     * @param \arc\prototype\Prototype $prototype
146
     * @return array
147
     */
148
    public static function keys(prototype\Prototype $prototype) :array
149
    {
150
        $entries = static::entries($prototype);
151
        return array_keys($entries);
152
    }
153
154
    /**
155
     * Returns an array with key:value pairs for all properties in the given prototype
156
     * @param \arc\prototype\Prototype $prototype
157
     * @return array
158
     */
159
    public static function entries(prototype\Prototype $prototype) :array
160
    {
161
        return $prototype->properties;
162
    }
163
164
    /**
165
     * Returns a list of all the property values in the given prototype
166
     * @param \arc\prototype\Prototype $prototype
167
     * @return array
168
     */
169
    public static function values(prototype\Prototype $prototype) :array
170
    {
171
        $entries = static::entries($prototype);
172
        return array_values($entries);
173
    }
174
175
    /**
176
     * Returns true if the the property name is available in this prototype
177
     * @param \arc\prototype\Prototype $prototype
178
     * @param string $property
179
     * @return bool
180
     */
181
    public static function hasProperty(prototype\Prototype $prototype, string $property) :bool
182
    {
183
        $entries = static::entries($prototype);
184
        return array_key_exists($property, $entries);
185
    }
186
187
    /**
188
     * Returns a list of all the property names defined in this prototype instance
189
     * without traversing its prototypes.
190
     * @param \arc\prototype\Prototype $prototype
191
     * @return array
192
     */
193
    public static function ownKeys(prototype\Prototype $prototype) :array
194
    {
195
        $entries = static::ownEntries($prototype);
196
        return array_keys($entries);
197
    }
198
199
    /**
200
     * Returns an array with key:value pairs for all properties in this prototype
201
     * instance wihtout traversing its prototypes.
202
     * @param \arc\prototype\Prototype $prototype
203
     * @return array
204
     */
205 2
    public static function ownEntries(prototype\Prototype $prototype) :array
206
    {
207 2
        return \arc\_getOwnEntries($prototype);
208
    }
209
210
    /**
211
     * Returns a list of all the property values in the given prototype
212
     * instance wihtout traversing its prototypes.
213
     * @param \arc\prototype\Prototype $prototype
214
     * @return array
215
     */
216
    public static function ownValues(prototype\Prototype $prototype) :array
217
    {
218
        $entries = static::ownEntries($prototype);
219
        return array_values($entries);
220
    }
221
222
    /**
223
     * Returns true if the the property name is available in this prototype
224
     * instance wihtout traversing its prototypes.
225
     * @param \arc\prototype\Prototype $prototype
226
     * @param string $property
227
     * @return bool
228
     */
229 2
    public static function hasOwnProperty(prototype\Prototype $prototype, string $property) :bool
230
    {
231 2
        $entries = static::ownEntries($prototype);
232 2
        return array_key_exists($property, $entries);
233
    }
234
235
    /**
236
     * Returns true if the given prototype is made immutable by freeze()
237
     * @param \arc\prototype\Prototype $prototype
238
     * @return bool
239
     */
240
    public static function isFrozen(prototype\Prototype $prototype) :bool
241
    {
242
        return isset(self::$frozen[$prototype]);
243
    }
244
245
    /**
246
     * Returns true if the given prototype is sealed by seal()
247
     * @param \arc\prototype\Prototype $prototype
248
     * @return bool
249
     */
250 8
    public static function isSealed(prototype\Prototype $prototype) :bool
251
    {
252 8
        return isset(self::$sealed[$prototype]);
253
    }
254
255
    /**
256
     * Returns true if the given prototype is made not Extensible
257
     * @param \arc\prototype\Prototype $prototype
258
     * @return bool
259
     */
260 6
    public static function isExtensible(prototype\Prototype $prototype) :bool
261
    {
262 6
        return !isset(self::$notExtensible[$prototype]);
263
    }
264
265
    /**
266
     * This calls the $callback function each time a property of $prototype is
267
     * changed or unset. The callback is called with the prototype object, the
268
     * name of the property and the new value (null if unset).
269
     * If the closure returns false exactly (no other 'falsy' values will work),
270
     * the change will be cancelled
271
     * @param \arc\prototype\Prototype $prototype
272
     * @param \Closure $callback
273
     * @param array $acceptList (optional)
274
     */
275 2
    public static function observe(prototype\Prototype $prototype, callable $callback, array $acceptList=null) :void
276
    {
277 2
        if ( !isset(self::$observers) ) {
278 2
            self::$observers = new \SplObjectStorage();
279
        }
280 2
        if ( !isset(self::$observers[$prototype]) ) {
281 2
            self::$observers[$prototype] = [];
282
        }
283 2
        if ( !isset($acceptList) ) {
284 2
            $acceptList = ['add','update','delete','reconfigure'];
285
        }
286 2
        $observers = self::$observers[$prototype];
287 2
        foreach( $acceptList as $acceptType ) {
288 2
            if ( !isset($observers[$acceptType]) ) {
289 2
                $observers[$acceptType] = new \SplObjectStorage();
290
            }
291 2
            $observers[$acceptType][$callback] = true;
292
        }
293 2
        self::$observers[$prototype] = $observers;
294 2
    }
295
296
    /**
297
     * Returns a list of observers for the given prototype.
298
     * @param \arc\prototype\Prototype $prototype
299
     * @return array
300
     */
301 2
    public static function getObservers(prototype\Prototype $prototype) :array
302
    {
303 2
        return (isset(self::$observers[$prototype]) ? self::$observers[$prototype] : [] );
304
    }
305
306
    /**
307
     * Makes an object no longer extensible.
308
     * @param \arc\prototype\Prototype $prototype
309
     */
310 4
    public static function preventExtensions(prototype\Prototype $prototype) :void
311
    {
312 4
        if ( !isset(self::$notExtensible) ) {
313 2
            self::$notExtensible = new \SplObjectStorage();
314
        }
315 4
        self::$notExtensible[$prototype] = true;
316 4
    }
317
318
    /**
319
     * Removes an observer callback for the given prototype.
320
     * @param \arc\prototype\Prototyp $prototype
321
     * @param \Closure $callback the observer callback to be removed
322
     */
323
    public static function unobserve(prototype\Prototype $prototype, callable $callback) :void
324
    {
325
        if ( isset(self::$observers) && isset(self::$observers[$prototype]) ) {
326
            unset(self::$observers[$prototype][$callback]);
327
        }
328
    }
329
330
    /**
331
     * Returns true if the object as the given prototype somewhere in its
332
     * prototype chain, including itself.
333
     * @param \arc\prototype\Prototype $object
334
     * @param \arc\prototype\Prototype $prototype
335
     * @return bool
336
     */
337 2
    public static function hasPrototype(prototype\Prototype $obj, prototype\Prototype $prototype) :bool
338
    {
339 2
        if (!$obj->prototype) {
340
            return false;
341
        }
342 2
        if ($obj === $prototype || $obj->prototype === $prototype) {
343 2
            return true;
344
        }
345
346
        return static::hasPrototype($obj->prototype, $prototype );
347
    }
348
349
    /**
350
     * Returns a list of prototype objects that have this prototype object
351
     * in their prototype chain.
352
     * @param \arc\prototype\Prototype $prototype
353
     * @return array
354
     */
355
    public static function getDescendants(prototype\Prototype $prototype) :array
356
    {
357
        $instances = self::getInstances($prototype);
358
        $descendants = $instances;
359
        foreach ($instances as $instance) {
360
            $descendants += self::getDescendants($instance);
361
        }
362
        return $descendants;
363
    }
364
365
    /**
366
     * Returns a list of prototype objects that have this prototype object
367
     * as their direct prototype.
368
     * @param \arc\prototype\Prototype $prototype
369
     * @return array
370
     */
371 2
    public static function getInstances(prototype\Prototype $prototype) :array
372
    {
373 2
        return (isset(self::$instances[$prototype]) ? self::$instances[$prototype] : [] );
374
    }
375
376
    /**
377
     * Returns the full prototype chain for the given object.
378
     * @param \arc\prototype\Prototype $obj
379
     * @return array
380
     */
381 2
    public static function getPrototypes(prototype\Prototype $obj) :array
382
    {
383 2
        $prototypes = [];
384 2
        while ( $prototype = $obj->prototype ) {
385 2
            $prototypes[] = $prototype;
386 2
            $obj = $prototype;
387
        }
388 2
        return $prototypes;
389
    }
390
391
    /**
392
     * Returns a new function that calls the given function just once and then simply
393
     * returns its result on each subsequent call.
394
     * @param callable function to call just once and then remember the result
0 ignored issues
show
Bug introduced by
The type arc\function was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
395
     * @return \Closure
396
     */
397
    public static function memoize(callable $f) 
398
    {
399
        return memoize($f);
400
    }
401
}
402
403
/**
404
 * Helper function to make sure that the returned Closure is not defined in a static scope.
405
 * @param callable function to call just once and then remember the result
406
 * @return \Closure
407
 */
408 1
function memoize(callable $f) :callable
409
{
410
    return function () use ($f) {
411
        static $result;
412
        if (null === $result) {
413
            if ( $f instanceof \Closure && isset($this) ) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $this seems to never exist and therefore isset should always be false.
Loading history...
414
                $f = \Closure::bind($f, $this);
415
            }
416
            $result = $f();
417
        }
418
        return $result;
419
    };
420
}
421
422
/**
423
 * 'private' function that must be declared outside static scope, so we can bind
424
 * the closure to an object to peek into its private _ownProperties property
425
 * @param \arc\prototype\Prototype $prototype
426
 * @return array
427
 */
428 1
function _getOwnEntries(prototype\Prototype $prototype) :array
429
{
430
    // this needs access to the private _ownProperties variable
431
    // this is one way to do that.
432 2
    $f = \Closure::bind(function() {
433 2
        return $this->_ownProperties;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $this seems to be never defined.
Loading history...
434 2
    }, $prototype, $prototype);
435 2
    return $f();
436
}
437