Completed
Pull Request — master (#5247)
by Damian
11:17
created

UnsavedRelationList::changeToList()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 8
rs 9.4285
cc 3
eloc 5
nc 3
nop 1
1
<?php
2
3
use SilverStripe\Model\Relation;
4
5
/**
6
 * An {@link ArrayList} that represents an unsaved relation.
7
 *
8
 * has_many and many_many relations cannot be saved until after the DataObject
9
 * they're on has been written. This List pretends to be a RelationList and
10
 * stores the related objects in memory.
11
 *
12
 * It can store both saved objects (as IDs) or unsaved objects (as instances
13
 * of $dataClass). Unsaved objects are then written when the list is saved
14
 * into an instance of {@link RelationList}.
15
 *
16
 * Most methods that alter the list of objects throw LogicExceptions.
17
 *
18
 * @package framework
19
 * @subpackage model
20
 */
21
class UnsavedRelationList extends ArrayList implements Relation {
22
23
	/**
24
	 * The DataObject class name that this relation is on
25
	 *
26
	 * @var string
27
	 */
28
	protected $baseClass;
29
30
	/**
31
	 * The name of the relation
32
	 *
33
	 * @var string
34
	 */
35
	protected $relationName;
36
37
	/**
38
	 * The DataObject class name that this relation is querying
39
	 *
40
	 * @var string
41
	 */
42
	protected $dataClass;
43
44
	/**
45
	 * The extra fields associated with the relation
46
	 *
47
	 * @var array
48
	 */
49
	protected $extraFields = array();
50
51
	/**
52
	 * Create a new UnsavedRelationList
53
	 *
54
	 * @param string $dataClass The DataObject class used in the relation
55
	 */
56
	public function __construct($baseClass, $relationName, $dataClass) {
57
		$this->baseClass = $baseClass;
58
		$this->relationName = $relationName;
59
		$this->dataClass = $dataClass;
60
		parent::__construct();
61
	}
62
63
	/**
64
	 * Add an item to this relationship
65
	 *
66
	 * @param $extraFields A map of additional columns to insert into the joinTable in the case of a many_many relation
67
	 */
68
	public function add($item, $extraFields = null) {
69
		$this->push($item, $extraFields);
70
	}
71
72
	/**
73
	 * Save all the items in this list into the RelationList
74
	 *
75
	 * @param RelationList $list
76
	 */
77
	public function changeToList(RelationList $list) {
78
		foreach($this->items as $key => $item) {
79
			if(is_object($item)) {
80
				$item->write();
81
			}
82
			$list->add($item, $this->extraFields[$key]);
0 ignored issues
show
Unused Code introduced by
The call to RelationList::add() has too many arguments starting with $this->extraFields[$key].

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
83
		}
84
	}
85
86
	/**
87
	 * Pushes an item onto the end of this list.
88
	 *
89
	 * @param array|object $item
90
	 */
91
	public function push($item, $extraFields = null) {
92
		if((is_object($item) && !$item instanceof $this->dataClass)
93
			|| (!is_object($item) && !is_numeric($item))) {
94
			throw new InvalidArgumentException(
95
				"UnsavedRelationList::add() expecting a $this->dataClass object, or ID value",
96
				E_USER_ERROR);
97
		}
98
		if(is_object($item) && $item->ID) {
99
			$item = $item->ID;
100
		}
101
		$this->extraFields[] = $extraFields;
102
		parent::push($item);
103
	}
104
105
	/**
106
	 * Get the dataClass name for this relation, ie the DataObject ClassName
107
	 *
108
	 * @return string
109
	 */
110
	public function dataClass() {
111
		return $this->dataClass;
112
	}
113
114
	/**
115
	 * Returns an Iterator for this relation.
116
	 *
117
	 * @return ArrayIterator
118
	 */
119
	public function getIterator() {
120
		return new ArrayIterator($this->toArray());
121
	}
122
123
	/**
124
	 * Return an array of the actual items that this relation contains at this stage.
125
	 * This is when the query is actually executed.
126
	 *
127
	 * @return array
128
	 */
129
	public function toArray() {
130
		$items = array();
131
		foreach($this->items as $key => $item) {
132
			if(is_numeric($item)) {
133
				$item = DataObject::get_by_id($this->dataClass, $item);
134
			}
135
			if(!empty($this->extraFields[$key])) {
136
				$item->update($this->extraFields[$key]);
137
			}
138
			$items[] = $item;
139
		}
140
		return $items;
141
	}
142
143
	/**
144
	 * Add a number of items to the relation.
145
	 *
146
	 * @param array $items Items to add, as either DataObjects or IDs.
147
	 * @return DataList
148
	 */
149
	public function addMany($items) {
150
		foreach($items as $item) {
151
			$this->add($item);
152
		}
153
		return $this;
154
	}
155
156
	/**
157
     * Remove all items from this relation.
158
     */
159
	public function removeAll() {
160
		$this->items = array();
161
		$this->extraFields = array();
162
	}
163
164
	/**
165
	 * Remove the items from this list with the given IDs
166
	 *
167
	 * @param array $items
168
	 */
169
	public function removeMany($items) {
170
		$this->items = array_diff($this->items, $items);
171
		return $this;
172
	}
173
174
	/**
175
	 * Removes items from this list which are equal.
176
	 *
177
	 * @param string $field unused
178
	 */
179
	public function removeDuplicates($field = 'ID') {
180
		$this->items = array_unique($this->items);
181
	}
182
183
	/**
184
	 * Sets the Relation to be the given ID list.
185
	 * Records will be added and deleted as appropriate.
186
	 *
187
	 * @param array $idList List of IDs.
188
	 */
189
	public function setByIDList($idList) {
190
		$this->removeAll();
191
		$this->addMany($idList);
192
	}
193
194
	/**
195
	 * Returns an array with both the keys and values set to the IDs of the records in this list.
196
	 * Does not respect sort order. Use ->column("ID") to get an ID list with the current sort.
197
	 * Does not return the IDs for unsaved DataObjects.
198
	 *
199
	 * @return array
200
	 */
201
	public function getIDList() {
202
		// Get a list of IDs of our current items - if it's not a number then object then assume it's a DO.
203
		$ids = array_map(function($obj) {
204
			return is_numeric($obj) ? $obj : $obj->ID;
205
		}, $this->items);
206
207
		// Strip out duplicates and anything resolving to False.
208
		$ids = array_filter(array_unique($ids));
209
210
		// Change the array from (1, 2, 3) to (1 => 1, 2 => 2, 3 => 3)
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...
211
		if ($ids) $ids = array_combine($ids, $ids);
0 ignored issues
show
Bug Best Practice introduced by
The expression $ids 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...
212
213
		return $ids;
214
	}
215
216
	/**
217
	 * Returns the first item in the list
218
	 *
219
	 * @return mixed
220
	 */
221
	public function first() {
222
		$item = reset($this->items);
223
		if(is_numeric($item)) {
224
			$item = DataObject::get_by_id($this->dataClass, $item);
225
		}
226
		if(!empty($this->extraFields[key($this->items)])) {
227
			$item->update($this->extraFields[key($this->items)]);
228
		}
229
		return $item;
230
	}
231
232
	/**
233
	 * Returns the last item in the list
234
	 *
235
	 * @return mixed
236
	 */
237
	public function last() {
238
		$item = end($this->items);
239
		if(!empty($this->extraFields[key($this->items)])) {
240
			$item->update($this->extraFields[key($this->items)]);
241
		}
242
		return $item;
243
	}
244
245
	/**
246
	 * Returns an array of a single field value for all items in the list.
247
	 *
248
	 * @param string $colName
249
	 * @return array
250
	 */
251
	public function column($colName = 'ID') {
252
		$list = new ArrayList($this->toArray());
253
		return $list->column($colName);
254
	}
255
256
	/**
257
	 * Returns a copy of this list with the relationship linked to the given foreign ID.
258
	 * @param $id An ID or an array of IDs.
259
	 */
260
	public function forForeignID($id) {
261
		$class = singleton($this->baseClass);
262
		$class->ID = 1;
263
		return $class->{$this->relationName}()->forForeignID($id);
264
	}
265
266
	/**
267
	 * Return the DBField object that represents the given field on the related class.
268
	 *
269
	 * @param string $fieldName Name of the field
270
	 * @return DBField The field as a DBField object
271
	 */
272
	public function dbObject($fieldName) {
273
		return singleton($this->dataClass)->dbObject($fieldName);
274
	}
275
276
	protected function extractValue($item, $key) {
277
		if(is_numeric($item)) {
278
			$item = DataObject::get_by_id($this->dataClass, $item);
279
		}
280
		return parent::extractValue($item, $key);
281
	}
282
}
283