Completed
Push — 3 ( d27970...2b05d8 )
by Luke
21s
created

Config::merge_low_into_high()   B

Complexity

Conditions 8
Paths 17

Size

Total Lines 24

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 8
nc 17
nop 3
dl 0
loc 24
rs 8.4444
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * The configuration system works like this:
5
 *
6
 * Each class has a set of named properties
7
 *
8
 * Each named property can contain either
9
 *
10
 * - An array
11
 * - A non-array value
12
 *
13
 * If the value is an array, each value in the array may also be one of those
14
 * three types.
15
 *
16
 * A property can have a value specified in multiple locations, each of which
17
 * have a hard coded or explicit priority. We combine all these values together
18
 * into a "composite" value using rules that depend on the priority order of
19
 * the locations to give the final value, using these rules:
20
 *
21
 * - If the value is an array, each array is added to the _beginning_ of the
22
 *	composite array in ascending priority order. If a higher priority item has
23
 *	a non-integer key which is the same as a lower priority item, the value of
24
 * 	those items  is merged using these same rules, and the result of the merge
25
 *	is located in the same location the higher priority item would be if there
26
 *	was no key clash. Other than in this key-clash situation, within the
27
 * 	particular array, order is preserved.
28
 *
29
 * - If the value is not an array, the highest priority value is used without
30
 *	any attempt to merge.
31
 *
32
 * It is an error to have mixed types of the same named property in different
33
 * locations (but an error will not necessarily be raised due to optimizations
34
 * in the lookup code).
35
 *
36
 * The exception to this is "false-ish" values - empty arrays, empty strings,
37
 * etc. When merging a non-false-ish value with a false-ish value, the result
38
 * will be the non-false-ish value regardless of priority. When merging two
39
 * false-ish values the result will be the higher priority false-ish value.
40
 *
41
 * The locations that configuration values are taken from in highest -> lowest
42
 * priority order.
43
 *
44
 * - Any values set via a call to Config#update.
45
 *
46
 * - The configuration values taken from the YAML files in _config directories
47
 *	(internally sorted in before / after order, where the item that is latest
48
 *	is highest priority).
49
 *
50
 * - Any static set on an "additional static source" class (such as an
51
 *	extension) named the same as the name of the property.
52
 *
53
 * - Any static set on the class named the same as the name of the property.
54
 *
55
 * - The composite configuration value of the parent class of this class.
56
 *
57
 * At some of these levels you can also set masks. These remove values from the
58
 * composite value at their priority point rather than add. They are much
59
 * simpler. They consist of a list of key / value pairs. When applied against
60
 * the current composite value:
61
 *
62
 * - If the composite value is a sequential array, any member of that array
63
 *	that matches any value in the mask is removed.
64
 *
65
 * - If the composite value is an associative array, any member of that array
66
 *	that matches both the key and value of any pair in the mask is removed.
67
 *
68
 * - If the composite value is not an array, if that value matches any value
69
 * in the mask it is removed.
70
 *
71
 * @package framework
72
 * @subpackage core
73
 */
74
class Config {
75
76
	/**
77
	 * A marker instance for the "anything" singleton value. Don't access
78
	 * directly, even in-class, always use self::anything()
79
	 *
80
	 * @var SS_Object
81
	 */
82
	private static $_anything = null;
83
84
	/**
85
	 * Get a marker class instance that is used to do a "remove anything with
86
	 * this key" by adding $key => Config::anything() to the suppress array
87
	 *
88
	 * @return SS_Object
89
	 */
90
	public static function anything() {
91
		if (self::$_anything === null) {
92
			self::$_anything = new stdClass();
0 ignored issues
show
Documentation Bug introduced by
It seems like new \stdClass() of type object<stdClass> is incompatible with the declared type object<SS_Object> of property $_anything.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
93
		}
94
95
		return self::$_anything;
96
	}
97
98
	// -- Source options bitmask --
99
100
	/**
101
	 * source options bitmask value - merge all parent configuration in as
102
	 * lowest priority.
103
	 *
104
	 * @const
105
	 */
106
	const INHERITED = 0;
107
108
	/**
109
	 * source options bitmask value - only get configuration set for this
110
	 * specific class, not any of it's parents.
111
	 *
112
	 * @const
113
	 */
114
	const UNINHERITED = 1;
115
116
	/**
117
	 * source options bitmask value - inherit, but stop on the first class
118
	 * that actually provides a value (event an empty value).
119
	 *
120
	 * @const
121
	 */
122
	const FIRST_SET = 2;
123
124
	/**
125
	 * @const source options bitmask value - do not use additional statics
126
	 * sources (such as extension)
127
	 */
128
	const EXCLUDE_EXTRA_SOURCES = 4;
129
130
	// -- get_value_type response enum --
131
132
	/**
133
	 * Return flag for get_value_type indicating value is a scalar (or really
134
	 * just not-an-array, at least ATM)
135
	 *
136
	 * @const
137
	 */
138
	const ISNT_ARRAY = 1;
139
140
	/**
141
	 * Return flag for get_value_type indicating value is an array.
142
	 * @const
143
	 */
144
	const IS_ARRAY = 2;
145
146
	/**
147
	 * Get whether the value is an array or not. Used to be more complicated,
148
	 * but still nice sugar to have an enum to compare and not just a true /
149
	 * false value.
150
	 *
151
	 * @param $val any - The value
152
	 *
153
	 * @return int - One of ISNT_ARRAY or IS_ARRAY
154
	 */
155
	protected static function get_value_type($val) {
156
		if (is_array($val)) {
157
			return self::IS_ARRAY;
158
		}
159
160
		return self::ISNT_ARRAY;
161
	}
162
163
	/**
164
	 * What to do if there's a type mismatch.
165
	 *
166
	 * @throws UnexpectedValueException
167
	 */
168
	protected static function type_mismatch() {
169
		throw new UnexpectedValueException('Type mismatch in configuration. All values for a particular property must'
170
			. ' contain the same type (or no value at all).');
171
	}
172
173
	/**
174
	 * @todo If we can, replace next static & static methods with DI once that's in
175
	 */
176
	protected static $instance;
177
178
	/**
179
	 * Get the current active Config instance.
180
	 *
181
	 * Configs should not normally be manually created.
182
	 *
183
	 * In general use you will use this method to obtain the current Config
184
	 * instance.
185
	 *
186
	 * @return Config
187
	 */
188
	public static function inst() {
189
		if (!self::$instance) {
190
			self::$instance = new Config();
191
		}
192
193
		return self::$instance;
194
	}
195
196
	/**
197
	 * Set the current active {@link Config} instance.
198
	 *
199
	 * {@link Config} objects should not normally be manually created.
200
	 *
201
	 * A use case for replacing the active configuration set would be for
202
	 * creating an isolated environment for unit tests.
203
	 *
204
	 * @param Config $instance New instance of Config to assign
205
	 * @return Config Reference to new active Config instance
206
	 */
207
	public static function set_instance($instance) {
208
		self::$instance = $instance;
209
210
		global $_SINGLETONS;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
211
		$_SINGLETONS['Config'] = $instance;
212
		return $instance;
213
	}
214
215
	/**
216
	 * Make the newly active {@link Config} be a copy of the current active
217
	 * {@link Config} instance.
218
	 *
219
	 * You can then make changes to the configuration by calling update and
220
	 * remove on the new value returned by {@link Config::inst()}, and then discard
221
	 * those changes later by calling unnest.
222
	 *
223
	 * @return Config Reference to new active Config instance
224
	 */
225
	public static function nest() {
226
		$current = self::inst();
227
228
		$new = clone $current;
229
		$new->nestedFrom = $current;
230
		return self::set_instance($new);
231
	}
232
233
	/**
234
	 * Change the active Config back to the Config instance the current active
235
	 * Config object was copied from.
236
	 *
237
	 * @return Config Reference to new active Config instance
238
	 */
239
	public static function unnest() {
240
		if (self::inst()->nestedFrom) {
241
			self::set_instance(self::inst()->nestedFrom);
242
		}
243
		else {
244
			user_error(
245
				"Unable to unnest root Config, please make sure you don't have mis-matched nest/unnest",
246
				E_USER_WARNING
247
			);
248
		}
249
		return self::inst();
250
	}
251
252
	/**
253
	 * @var array
254
	 */
255
	protected $cache;
256
257
	/**
258
	 * Each copy of the Config object need's it's own cache, so changes don't
259
	 * leak through to other instances.
260
	 */
261
	public function __construct() {
262
		$this->cache = new Config_MemCache();
0 ignored issues
show
Documentation Bug introduced by
It seems like new \Config_MemCache() of type object<Config_MemCache> is incompatible with the declared type array of property $cache.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
263
	}
264
265
	public function __clone() {
266
		$this->cache = clone $this->cache;
0 ignored issues
show
Documentation Bug introduced by
It seems like clone $this->cache of type object is incompatible with the declared type array of property $cache.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
267
	}
268
269
	/**
270
	 * @var Config - The config instance this one was copied from when
271
	 * Config::nest() was called.
272
	 */
273
	protected $nestedFrom = null;
274
275
	/**
276
	 * @var array - Array of arrays. Each member is an nested array keyed as
277
	 * $class => $name => $value, where value is a config value to treat as
278
	 * the highest priority item.
279
	 */
280
	protected $overrides = array();
281
282
	/**
283
	 * @var array $suppresses Array of arrays. Each member is an nested array
284
	 * keyed as $class => $name => $value, where value is a config value suppress
285
	 * from any lower priority item.
286
	 */
287
	protected $suppresses = array();
288
289
	/**
290
	 * @var array
291
	 */
292
	protected $staticManifests = array();
293
294
	/**
295
	 * @param SS_ConfigStaticManifest
296
	 */
297
	public function pushConfigStaticManifest(SS_ConfigStaticManifest $manifest) {
298
		array_unshift($this->staticManifests, $manifest);
299
300
		$this->cache->clean();
0 ignored issues
show
Bug introduced by
The method clean cannot be called on $this->cache (of type array).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
301
	}
302
303
	/** @var [array] - The list of settings pulled from config files to search through */
304
	protected $manifests = array();
305
306
	/**
307
	 * Add another manifest to the list of config manifests to search through.
308
	 *
309
	 * WARNING: Config manifests to not merge entries, and do not solve before/after rules inter-manifest -
310
	 * instead, the last manifest to be added always wins
311
	 */
312
	public function pushConfigYamlManifest(SS_ConfigManifest $manifest) {
313
		array_unshift($this->manifests, $manifest);
314
315
		// Now that we've got another yaml config manifest we need to clean the cache
316
		$this->cache->clean();
0 ignored issues
show
Bug introduced by
The method clean cannot be called on $this->cache (of type array).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
317
		// We also need to clean the cache if the manifest's calculated config values change
318
		$manifest->registerChangeCallback(array($this->cache, 'clean'));
319
320
		// @todo: Do anything with these. They're for caching after config.php has executed
321
		$this->collectConfigPHPSettings = true;
0 ignored issues
show
Bug introduced by
The property collectConfigPHPSettings does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
322
		$this->configPHPIsSafe = false;
0 ignored issues
show
Bug introduced by
The property configPHPIsSafe does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
323
324
		$manifest->activateConfig();
325
326
		$this->collectConfigPHPSettings = false;
327
	}
328
329
	/** @var [Config_ForClass] - The list of Config_ForClass instances, keyed off class */
330
	static protected $for_class_instances = array();
331
332
	/**
333
	 * Get an accessor that returns results by class by default.
334
	 *
335
	 * Shouldn't be overridden, since there might be many Config_ForClass instances already held in the wild. Each
336
	 * Config_ForClass instance asks the current_instance of Config for the actual result, so override that instead
337
	 *
338
	 * @param $class
339
	 * @return Config_ForClass
340
	 */
341
	public function forClass($class) {
342
		if (isset(self::$for_class_instances[$class])) {
343
			return self::$for_class_instances[$class];
344
		}
345
		else {
346
			return self::$for_class_instances[$class] = new Config_ForClass($class);
347
		}
348
	}
349
350
	/**
351
	 * Merge a lower priority associative array into an existing higher priority associative array, as per the class
352
	 * docblock rules
353
	 *
354
	 * It is assumed you've already checked that you've got two associative arrays, not scalars or sequential arrays
355
	 *
356
	 * @param $dest array - The existing high priority associative array
357
	 * @param $src array - The low priority associative array to merge in
358
	 */
359
	public static function merge_array_low_into_high(&$dest, $src) {
360
		foreach ($src as $k => $v) {
361
			if (!$v) {
362
				continue;
363
			}
364
			else if (is_int($k)) {
365
				$dest[] = $v;
366
			}
367
			else if (isset($dest[$k])) {
368
				$newType = self::get_value_type($v);
369
				$currentType = self::get_value_type($dest[$k]);
370
371
				// Throw error if types don't match
372
				if ($currentType !== $newType) self::type_mismatch();
373
374
				if ($currentType == self::IS_ARRAY) self::merge_array_low_into_high($dest[$k], $v);
375
				else continue;
376
			}
377
			else {
378
				$dest[$k] = $v;
379
			}
380
		}
381
	}
382
383
	/**
384
	 * Merge a higher priority assocative array into an existing lower priority associative array, as per the class
385
	 * docblock rules.
386
	 *
387
	 * Much more expensive that the other way around, as there's no way to insert an associative k/v pair into an
388
	 * array at the top of the array
389
	 *
390
	 * @static
391
	 * @param $dest array - The existing low priority associative array
392
	 * @param $src array - The high priority array to merge in
393
	 */
394
	public static function merge_array_high_into_low(&$dest, $src) {
395
		$res = $src;
396
		self::merge_array_low_into_high($res, $dest);
397
		$dest = $res;
398
	}
399
400
	public static function merge_high_into_low(&$result, $value) {
401
		$newType = self::get_value_type($value);
402
403
		if (!$result) {
404
			$result = $value;
405
		}
406
		else {
407
			$currentType = self::get_value_type($result);
408
			if ($currentType !== $newType) self::type_mismatch();
409
410
			if ($currentType == self::ISNT_ARRAY) $result = $value;
411
			else self::merge_array_high_into_low($result, $value);
412
		}
413
	}
414
415
	public static function merge_low_into_high(&$result, $value, $suppress) {
416
		$newType = self::get_value_type($value);
417
418
		if ($suppress) {
419
			if ($newType == self::IS_ARRAY) {
420
				$value = self::filter_array_by_suppress_array($value, $suppress);
421
				if (!$value) return;
0 ignored issues
show
Bug Best Practice introduced by
The expression $value of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
422
			}
423
			else {
424
				if (self::check_value_contained_in_suppress_array($value, $suppress)) return;
425
			}
426
		}
427
428
		if (!$result) {
429
			$result = $value;
430
		}
431
		else {
432
			$currentType = self::get_value_type($result);
433
			if ($currentType !== $newType) self::type_mismatch();
434
435
			if ($currentType == self::ISNT_ARRAY) return; // PASS
436
			else self::merge_array_low_into_high($result, $value);
437
		}
438
	}
439
440
	public static function check_value_contained_in_suppress_array($v, $suppresses) {
441
		foreach ($suppresses as $suppress) {
442
			list($sk, $sv) = $suppress;
0 ignored issues
show
Unused Code introduced by
The assignment to $sk is unused. Consider omitting it like so list($first,,$third).

This checks looks for assignemnts to variables using the list(...) function, where not all assigned variables are subsequently used.

Consider the following code example.

<?php

function returnThreeValues() {
    return array('a', 'b', 'c');
}

list($a, $b, $c) = returnThreeValues();

print $a . " - " . $c;

Only the variables $a and $c are used. There was no need to assign $b.

Instead, the list call could have been.

list($a,, $c) = returnThreeValues();
Loading history...
443
			if ($sv === self::anything() || $v == $sv) return true;
444
		}
445
		return false;
446
	}
447
448
	static protected function check_key_or_value_contained_in_suppress_array($k, $v, $suppresses) {
0 ignored issues
show
Coding Style introduced by
As per PSR2, the static declaration should come after the visibility declaration.
Loading history...
449
		foreach ($suppresses as $suppress) {
450
			list($sk, $sv) = $suppress;
451
			if (($sk === self::anything() || $k == $sk) && ($sv === self::anything() || $v == $sv)) return true;
452
		}
453
		return false;
454
	}
455
456
	static protected function filter_array_by_suppress_array($array, $suppress) {
0 ignored issues
show
Coding Style introduced by
As per PSR2, the static declaration should come after the visibility declaration.
Loading history...
457
		$res = array();
458
459
		foreach ($array as $k => $v) {
460
			$suppressed = self::check_key_or_value_contained_in_suppress_array($k, $v, $suppress);
461
462
			if (!$suppressed) {
463
				if (is_numeric($k)) $res[] = $v;
464
				else $res[$k] = $v;
465
			}
466
		}
467
468
		return $res;
469
	}
470
471
	protected $extraConfigSources = array();
472
473
	public function extraConfigSourcesChanged($class) {
474
		unset($this->extraConfigSources[$class]);
475
		$this->cache->clean("__{$class}");
0 ignored issues
show
Bug introduced by
The method clean cannot be called on $this->cache (of type array).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
476
	}
477
478
	protected function getUncached($class, $name, $sourceOptions, &$result, $suppress, &$tags) {
479
		$tags[] = "__{$class}";
480
		$tags[] = "__{$class}__{$name}";
481
482
		// If result is already not something to merge into, just return it
483
		if ($result !== null && !is_array($result)) return $result;
484
485
		// First, look through the override values
486
		foreach($this->overrides as $k => $overrides) {
487
			if (isset($overrides[$class][$name])) {
488
				$value = $overrides[$class][$name];
489
490
				self::merge_low_into_high($result, $value, $suppress);
491
				if ($result !== null && !is_array($result)) return $result;
492
			}
493
494
			if (isset($this->suppresses[$k][$class][$name])) {
495
				$suppress = $suppress
496
					? array_merge($suppress, $this->suppresses[$k][$class][$name])
497
					: $this->suppresses[$k][$class][$name];
498
			}
499
		}
500
501
		$nothing = null;
502
503
		// Then the manifest values
504
		foreach($this->manifests as $manifest) {
505
			$value = $manifest->get($class, $name, $nothing);
506
			if ($value !== $nothing) {
507
				self::merge_low_into_high($result, $value, $suppress);
508
				if ($result !== null && !is_array($result)) return $result;
509
			}
510
		}
511
512
		$sources = array($class);
513
514
		// Include extensions only if not flagged not to, and some have been set
515
		if (($sourceOptions & self::EXCLUDE_EXTRA_SOURCES) != self::EXCLUDE_EXTRA_SOURCES) {
516
			// If we don't have a fresh list of extra sources, get it from the class itself
517
			if (!array_key_exists($class, $this->extraConfigSources)) {
518
				$this->extraConfigSources[$class] = SS_Object::get_extra_config_sources($class);
519
			}
520
521
			// Update $sources with any extra sources
522
			$extraSources = $this->extraConfigSources[$class];
523
			if ($extraSources) $sources = array_merge($sources, $extraSources);
524
		}
525
526
		$value = $nothing = null;
527
528
		foreach ($sources as $staticSource) {
529
			if (is_array($staticSource)) {
530
				$value = isset($staticSource[$name]) ? $staticSource[$name] : $nothing;
531
			}
532
			else {
533
				foreach ($this->staticManifests as $i => $statics) {
534
					$value = $statics->get($staticSource, $name, $nothing);
535
					if ($value !== $nothing) break;
536
				}
537
			}
538
539
			if ($value !== $nothing) {
540
				self::merge_low_into_high($result, $value, $suppress);
541
				if ($result !== null && !is_array($result)) return $result;
542
			}
543
		}
544
545
		// Finally, merge in the values from the parent class
546
		if (
547
			($sourceOptions & self::UNINHERITED) != self::UNINHERITED &&
548
			(($sourceOptions & self::FIRST_SET) != self::FIRST_SET || $result === null)
549
		) {
550
			$parent = get_parent_class($class);
551
			if ($parent) $this->getUncached($parent, $name, $sourceOptions, $result, $suppress, $tags);
552
		}
553
554
		return $result;
555
	}
556
557
	/**
558
	 * Get the config value associated for a given class and property
559
	 *
560
	 * This merges all current sources and overrides together to give final value
561
	 * todo: Currently this is done every time. This function is an inner loop function, so we really need to be
562
	 * caching heavily here.
563
	 *
564
	 * @param $class string - The name of the class to get the value for
565
	 * @param $name string - The property to get the value for
566
	 * @param int $sourceOptions Bitmask which can be set to some combintain of Config::UNINHERITED,
567
	 *                           Config::FIRST_SET, and Config::EXCLUDE_EXTENSIONS.
568
	 *
569
	 *   Config::UNINHERITED does not include parent classes when merging configuration fragments
570
	 *   Config::FIRST_SET stops inheriting once the first class that sets a value (even an empty value) is encoutered
571
	 *   Config::EXCLUDE_EXTRA_SOURCES does not include any additional static sources (such as extensions)
572
	 *
573
	 *   Config::INHERITED is a utility constant that can be used to mean "none of the above", equvilient to 0
574
	 *   Setting both Config::UNINHERITED and Config::FIRST_SET behaves the same as just Config::UNINHERITED
575
	 *
576
	 * should the parent classes value be merged in as the lowest priority source?
577
	 * @param $result array|scalar Reference to a variable to put the result in. Also returned, so this can be left
578
	 *                             as null safely. If you do pass a value, it will be treated as the highest priority
579
	 *                             value in the result chain
580
	 * @param $suppress array Internal use when called by child classes. Array of mask pairs to filter value by
581
	 * @return array|scalar The value of the config item, or null if no value set. Could be an associative array,
582
	 *                      sequential array or scalar depending on value (see class docblock)
583
	 */
584
	public function get($class, $name, $sourceOptions = 0, &$result = null, $suppress = null) {
0 ignored issues
show
Unused Code introduced by
The parameter $result is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
585
		// Have we got a cached value? Use it if so
586
		$key = $class.$name.$sourceOptions;
587
588
		list($cacheHit, $result) = $this->cache->checkAndGet($key);
0 ignored issues
show
Bug introduced by
The method checkAndGet cannot be called on $this->cache (of type array).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
589
		if (!$cacheHit) {
590
			$tags = array();
591
			$result = null;
592
			$this->getUncached($class, $name, $sourceOptions, $result, $suppress, $tags);
593
			$this->cache->set($key, $result, $tags);
0 ignored issues
show
Bug introduced by
The method set cannot be called on $this->cache (of type array).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
594
		}
595
596
		return $result;
597
	}
598
599
	/**
600
	 * Update a configuration value
601
	 *
602
	 * Configuration is modify only. The value passed is merged into the existing configuration. If you want to
603
	 * replace the current array value, you'll need to call remove first.
604
	 *
605
	 * @param string $class The class to update a configuration value for
606
	 * @param string $name  The configuration property name to update
607
	 * @param mixed $value The value to update with
0 ignored issues
show
Bug introduced by
There is no parameter named $value. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
608
	 *
609
	 * Arrays are recursively merged into current configuration as "latest" - for associative arrays the passed value
610
	 * replaces any item with the same key, for sequential arrays the items are placed at the end of the array, for
611
	 * non-array values, this value replaces any existing value
612
	 *
613
	 * You will get an error if you try and override array values with non-array values or vice-versa
614
	 */
615
	public function update($class, $name, $val) {
616
		if(is_null($val)) {
617
			$this->remove($class, $name);
618
		} else {
619
			if (!isset($this->overrides[0][$class])) $this->overrides[0][$class] = array();
620
621
			if (!array_key_exists($name, $this->overrides[0][$class])) {
622
				$this->overrides[0][$class][$name] = $val;
623
			} else {
624
				self::merge_high_into_low($this->overrides[0][$class][$name], $val);
625
			}
626
		}
627
628
		$this->cache->clean("__{$class}__{$name}");
0 ignored issues
show
Bug introduced by
The method clean cannot be called on $this->cache (of type array).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
629
	}
630
631
	/**
632
	 * Remove a configuration value
633
	 *
634
	 * You can specify a key, a key and a value, or neither. Either argument can be Config::anything(), which is
635
	 * what is defaulted to if you don't specify something
636
	 *
637
	 * This removes any current configuration value that matches the key and/or value specified
638
	 *
639
	 * Works like this:
640
	 *   - Check the current override array, and remove any values that match the arguments provided
641
	 *   - Keeps track of the arguments passed to this method, and in get filters everything _except_ the current
642
	 *     override array to exclude any match
643
	 *
644
	 * This way we can re-set anything removed by a call to this function by calling set. Because the current override
645
	 * array is only filtered immediately on calling this remove method, that value will then be exposed. However,
646
	 * every other source is filtered on request, so no amount of changes to parent's configuration etc can override a
647
	 * remove call.
648
	 *
649
	 * @param string $class The class to remove a configuration value from
650
	 * @param string $name The configuration name
651
	 *
652
	 * Matching is always by "==", not by "==="
653
	 */
654
	public function remove($class, $name /*,$key = null*/ /*,$value = null*/) {
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
655
		$argc = func_num_args();
656
		$key = $argc > 2 ? func_get_arg(2) : self::anything();
657
		$value = $argc > 3 ? func_get_arg(3) : self::anything();
658
659
		$suppress = array($key, $value);
660
661
		if (isset($this->overrides[0][$class][$name])) {
662
			$value = $this->overrides[0][$class][$name];
663
664
			if (is_array($value)) {
665
				$this->overrides[0][$class][$name] = self::filter_array_by_suppress_array($value, array($suppress));
666
			}
667
			else {
668
				if (self::check_value_contained_in_suppress_array($value, array($suppress))) {
669
					unset($this->overrides[0][$class][$name]);
670
				}
671
			}
672
		}
673
674
		if (!isset($this->suppresses[0][$class])) $this->suppresses[0][$class] = array();
675
		if (!isset($this->suppresses[0][$class][$name])) $this->suppresses[0][$class][$name] = array();
676
677
		$this->suppresses[0][$class][$name][] = $suppress;
678
679
		$this->cache->clean("__{$class}__{$name}");
0 ignored issues
show
Bug introduced by
The method clean cannot be called on $this->cache (of type array).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
680
	}
681
682
}
683
684
/**
685
 * @package framework
686
 * @subpackage core
687
 * @deprecated 4.0
688
 */
689
class Config_LRU {
690
	const SIZE = 1000;
691
692
	protected $cache;
693
	protected $indexing;
694
695
	protected $i = 0;
696
	protected $c = 0;
697
698
	public function __construct() {
699
		Deprecation::notice('4.0', 'Please use Config_MemCache instead', Deprecation::SCOPE_CLASS);
700
		if (version_compare(PHP_VERSION, '5.3.7', '<') || version_compare(PHP_VERSION, '6.99.99', '>')) {
701
			// SplFixedArray causes seg faults before PHP 5.3.7, and also in 7.0.0-RC1
702
			$this->cache = array();
703
		}
704
		else {
705
			$this->cache = new SplFixedArray(self::SIZE);
706
		}
707
708
		// Pre-fill with stdClass instances. By reusing we avoid object-thrashing
709
		for ($i = 0; $i < self::SIZE; $i++) {
710
			$this->cache[$i] = new stdClass();
711
			$this->cache[$i]->key = null;
712
		}
713
714
		$this->indexing = array();
715
	}
716
717
	public function __clone() {
718
		if (version_compare(PHP_VERSION, '5.3.7', '<')) {
719
			// SplFixedArray causes seg faults before PHP 5.3.7
720
			$cloned = array();
721
		}
722
		else {
723
			$cloned = new SplFixedArray(self::SIZE);
724
		}
725
		for ($i = 0; $i < self::SIZE; $i++) {
726
			$cloned[$i] = clone $this->cache[$i];
727
		}
728
		$this->cache = $cloned;
729
	}
730
731
	public function set($key, $val, $tags = array()) {
732
		// Find an index to set at
733
		$replacing = null;
734
735
		// Target count - not always the lowest, but guaranteed to exist (or hit an empty item)
736
		$target = $this->c - self::SIZE + 1;
737
		$i = $stop = $this->i;
738
739
		do {
740
			if (!($i--)) $i = self::SIZE-1;
741
			$item = $this->cache[$i];
742
743
			if ($item->key === null) { $replacing = null; break; }
744
			else if ($item->c <= $target) { $replacing = $item; break; }
745
		}
746
		while ($i != $stop);
747
748
		if ($replacing) unset($this->indexing[$replacing->key]);
749
750
		$this->indexing[$key] = $this->i = $i;
751
752
		$obj = $this->cache[$i];
753
		$obj->key = $key;
754
		$obj->value = $val;
755
		$obj->tags = $tags;
756
		$obj->c = ++$this->c;
757
	}
758
759
	private $hit = 0;
760
	private $miss = 0;
761
762
	public function stats() {
763
		return $this->miss ? ($this->hit / $this->miss) : 0;
764
	}
765
766
	/**
767
	 * Return a cached value in the case of a hit, false otherwise.
768
	 * For a more robust cache checking, use {@link checkAndGet()}
769
	 *
770
	 * @param  string $key The cache key
771
	 * @return variant     Cached value, if hit. False otherwise
772
	 */
773
	public function get($key) {
774
		list($hit, $result) = $this->checkAndGet($key);
775
		return $hit ? $result : false;
776
	}
777
778
	/**
779
	 * Checks for a cache hit and looks up the value by returning multiple values.
780
	 * Distinguishes a cached 'false' value from a cache miss.
781
	 *
782
	 * @param  string $key The cache key
783
	 * @return array  First element boolean, isHit. Second element the actual result.
784
	 */
785
	public function checkAndGet($key) {
786
		if (array_key_exists($key, $this->indexing)) {
787
			$this->hit++;
788
789
			$res = $this->cache[$this->indexing[$key]];
790
			$res->c = ++$this->c;
791
			return array(true, $res->value);
792
793
		} else {
794
			$this->miss++;
795
			return array(false, null);
796
		}
797
	}
798
799
	public function clean($tag = null) {
800
		if ($tag) {
801
			foreach ($this->cache as $i => $v) {
802
				if ($v->key !== null && in_array($tag, $v->tags)) {
803
					unset($this->indexing[$v->key]);
804
					$this->cache[$i]->key = null;
805
				}
806
			}
807
		}
808
		else {
809
			for ($i = 0; $i < self::SIZE; $i++) $this->cache[$i]->key = null;
810
			$this->indexing = array();
811
		}
812
	}
813
}
814
815
/**
816
 * @package framework
817
 * @subpackage core
818
 */
819
class Config_MemCache {
820
	protected $cache;
821
822
	protected $i = 0;
823
	protected $c = 0;
824
	protected $tags = array();
825
826
	public function __construct() {
827
		$this->cache = array();
828
	}
829
830
	public function set($key, $val, $tags = array()) {
831
		foreach($tags as $t) {
832
			if(!isset($this->tags[$t])) {
833
				$this->tags[$t] = array();
834
			}
835
			$this->tags[$t][$key] = true;
836
		}
837
838
		$this->cache[$key] = array($val, $tags);
839
	}
840
841
	private $hit = 0;
842
	private $miss = 0;
843
844
	public function stats() {
845
		return $this->miss ? ($this->hit / $this->miss) : 0;
846
	}
847
848
	public function get($key) {
849
		list($hit, $result) = $this->checkAndGet($key);
850
		return $hit ? $result : false;
851
	}
852
853
	/**
854
	 * Checks for a cache hit and returns the value as a multi-value return
855
	 * @return array First element boolean, isHit. Second element the actual result.
856
	 */
857
	public function checkAndGet($key) {
858
		if(array_key_exists($key, $this->cache)) {
859
			++$this->hit;
860
			return array(true, $this->cache[$key][0]);
861
		} else {
862
			++$this->miss;
863
			return array(false, null);
864
		}
865
	}
866
867
	public function clean($tag = null) {
868
		if($tag) {
869
			if(isset($this->tags[$tag])) {
870
				foreach($this->tags[$tag] as $k => $dud) {
871
					// Remove the key from everywhere else it is tagged
872
					$ts = $this->cache[$k][1];
873
					foreach($ts as $t) {
874
						unset($this->tags[$t][$k]);
875
					}
876
					unset($this->cache[$k]);
877
				}
878
				unset($this->tags[$tag]);
879
			}
880
		} else {
881
			$this->cache = array();
882
			$this->tags = array();
883
		}
884
	}
885
}
886
887
/**
888
 * @package framework
889
 * @subpackage core
890
 */
891
class Config_ForClass {
892
893
	/**
894
	 * @var string $class
895
	 */
896
	protected $class;
897
898
	/**
899
	 * @param string $class
900
	 */
901
	public function __construct($class) {
902
		$this->class = $class;
903
	}
904
905
	/**
906
	 * @param string $name
907
	 * @return mixed
908
	 */
909
	public function __get($name) {
910
		return Config::inst()->get($this->class, $name);
911
	}
912
913
	/**
914
	 * @param string $name
915
	 * @param mixed $val
916
	 */
917
	public function __set($name, $val) {
918
		return Config::inst()->update($this->class, $name, $val);
919
	}
920
921
	/**
922
	 * @param string $name
923
	 * @return bool
924
	 */
925
	public function __isset($name)
926
	{
927
		$val = $this->__get($name);
928
		return isset($val);
929
	}
930
931
	/**
932
	 * @param string $name
933
	 * @param int $sourceOptions
934
	 *
935
	 * @return array|scalar
936
	 */
937
	public function get($name, $sourceOptions = 0) {
938
		return Config::inst()->get($this->class, $name, $sourceOptions);
939
	}
940
941
	/**
942
	 * @param string
943
	 *
944
	 * @return Config_ForClass
945
	 */
946
	public function forClass($class) {
947
		return Config::inst()->forClass($class);
948
	}
949
}
950