Completed
Pull Request — master (#608)
by Jeroen De
16:30 queued 13:37
created

ReferenceList::serialize()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2
Metric Value
dl 0
loc 3
ccs 0
cts 0
cp 0
rs 10
cc 1
eloc 2
nc 1
nop 0
crap 2
1
<?php
2
3
namespace Wikibase\DataModel;
4
5
use Comparable;
6
use Hashable;
7
use InvalidArgumentException;
8
use SplObjectStorage;
9
use Traversable;
10
use Wikibase\DataModel\Internal\MapValueHasher;
11
use Wikibase\DataModel\Snak\Snak;
12
13
/**
14
 * List of Reference objects.
15
 *
16
 * Note that this implementation is based on SplObjectStorage and
17
 * is not enforcing the type of objects set via it's native methods.
18
 * Therefore one can add non-Reference-implementing objects when
19
 * not sticking to the methods of the References interface.
20
 *
21
 * @since 0.1
22
 * Does not implement References anymore since 2.0
23
 *
24
 * @licence GNU GPL v2+
25
 * @author Jeroen De Dauw < [email protected] >
26
 * @author H. Snater < [email protected] >
27
 * @author Thiemo Mättig
28
 * @author Bene* < [email protected] >
29
 */
30
class ReferenceList extends SplObjectStorage implements Comparable {
31
32
	/**
33
	 * @param Reference[]|Traversable $references
34 29
	 *
35 29
	 * @throws InvalidArgumentException
36 8
	 */
37
	public function __construct( $references = array() ) {
38
		if ( !is_array( $references ) && !( $references instanceof Traversable ) ) {
39 21
			throw new InvalidArgumentException( '$references must be an array or an instance of Traversable' );
40 12
		}
41 4
42
		foreach ( $references as $reference ) {
43
			if ( !( $reference instanceof Reference ) ) {
44 8
				throw new InvalidArgumentException( 'Every element in $references must be an instance of Reference' );
45 17
			}
46 17
47
			$this->addReference( $reference );
48
		}
49
	}
50
51
	/**
52
	 * Adds the provided reference to the list.
53
	 *
54
	 * @since 0.1
55
	 *
56
	 * @param Reference $reference
57
	 * @param int|null $index
58 14
	 *
59 14
	 * @throws InvalidArgumentException
60
	 */
61
	public function addReference( Reference $reference, $index = null ) {
62
		if ( !is_int( $index ) && $index !== null ) {
63 14
			throw new InvalidArgumentException( '$index must be an integer or null' );
64
		}
65 14
66 14
		if ( $index === null || $index >= count( $this ) ) {
67 2
			// Append object to the end of the reference list.
68
			$this->attach( $reference );
69 14
		} else {
70
			$this->insertReferenceAtIndex( $reference, $index );
71
		}
72
	}
73
74
	/**
75
	 * @see SplObjectStorage::attach
76
	 *
77 14
	 * @param Reference $reference
78 14
	 * @param mixed $data Unused in the ReferenceList class.
79 14
	 */
80 14
	public function attach( $reference, $data = null ) {
81 14
		if ( !$reference->isEmpty() ) {
82
			parent::attach( $reference, $data );
83
		}
84
	}
85
86
	/**
87
	 * @since 1.1
88
	 *
89
	 * @param Snak[]|Snak $snaks
90
	 * @param Snak [$snak2,...]
91 6
	 *
92 6
	 * @throws InvalidArgumentException
93 5
	 */
94 5
	public function addNewReference( $snaks = array() /*...*/ ) {
95
		if ( $snaks instanceof Snak ) {
96 6
			$snaks = func_get_args();
97 5
		}
98
99
		$this->addReference( new Reference( $snaks ) );
100
	}
101
102
	/**
103 2
	 * @param Reference $reference
104 2
	 * @param int $index
105 2
	 */
106
	private function insertReferenceAtIndex( Reference $reference, $index ) {
107
		$referencesToShift = array();
108 2
		$i = 0;
109 2
110 2
		// Determine the references that need to be shifted and detach them:
111 2
		foreach ( $this as $object ) {
112 2
			if ( $i++ >= $index ) {
113
				$referencesToShift[] = $object;
114 2
			}
115 2
		}
116 2
117
		foreach ( $referencesToShift as $object ) {
118
			$this->detach( $object );
119 2
		}
120
121 2
		// Attach the new reference and reattach the previously detached references:
122 2
		$this->attach( $reference );
123 2
124 2
		foreach ( $referencesToShift as $object ) {
125
			$this->attach( $object );
126
		}
127
	}
128
129
	/**
130
	 * Returns if the list contains a reference with the same hash as the provided reference.
131
	 *
132
	 * @since 0.1
133
	 *
134
	 * @param Reference $reference
135 6
	 *
136 6
	 * @return boolean
137 6
	 */
138
	public function hasReference( Reference $reference ) {
139
		return $this->contains( $reference )
140
			|| $this->hasReferenceHash( $reference->getHash() );
141
	}
142
143
	/**
144
	 * Returns the index of a reference or false if the reference could not be found.
145
	 *
146
	 * @since 0.5
147
	 *
148
	 * @param Reference $reference
149 2
	 *
150 2
	 * @return int|boolean
151
	 */
152 2
	public function indexOf( Reference $reference ) {
153 1
		$index = 0;
154 1
155
		foreach ( $this as $object ) {
156 1
			if ( $object === $reference ) {
157 2
				return $index;
158
			}
159 2
			$index++;
160
		}
161
162
		return false;
163
	}
164
165
	/**
166
	 * Removes the reference with the same hash as the provided reference if such a reference exists in the list.
167
	 *
168
	 * @since 0.1
169 3
	 *
170 3
	 * @param Reference $reference
171 3
	 */
172
	public function removeReference( Reference $reference ) {
173
		$this->removeReferenceHash( $reference->getHash() );
174
	}
175
176
	/**
177
	 * Returns if the list contains a reference with the provided hash.
178
	 *
179
	 * @since 0.3
180
	 *
181
	 * @param string $referenceHash
182 8
	 *
183 8
	 * @return boolean
184
	 */
185
	public function hasReferenceHash( $referenceHash ) {
186
		return $this->getReference( $referenceHash ) !== null;
187
	}
188
189
	/**
190
	 * Removes the reference with the provided hash if it exists in the list.
191
	 *
192
	 * @since 0.3
193 5
	 *
194 5
	 * @param string $referenceHash	`
195
	 */
196 5
	public function removeReferenceHash( $referenceHash ) {
197 3
		$reference = $this->getReference( $referenceHash );
198 3
199 5
		if ( $reference !== null ) {
200
			$this->detach( $reference );
201
		}
202
	}
203
204
	/**
205
	 * Returns the reference with the provided hash, or null if there is no such reference in the list.
206
	 *
207
	 * @since 0.3
208
	 *
209
	 * @param string $referenceHash
210 14
	 *
211
	 * @return Reference|null
212
	 */
213
	public function getReference( $referenceHash ) {
214 14
		/**
215 10
		 * @var Hashable $hashable
216 10
		 */
217
		foreach ( $this as $hashable ) {
218 9
			if ( $hashable->getHash() === $referenceHash ) {
219
				return $hashable;
220 9
			}
221
		}
222
223
		return null;
224
	}
225
226
	/**
227
	 * @see Serializable::serialize
228
	 *
229
	 * @since 2.1
230 3
	 *
231 3
	 * @return string
232
	 */
233
	public function serialize() {
234
		return serialize( iterator_to_array( $this ) );
235
	}
236
237
	/**
238
	 * @see Serializable::unserialize
239
	 *
240
	 * @since 2.1
241 3
	 *
242 3
	 * @param string $data
243 3
	 */
244
	public function unserialize( $data ) {
245
		$this->__construct( unserialize( $data ) );
246
	}
247
248
	/**
249
	 * @since 4.4
250 3
	 *
251 3
	 * @return bool
252
	 */
253
	public function isEmpty() {
254
		return $this->count() === 0;
255
	}
256
257
	/**
258
	 * The hash is purely valuer based. Order of the elements in the array is not held into account.
259
	 *
260
	 * @since 0.3
261
	 *
262
	 * @return string
263
	 */
264
	public function getValueHash() {
265
		$hasher = new MapValueHasher();
266
		return $hasher->hash( $this );
267
	}
268
269
	/**
270
	 * @see Comparable::equals
271
	 *
272
	 * The comparison is done purely value based, ignoring the order of the elements in the array.
273
	 *
274
	 * @since 0.3
275
	 *
276
	 * @param mixed $target
277
	 *
278
	 * @return bool
279
	 */
280
	public function equals( $target ) {
281
		if ( $this === $target ) {
282
			return true;
283
		}
284
285
		return $target instanceof self
286
		       && $this->getValueHash() === $target->getValueHash();
287
	}
288
289
}
290