Passed
Push — master ( 30451f...036a09 )
by Fabio
07:34 queued 02:23
created

TWeakList::ensureDiscardInvalid()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 2
nc 2
nop 0
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * TWeakList class
4
 *
5
 * @author Brad Anderson <[email protected]>
6
 * @link https://github.com/pradosoft/prado
7
 * @license https://github.com/pradosoft/prado/blob/master/LICENSE
8
 */
9
10
namespace Prado\Collections;
11
12
use Prado\Exceptions\TInvalidOperationException;
13
use Prado\Exceptions\TInvalidDataTypeException;
14
use Prado\Exceptions\TInvalidDataValueException;
15
use Prado\Prado;
16
use Prado\TEventHandler;
17
use Prado\TPropertyValue;
18
19
use ArrayAccess;
20
use Closure;
21
use Traversable;
22
use WeakReference;
23
24
/**
25
 * TWeakList class
26
 *
27
 * TWeakList implements an integer-indexed collection class with objects kept as
28
 * WeakReference.  Closure are treated as function PHP types rather than as objects.
29
 *
30
 * Objects in the TWeakList are encoded into WeakReference when saved, and the objects
31
 * restored on retrieval.  When an object becomes unset in the application/system
32
 * and its WeakReference invalidated, it can be removed from the TWeakList or have
33
 * a null in place of the object, depending on the mode.  The mode can be set during
34
 * {@link __construct Construct}.  The default mode of the TWeakList is to maintain
35
 * a list of only valid objects -where the count and item locations can change when
36
 * an item is invalidated-.  The other mode is to retain the place of invalidated
37
 * objects and replace the object with null -maintaining the count and item locations-.
38
 *
39
 * List items do not need to be objects.  TWeakList is similar to TList except list
40
 * items that are objects (except Closure and IWeakRetainable) are stored as WeakReference.
41
 * List items that are arrays are recursively traversed for replacement of objects
42
 * with WeakReference before storing.  In this way, TWeakList will not retain objects
43
 * (incrementing their use/reference counter) that it contains.  Only primary list
44
 * items are tracked with the WeakMap, and objects in arrays has no effect on the whole.
45
 * If an object in an array is invalidated, it well be replaced by "null".  Arrays
46
 * in the TWeakList are kept regardless of the use/reference count of contained objects.
47
 *
48
 * When searching by a {@link TEventHandler} object, it will only find itself and
49
 * will not match on its {@link TEventHandler::getHandler}.  However, if searching
50
 * for a callable handler, it will first match direct callable handlers in the list,
51
 * and then search for matching TEventHandlers' Handler regardless of the data.
52
 *
53
 * {@link TWeakCollectionTrait} implements a PHP 8 WeakMap used to track any changes
54
 * in WeakReference objects in the TWeakList and optionally scrubs the list of invalid
55
 * objects on any changes to the WeakMap.
56
 *
57
 * Note that any objects or objects in arrays will be lost if they are not otherwise
58
 * retained in other parts of the application.  The only exception is a PHP Closure.
59
 * Closures are stored without WeakReference so anonymous functions can be stored
60
 * without risk of deletion if it is the only reference.  Closures act similarly to
61
 * a PHP data type rather than an object.
62
 *
63
 * @author Brad Anderson <[email protected]>
64
 * @since 4.2.3
65
 */
66
class TWeakList extends TList
67
{
68
	use TWeakCollectionTrait;
69
70
	/** @var ?bool Should invalid WeakReference automatically be deleted from the list.
71
	 *    Default True.
72
	 */
73
	private ?bool $_discardInvalid = null;
74
75
	/** @var int The number of TEventHandlers in the list */
76
	private int $_eventHandlerCount = 0;
77
78
	/**
79
	 * Constructor.
80
	 * Initializes the weak list with an array or an iterable object.
81
	 * @param null|array|\Iterator $data The initial data. Default is null, meaning no initialization.
82
	 * @param ?bool $readOnly Whether the list is read-only. Default is null.
83
	 * @param ?bool $discardInvalid Whether the list is scrubbed of invalid WeakReferences.
84
	 *   Default is null for the opposite of $readOnly.  Thus, Read Only lists retain
85
	 *   invalid WeakReference; and Mutable lists scrub invalid WeakReferences.
86
	 * @throws TInvalidDataTypeException If data is not null and neither an array nor an iterator.
87
	 */
88
	public function __construct($data = null, $readOnly = null, $discardInvalid = null)
89
	{
90
		parent::__construct($data, $readOnly);
91
		$this->setDiscardInvalid($discardInvalid);
92
	}
93
94
	/**
95
	 * Cloning a TWeakList requires cloning the WeakMap
96
	 */
97
	public function __clone()
98
	{
99
		$this->weakClone();
100
		parent::__clone();
101
	}
102
103
	/**
104
	 * Waking up a TWeakList requires creating the WeakMap.  No items are saved in
105
	 * TWeakList so only initialization of the WeakMap is required.
106
	 */
107
	public function __wakeup()
108
	{
109
		if ($this->_discardInvalid) {
110
			$this->weakStart();
111
		}
112
		parent::__wakeup();
113
	}
114
115
	/**
116
	 * This is a custom function for adding objects to the weak map.  Specifically,
117
	 * if the object being added is a TEventHandler, we use the {@link TEventHandler::getHandlerObject}
118
	 * object instead of the TEventHandler itself.
119
	 * @param object $object The object to add to the managed weak map.
120
	 * @since 4.2.3
121
	 */
122
	protected function weakCustomAdd(object $object)
123
	{
124
		if($object instanceof TEventHandler) {
125
			$object = $object->getHandlerObject();
126
			$this->_eventHandlerCount++;
127
		}
128
		return $this->weakAdd($object);
0 ignored issues
show
Bug introduced by
It seems like $object can also be of type null; however, parameter $object of Prado\Collections\TWeakList::weakAdd() does only seem to accept object, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

128
		return $this->weakAdd(/** @scrutinizer ignore-type */ $object);
Loading history...
129
	}
130
131
	/**
132
	 * This is a custom function for removing objects to the weak map.  Specifically,
133
	 * if the object being removed is a TEventHandler, we use the {@link TEventHandler::getHandlerObject}
134
	 * object instead of the TEventHandler itself.
135
	 * @param object $object The object to remove to the managed weak map.
136
	 * @since 4.2.3
137
	 */
138
	protected function weakCustomRemove(object $object)
139
	{
140
		if($object instanceof TEventHandler) {
141
			$object = $object->getHandlerObject();
142
			$this->_eventHandlerCount--;
143
		}
144
		return $this->weakRemove($object);
0 ignored issues
show
Bug introduced by
It seems like $object can also be of type null; however, parameter $object of Prado\Collections\TWeakList::weakRemove() does only seem to accept object, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

144
		return $this->weakRemove(/** @scrutinizer ignore-type */ $object);
Loading history...
145
	}
146
147
	/**
148
	 * Converts the $item callable that has WeakReference rather than the actual object
149
	 * back into a regular callable.
150
	 * @param mixed &$item
151
	 */
152
	protected function filterItemForOutput(&$item): void
153
	{
154
		if (is_array($item) || ($item instanceof Traversable && $item instanceof ArrayAccess)) {
155
			foreach (array_keys($item) as $key) {
0 ignored issues
show
Bug introduced by
It seems like $item can also be of type Traversable&ArrayAccess; however, parameter $array of array_keys() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

155
			foreach (array_keys(/** @scrutinizer ignore-type */ $item) as $key) {
Loading history...
156
				$this->filterItemForOutput($item[$key]);
157
			}
158
		} elseif (is_object($item)) {
159
			if($item instanceof WeakReference) {
160
				$item = $item->get();
161
			} elseif (($item instanceof TEventHandler) && !$item->hasHandler()) {
162
				$item = null;
163
			}
164
		}
165
	}
166
167
	/**
168
	 * Converts the $item object and objects in an array into their WeakReference version
169
	 * for storage.  Closure[s] are not converted into WeakReference and so act like a
170
	 * basic PHP type.  Closures are added to the the WeakMap cache but has no weak
171
	 * effect because the TWeakList maintains references to Closure[s] preventing their
172
	 * invalidation.
173
	 * @param mixed &$item object to convert into a WeakReference where needed.
174
	 */
175
	protected function filterItemForInput(&$item): void
176
	{
177
		if (is_array($item) || ($item instanceof Traversable && $item instanceof ArrayAccess)) {
178
			foreach (array_keys($item) as $key) {
0 ignored issues
show
Bug introduced by
It seems like $item can also be of type Traversable&ArrayAccess; however, parameter $array of array_keys() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

178
			foreach (array_keys(/** @scrutinizer ignore-type */ $item) as $key) {
Loading history...
179
				$this->filterItemForInput($item[$key]);
180
			}
181
		} elseif (is_object($item) && !($item instanceof Closure) && !($item instanceof IWeakRetainable)) {
182
			$item = WeakReference::create($item);
183
		}
184
	}
185
186
	/**
187
	 * When a change in the WeakMap is detected, scrub the list of invalid WeakReference.
188
	 */
189
	protected function scrubWeakReferences(): void
190
	{
191
		if (!$this->getDiscardInvalid() || !$this->weakChanged()) {
192
			return;
193
		}
194
		for ($i = $this->_c - 1; $i >= 0; $i--) {
195
			if (is_object($this->_d[$i])) {
196
				$object = $this->_d[$i];
197
				if ($isEventHandler = ($object instanceof TEventHandler)) {
198
					$object = $object->getHandlerObject(true);
199
				}
200
				if(($object instanceof WeakReference) && $object->get() === null) {
201
					$this->_c--;
202
					if ($i === $this->_c) {
203
						array_pop($this->_d);
204
					} else {
205
						array_splice($this->_d, $i, 1);
206
					}
207
					if ($isEventHandler) {
208
						$this->_eventHandlerCount--;
209
					}
210
				}
211
			}
212
		}
213
		$this->weakResetCount();
214
	}
215
216
	/**
217
	 * @return bool Does the TWeakList scrub invalid WeakReference.
218
	 */
219
	public function getDiscardInvalid(): bool
220
	{
221
		$this->collapseDiscardInvalid();
222
		return $this->_discardInvalid;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->_discardInvalid could return the type null which is incompatible with the type-hinted return boolean. Consider adding an additional type-check to rule them out.
Loading history...
223
	}
224
225
	/**
226
	 * Ensures that DiscardInvalid is set.
227
	 */
228
	protected function collapseDiscardInvalid()
229
	{
230
		if ($this->_discardInvalid === null) {
231
			$this->setDiscardInvalid(!$this->getReadOnly());
232
		}
233
	}
234
235
	/**
236
	 * @param bool $value Sets the TWeakList scrubbing of invalid WeakReference.
237
	 */
238
	public function setDiscardInvalid($value): void
239
	{
240
		if($value === $this->_discardInvalid) {
241
			return;
242
		}
243
		if ($this->_discardInvalid !== null && !Prado::isCallingSelf()) {
244
			throw new TInvalidOperationException('weak_no_set_discard_invalid', $this::class);
245
		}
246
		$value = TPropertyValue::ensureBoolean($value);
247
		if ($value && !$this->_discardInvalid) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->_discardInvalid of type boolean|null is loosely compared to false; this is ambiguous if the boolean can be false. You might want to explicitly use !== null instead.

If an expression can have both false, and null as possible values. It is generally a good practice to always use strict comparison to clearly distinguish between those two values.

$a = canBeFalseAndNull();

// Instead of
if ( ! $a) { }

// Better use one of the explicit versions:
if ($a !== null) { }
if ($a !== false) { }
if ($a !== null && $a !== false) { }
Loading history...
248
			$this->weakStart();
249
			for ($i = $this->_c - 1; $i >= 0; $i--) {
250
				if (is_object($this->_d[$i])) {
251
					$object = $this->_d[$i];
252
					if ($isEventHandler = ($object instanceof TEventHandler)) {
253
						$object = $object->getHandlerObject(true);
254
					}
255
					if ($object instanceof WeakReference) {
256
						$object = $object->get();
257
					}
258
					if ($object === null) {
259
						$this->_c--;	//on read only, parent::removeAt won't remove for scrub.
260
						if ($i === $this->_c) {
261
							array_pop($this->_d);
262
						} else {
263
							array_splice($this->_d, $i, 1);
264
						}
265
						if ($isEventHandler) {
266
							$this->_eventHandlerCount--;
267
						}
268
					} else {
269
						$this->weakAdd($object);
270
					}
271
				}
272
			}
273
		} elseif (!$value && $this->_discardInvalid) {
274
			$this->weakStop();
275
		}
276
		$this->_discardInvalid = $value;
277
	}
278
279
	/**
280
	 * Returns an iterator for traversing the items in the list.
281
	 * This method is required by the interface \IteratorAggregate.
282
	 * All invalid WeakReference[s] are optionally removed from the iterated list.
283
	 * @return \Iterator an iterator for traversing the items in the list.
284
	 */
285
	public function getIterator(): \Iterator
286
	{
287
		return new \ArrayIterator($this->toArray());
288
	}
289
290
	/**
291
	 * All invalid WeakReference[s] are optionally removed from the list before counting.
292
	 * @return int the number of items in the list
293
	 */
294
	public function getCount(): int
295
	{
296
		$this->scrubWeakReferences();
297
		return parent::getCount();
298
	}
299
300
	/**
301
	 * Returns the item at the specified offset.
302
	 * This method is exactly the same as {@link offsetGet}.
303
	 * All invalid WeakReference[s] are optionally removed from the list before indexing.
304
	 * @param int $index the index of the item
305
	 * @throws TInvalidDataValueException if the index is out of the range
306
	 * @return mixed the item at the index
307
	 */
308
	public function itemAt($index)
309
	{
310
		$this->scrubWeakReferences();
311
		$item = parent::itemAt($index);
312
		$this->filterItemForOutput($item);
313
		return $item;
314
	}
315
316
	/**
317
	 * Appends an item at the end of the list.
318
	 * All invalid WeakReference[s] are optionally removed from the list before adding
319
	 * for proper indexing.
320
	 * @param mixed $item new item
321
	 * @throws TInvalidOperationException if the list is read-only
322
	 * @return int the zero-based index at which the item is added
323
	 */
324
	public function add($item)
325
	{
326
		$this->collapseDiscardInvalid();
327
		$this->scrubWeakReferences();
328
		if (is_object($item)) {
329
			$this->weakCustomAdd($item);
330
		}
331
		$this->filterItemForInput($item);
332
		parent::insertAt($this->_c, $item);
333
		return $this->_c - 1;
334
	}
335
336
	/**
337
	 * Inserts an item at the specified position.
338
	 * Original item at the position and the next items
339
	 * will be moved one step towards the end.
340
	 * All invalid WeakReference[s] are optionally removed from the list before indexing.
341
	 * @param int $index the specified position.
342
	 * @param mixed $item new item
343
	 * @throws TInvalidDataValueException If the index specified exceeds the bound
344
	 * @throws TInvalidOperationException if the list is read-only
345
	 */
346
	public function insertAt($index, $item)
347
	{
348
		$this->collapseDiscardInvalid();
349
		$this->scrubWeakReferences();
350
		if (is_object($item)) {
351
			$this->weakCustomAdd($item);
352
		}
353
		$this->filterItemForInput($item);
354
		parent::insertAt($index, $item);
355
	}
356
357
	/**
358
	 * Removes an item from the list.
359
	 * The list will first search for the item.
360
	 * The first item found will be removed from the list.
361
	 * All invalid WeakReference[s] are optionally removed from the list before indexing.
362
	 * @param mixed $item the item to be removed.
363
	 * @throws TInvalidDataValueException If the item does not exist
364
	 * @throws TInvalidOperationException if the list is read-only
365
	 * @return int the index at which the item is being removed
366
	 */
367
	public function remove($item)
368
	{
369
		if (!$this->getReadOnly()) {
370
			if (($index = $this->indexOf($item)) !== -1) {
371
				if (is_object($item)) {
372
					$this->weakCustomRemove($item);
373
				}
374
				parent::removeAt($index);
375
				return $index;
376
			} else {
377
				throw new TInvalidDataValueException('list_item_inexistent');
378
			}
379
		} else {
380
			throw new TInvalidOperationException('list_readonly', get_class($this));
381
		}
382
	}
383
384
	/**
385
	 * Removes an item at the specified position.
386
	 * All invalid WeakReference[s] are optionally removed from the list before indexing.
387
	 * @param int $index the index of the item to be removed.
388
	 * @throws TInvalidDataValueException If the index specified exceeds the bound
389
	 * @throws TInvalidOperationException if the list is read-only
390
	 * @return mixed the removed item.
391
	 */
392
	public function removeAt($index)
393
	{
394
		$this->scrubWeakReferences();
395
		$item = parent::removeAt($index);
396
		$this->filterItemForOutput($item);
397
		if (is_object($item)) {
398
			$this->weakCustomRemove($item);
399
		}
400
		return $item;
401
	}
402
403
	/**
404
	 * Removes all items in the list and resets the Weak Cache.
405
	 * @throws TInvalidOperationException if the list is read-only
406
	 */
407
	public function clear(): void
408
	{
409
		$c = $this->_c;
410
		for ($i = $this->_c - 1; $i >= 0; --$i) {
411
			parent::removeAt($i);
412
		}
413
		if ($c) {
414
			$this->weakRestart();
415
		}
416
	}
417
418
	/**
419
	 * @param mixed $item the item
420
	 * @return bool whether the list contains the item
421
	 */
422
	public function contains($item): bool
423
	{
424
		return $this->indexOf($item) !== -1;
425
	}
426
427
	/**
428
	 * All invalid WeakReference[s] are optionally removed from the list before indexing.
429
	 * @param mixed $item the item
430
	 * @return int the index of the item in the list (0 based), -1 if not found.
431
	 */
432
	public function indexOf($item)
433
	{
434
		$this->scrubWeakReferences();
435
		$this->filterItemForInput($item);
436
		if (($index = parent::indexOf($item)) === -1 && $this->_eventHandlerCount) {
437
			$index = false;
438
			foreach($this->_d as $index => $dItem) {
439
				if (($dItem instanceof TEventHandler) && $dItem->isSameHandler($item, true)) {
440
					break;
441
				}
442
				$index = false;
443
			}
444
		}
445
		if ($index === false) {
446
			return -1;
447
		} else {
448
			return $index;
449
		}
450
	}
451
452
	/**
453
	 * Finds the base item.  If found, the item is inserted before it.
454
	 * All invalid WeakReference[s] are optionally removed from the list before indexing.
455
	 * @param mixed $baseitem the base item which will be pushed back by the second parameter
456
	 * @param mixed $item the item
457
	 * @throws TInvalidDataValueException if the base item is not within this list
458
	 * @throws TInvalidOperationException if the list is read-only
459
	 * @return int the index where the item is inserted
460
	 */
461
	public function insertBefore($baseitem, $item)
462
	{
463
		if (!$this->getReadOnly()) {
464
			if (($index = $this->indexOf($baseitem)) === -1) {
465
				throw new TInvalidDataValueException('list_item_inexistent');
466
			}
467
			if (is_object($item)) {
468
				$this->weakCustomAdd($item);
469
			}
470
			$this->filterItemForInput($item);
471
			parent::insertAt($index, $item);
472
			return $index;
473
		} else {
474
			throw new TInvalidOperationException('list_readonly', get_class($this));
475
		}
476
	}
477
478
	/**
479
	 * Finds the base item.  If found, the item is inserted after it.
480
	 * All invalid WeakReference[s] are optionally removed from the list before indexing.
481
	 * @param mixed $baseitem the base item which comes before the second parameter when added to the list
482
	 * @param mixed $item the item
483
	 * @throws TInvalidDataValueException if the base item is not within this list
484
	 * @throws TInvalidOperationException if the list is read-only
485
	 * @return int the index where the item is inserted
486
	 */
487
	public function insertAfter($baseitem, $item)
488
	{
489
		if (!$this->getReadOnly()) {
490
			if (($index = $this->indexOf($baseitem)) === -1) {
491
				throw new TInvalidDataValueException('list_item_inexistent');
492
			}
493
			if (is_object($item)) {
494
				$this->weakCustomAdd($item);
495
			}
496
			$this->filterItemForInput($item);
497
			parent::insertAt($index + 1, $item);
498
			return $index + 1;
499
		} else {
500
			throw new TInvalidOperationException('list_readonly', get_class($this));
501
		}
502
	}
503
504
	/**
505
	 * All invalid WeakReference[s] are optionally removed from the list.
506
	 * @return array the list of items in array
507
	 */
508
	public function toArray(): array
509
	{
510
		$this->scrubWeakReferences();
511
		$items = $this->_d;
512
		$this->filterItemForOutput($items);
513
		return $items;
514
	}
515
516
	/**
517
	 * Copies iterable data into the list.
518
	 * Note, existing data in the list will be cleared first.
519
	 * @param mixed $data the data to be copied from, must be an array or object implementing Traversable
520
	 * @throws TInvalidDataTypeException If data is neither an array nor a Traversable.
521
	 */
522
	public function copyFrom($data): void
523
	{
524
		if (is_array($data) || ($data instanceof Traversable)) {
525
			if ($this->_c > 0) {
526
				$this->clear();
527
			}
528
			foreach ($data as $item) {
529
				if (is_object($item)) {
530
					$this->weakCustomAdd($item);
531
				}
532
				$this->filterItemForInput($item);
533
				parent::insertAt($this->_c, $item);
534
			}
535
		} elseif ($data !== null) {
536
			throw new TInvalidDataTypeException('list_data_not_iterable');
537
		}
538
	}
539
540
	/**
541
	 * Merges iterable data into the map.
542
	 * New data will be appended to the end of the existing data.
543
	 * @param mixed $data the data to be merged with, must be an array or object implementing Traversable
544
	 * @throws TInvalidDataTypeException If data is neither an array nor an iterator.
545
	 */
546
	public function mergeWith($data): void
547
	{
548
		if (is_array($data) || ($data instanceof Traversable)) {
549
			foreach ($data as $item) {
550
				if (is_object($item)) {
551
					$this->weakCustomAdd($item);
552
				}
553
				$this->filterItemForInput($item);
554
				parent::insertAt($this->_c, $item);
555
			}
556
		} elseif ($data !== null) {
557
			throw new TInvalidDataTypeException('list_data_not_iterable');
558
		}
559
	}
560
561
	/**
562
	 * Returns whether there is an item at the specified offset.
563
	 * This method is required by the interface \ArrayAccess.
564
	 * All invalid WeakReference[s] are optionally removed from the list before indexing.
565
	 * @param int $offset the offset to check on
566
	 * @return bool
567
	 */
568
	public function offsetExists($offset): bool
569
	{
570
		return ($offset >= 0 && $offset < $this->getCount());
571
	}
572
573
	/**
574
	 * Sets the item at the specified offset.
575
	 * This method is required by the interface \ArrayAccess.
576
	 * All invalid WeakReference[s] are optionally removed from the list before indexing.
577
	 * @param int $offset the offset to set item
578
	 * @param mixed $item the item value
579
	 */
580
	public function offsetSet($offset, $item): void
581
	{
582
		$this->scrubWeakReferences();
583
		if ($offset === null || $offset === $this->_c) {
584
			if (is_object($item)) {
585
				$this->weakCustomAdd($item);
586
			}
587
			$this->filterItemForInput($item);
588
			parent::insertAt($this->_c, $item);
589
		} else {
590
			$removed = parent::removeAt($offset);
591
			$this->filterItemForOutput($removed);
592
			if (is_object($removed)) {
593
				$this->weakCustomRemove($removed);
594
			}
595
			if (is_object($item)) {
596
				$this->weakCustomAdd($item);
597
			}
598
			$this->filterItemForInput($item);
599
			parent::insertAt($offset, $item);
600
		}
601
	}
602
603
	/**
604
	 * Returns an array with the names of all variables of this object that should
605
	 * NOT be serialized because their value is the default one or useless to be cached
606
	 * for the next page loads.  Reimplement in derived classes to add new variables,
607
	 * but remember to  also to call the parent implementation first.
608
	 * Due to being weak, the TWeakList is not serialized.  The count is artificially
609
	 * made zero so the parent has no values to save.
610
	 * @param array $exprops by reference
611
	 */
612
	protected function _getZappableSleepProps(&$exprops)
613
	{
614
		$c = $this->_c;
615
		$this->_c = 0;
616
		parent::_getZappableSleepProps($exprops);
617
		$this->_c = $c;
618
619
		$this->_weakZappableSleepProps($exprops);
620
		if ($this->_discardInvalid === null) {
621
			$exprops[] = "\0" . __CLASS__ . "\0_discardInvalid";
622
		}
623
	}
624
}
625