Completed
Push — member-groupset-delete ( a90a9a )
by Loz
11:22
created

Config_MemCache::checkAndGet()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

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