Passed
Push — 4 ( ac5c34...1a634f )
by Daniel
09:26
created

UnsavedRelationList::columnUnique()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 1
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace SilverStripe\ORM;
4
5
use InvalidArgumentException;
6
use ArrayIterator;
7
use SilverStripe\ORM\FieldType\DBField;
8
9
/**
10
 * An {@link ArrayList} that represents an unsaved relation.
11
 *
12
 * has_many and many_many relations cannot be saved until after the DataObject
13
 * they're on has been written. This List pretends to be a RelationList and
14
 * stores the related objects in memory.
15
 *
16
 * It can store both saved objects (as IDs) or unsaved objects (as instances
17
 * of $dataClass). Unsaved objects are then written when the list is saved
18
 * into an instance of {@link RelationList}.
19
 */
20
class UnsavedRelationList extends ArrayList implements Relation
21
{
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 $baseClass
55
     * @param string $relationName
56
     * @param string $dataClass The DataObject class used in the relation
57
     */
58
    public function __construct($baseClass, $relationName, $dataClass)
59
    {
60
        $this->baseClass = $baseClass;
61
        $this->relationName = $relationName;
62
        $this->dataClass = $dataClass;
63
        parent::__construct();
64
    }
65
66
    /**
67
     * Add an item to this relationship
68
     *
69
     * @param mixed $item
70
     * @param array $extraFields A map of additional columns to insert into the joinTable in the case of a many_many relation
71
     */
72
    public function add($item, $extraFields = null)
73
    {
74
        $this->push($item, $extraFields);
75
    }
76
77
    /**
78
     * Save all the items in this list into the RelationList
79
     *
80
     * @param RelationList $list
81
     */
82
    public function changeToList(RelationList $list)
83
    {
84
        foreach ($this->items as $key => $item) {
85
            $list->add($item, $this->extraFields[$key]);
0 ignored issues
show
Unused Code introduced by
The call to SilverStripe\ORM\DataList::add() has too many arguments starting with $this->extraFields[$key]. ( Ignorable by Annotation )

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

85
            $list->/** @scrutinizer ignore-call */ 
86
                   add($item, $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. Please note the @ignore annotation hint above.

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