/
PolymorphicHasManyList.php
146 lines (128 loc) · 4.62 KB
/
PolymorphicHasManyList.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
<?php
namespace SilverStripe\ORM;
use SilverStripe\Core\ClassInfo;
use SilverStripe\Core\Convert;
use InvalidArgumentException;
/**
* Represents a has_many list linked against a polymorphic relationship
*/
class PolymorphicHasManyList extends HasManyList
{
/**
* Name of foreign key field that references the class name of the relation
*
* @var string
*/
protected $classForeignKey;
/**
* Retrieve the name of the class this relation is filtered by
*
* @return string
*/
public function getForeignClass()
{
return $this->dataQuery->getQueryParam('Foreign.Class');
}
/**
* Gets the field name which holds the related object class.
*/
public function getForeignClassKey(): string
{
return $this->classForeignKey;
}
/**
* Create a new PolymorphicHasManyList relation list.
*
* @param string $dataClass The class of the DataObjects that this will list.
* @param string $foreignField The name of the composite foreign relation field. Used
* to generate the ID and Class foreign keys.
* @param string $foreignClass Name of the class filter this relation is filtered against
*/
public function __construct($dataClass, $foreignField, $foreignClass)
{
// Set both id foreign key (as in HasManyList) and the class foreign key
parent::__construct($dataClass, "{$foreignField}ID");
$this->classForeignKey = "{$foreignField}Class";
// Ensure underlying DataQuery globally references the class filter
$this->dataQuery->setQueryParam('Foreign.Class', $foreignClass);
// For queries with multiple foreign IDs (such as that generated by
// DataList::relation) the filter must be generalised to filter by subclasses
$classNames = Convert::raw2sql(ClassInfo::subclassesFor($foreignClass));
$this->dataQuery->where(sprintf(
"\"{$this->classForeignKey}\" IN ('%s')",
implode("', '", $classNames)
));
}
/**
* Adds the item to this relation.
*
* It does so by setting the relationFilters.
*
* @param DataObject|int $item The DataObject to be added, or its ID
*/
public function add($item)
{
if (is_numeric($item)) {
$item = DataObject::get_by_id($this->dataClass, $item);
} elseif (!($item instanceof $this->dataClass)) {
throw new InvalidArgumentException(
"PolymorphicHasManyList::add() expecting a $this->dataClass object, or ID value"
);
}
$foreignID = $this->getForeignID();
// Validate foreignID
if (!$foreignID) {
user_error(
"PolymorphicHasManyList::add() can't be called until a foreign ID is set",
E_USER_WARNING
);
return;
}
if (is_array($foreignID)) {
user_error(
"PolymorphicHasManyList::add() can't be called on a list linked to multiple foreign IDs",
E_USER_WARNING
);
return;
}
$foreignKey = $this->foreignKey;
$classForeignKey = $this->classForeignKey;
$item->$foreignKey = $foreignID;
$item->$classForeignKey = $this->getForeignClass();
$item->write();
}
/**
* Remove an item from this relation.
* Doesn't actually remove the item, it just clears the foreign key value.
*
* @param DataObject $item The DataObject to be removed
* @todo Maybe we should delete the object instead?
*/
public function remove($item)
{
if (!($item instanceof $this->dataClass)) {
throw new InvalidArgumentException(
"HasManyList::remove() expecting a $this->dataClass object, or ID"
);
}
// Don't remove item with unrelated class key
$foreignClass = $this->getForeignClass();
$classNames = ClassInfo::subclassesFor($foreignClass);
$classForeignKey = $this->classForeignKey;
$classValueLower = strtolower($item->$classForeignKey ?? '');
if (!array_key_exists($classValueLower, $classNames ?? [])) {
return;
}
// Don't remove item which doesn't belong to this list
$foreignID = $this->getForeignID();
$foreignKey = $this->foreignKey;
if (empty($foreignID)
|| $foreignID == $item->$foreignKey
|| (is_array($foreignID) && in_array($item->$foreignKey, $foreignID ?? []))
) {
$item->$foreignKey = null;
$item->$classForeignKey = null;
$item->write();
}
}
}