Passed
Pull Request — 4 (#10222)
by Steve
07:01
created

UnsavedRelationList::dataClass()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
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 = [];
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
        ) {
100
            throw new InvalidArgumentException(
101
                "UnsavedRelationList::add() expecting a $this->dataClass object, or ID value"
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
    #[\ReturnTypeWillChange]
127
    public function getIterator()
128
    {
129
        return new ArrayIterator($this->toArray());
130
    }
131
132
    /**
133
     * Return an array of the actual items that this relation contains at this stage.
134
     * This is when the query is actually executed.
135
     *
136
     * @return array
137
     */
138
    public function toArray()
139
    {
140
        $items = [];
141
        foreach ($this->items as $key => $item) {
142
            if (is_numeric($item)) {
143
                $item = DataObject::get_by_id($this->dataClass, $item);
144
            }
145
            if (!empty($this->extraFields[$key])) {
146
                $item->update($this->extraFields[$key]);
147
            }
148
            $items[] = $item;
149
        }
150
        return $items;
151
    }
152
153
    /**
154
     * Add a number of items to the relation.
155
     *
156
     * @param array $items Items to add, as either DataObjects or IDs.
157
     * @return $this
158
     */
159
    public function addMany($items)
160
    {
161
        foreach ($items as $item) {
162
            $this->add($item);
163
        }
164
        return $this;
165
    }
166
167
    /**
168
     * Remove all items from this relation.
169
     */
170
    public function removeAll()
171
    {
172
        $this->items = [];
173
        $this->extraFields = [];
174
    }
175
176
    /**
177
     * Remove the items from this list with the given IDs
178
     *
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)
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