|
1
|
|
|
<?php |
|
2
|
|
|
namespace Darya\ORM\Relation; |
|
3
|
|
|
|
|
4
|
|
|
use Darya\ORM\Record; |
|
5
|
|
|
use Darya\ORM\Relation; |
|
6
|
|
|
|
|
7
|
|
|
/** |
|
8
|
|
|
* Darya's has-many entity relation. |
|
9
|
|
|
* |
|
10
|
|
|
* @author Chris Andrew <[email protected]> |
|
11
|
|
|
*/ |
|
12
|
|
|
class HasMany extends Has { |
|
13
|
|
|
|
|
14
|
|
|
/** |
|
15
|
|
|
* Eagerly load the related models of the given parent instances. |
|
16
|
|
|
* |
|
17
|
|
|
* Retrieves the related models without matching them to their parents. |
|
18
|
|
|
* |
|
19
|
|
|
* @param array $instances |
|
20
|
|
|
* @return array |
|
21
|
|
|
*/ |
|
22
|
|
|
public function eagerLoad(array $instances) |
|
23
|
|
|
{ |
|
24
|
|
|
$this->verifyParents($instances); |
|
25
|
|
|
$ids = static::attributeList($instances, 'id'); |
|
26
|
|
|
|
|
27
|
|
|
$filter = array_merge($this->filter(), array( |
|
28
|
|
|
$this->foreignKey => array_unique($ids) |
|
29
|
|
|
)); |
|
30
|
|
|
|
|
31
|
|
|
$data = $this->storage()->read($this->target->table(), $filter); |
|
32
|
|
|
|
|
33
|
|
|
return $this->target->generate($data); |
|
34
|
|
|
} |
|
35
|
|
|
|
|
36
|
|
|
/** |
|
37
|
|
|
* Eagerly load and match the related models for the given parent instances. |
|
38
|
|
|
* |
|
39
|
|
|
* Returns the given instances with their related models loaded. |
|
40
|
|
|
* |
|
41
|
|
|
* @param array $instances |
|
42
|
|
|
* @return array |
|
43
|
|
|
*/ |
|
44
|
|
|
public function eager(array $instances) { |
|
45
|
|
|
if ($this->parent instanceof $this->target) { |
|
46
|
|
|
return $this->eagerSelf($instances); |
|
47
|
|
|
} |
|
48
|
|
|
|
|
49
|
|
|
$related = $this->eagerLoad($instances); |
|
50
|
|
|
|
|
51
|
|
|
$instances = $this->match($instances, $related); |
|
52
|
|
|
|
|
53
|
|
|
return $instances; |
|
54
|
|
|
} |
|
55
|
|
|
|
|
56
|
|
|
/** |
|
57
|
|
|
* Eagerly load the related models from the same table. |
|
58
|
|
|
* |
|
59
|
|
|
* This continues to load the same relation recursively. |
|
60
|
|
|
* |
|
61
|
|
|
* @param array $instances |
|
62
|
|
|
* @return array |
|
63
|
|
|
*/ |
|
64
|
|
|
protected function eagerSelf(array $instances) |
|
65
|
|
|
{ |
|
66
|
|
|
$parents = $instances; |
|
67
|
|
|
|
|
68
|
|
|
while ($related = $this->eagerLoad($parents)) { |
|
69
|
|
|
$this->match($parents, $related); |
|
70
|
|
|
|
|
71
|
|
|
$parents = $related; |
|
72
|
|
|
} |
|
73
|
|
|
|
|
74
|
|
|
return $instances; |
|
75
|
|
|
} |
|
76
|
|
|
|
|
77
|
|
|
/** |
|
78
|
|
|
* Match the given related models to their parent instances. |
|
79
|
|
|
* |
|
80
|
|
|
* @param Record[] $instances |
|
81
|
|
|
* @param Record[] $related |
|
82
|
|
|
* @return Record[] |
|
83
|
|
|
*/ |
|
84
|
|
|
protected function match(array $instances, array $related) { |
|
85
|
|
|
$list = $this->adjacencyList($related); |
|
86
|
|
|
|
|
87
|
|
|
foreach ($instances as $instance) { |
|
88
|
|
|
$key = $instance->id(); |
|
89
|
|
|
$value = isset($list[$key]) ? $list[$key] : array(); |
|
90
|
|
|
$instance->relation($this->name)->set($value); |
|
91
|
|
|
} |
|
92
|
|
|
|
|
93
|
|
|
return $instances; |
|
94
|
|
|
} |
|
95
|
|
|
|
|
96
|
|
|
/** |
|
97
|
|
|
* Retrieve the related models. |
|
98
|
|
|
* |
|
99
|
|
|
* @return Record[] |
|
100
|
|
|
*/ |
|
101
|
|
|
public function retrieve() { |
|
102
|
|
|
return $this->all(); |
|
|
|
|
|
|
103
|
|
|
} |
|
104
|
|
|
|
|
105
|
|
|
/** |
|
106
|
|
|
* Associate the given models. |
|
107
|
|
|
* |
|
108
|
|
|
* Returns the number of models successfully associated. |
|
109
|
|
|
* |
|
110
|
|
|
* TODO: Stop this from assuming an ID on the instances. Somehow. Maybe save |
|
111
|
|
|
* if it doesn't have one yet, or don't use IDs at the risk of saving |
|
112
|
|
|
* more relations than necessary (all of them...). |
|
113
|
|
|
* |
|
114
|
|
|
* @param Record[]|Record $instances |
|
115
|
|
|
* @return int |
|
116
|
|
|
*/ |
|
117
|
|
|
public function associate($instances) { |
|
118
|
|
|
$ids = array(); |
|
119
|
|
|
|
|
120
|
|
|
foreach (static::arrayify($instances) as $instance) { |
|
121
|
|
|
$this->replace($instance); |
|
122
|
|
|
|
|
123
|
|
|
$ids[] = $instance->id(); |
|
124
|
|
|
} |
|
125
|
|
|
|
|
126
|
|
|
return $this->save($ids); |
|
127
|
|
|
} |
|
128
|
|
|
|
|
129
|
|
|
/** |
|
130
|
|
|
* Dissociate the given models. |
|
131
|
|
|
* |
|
132
|
|
|
* Returns the number of models successfully dissociated. |
|
133
|
|
|
* |
|
134
|
|
|
* TODO: Consider constraints |
|
135
|
|
|
* |
|
136
|
|
|
* @param Record[]|Record $instances [optional] |
|
137
|
|
|
* @return int |
|
138
|
|
|
*/ |
|
139
|
|
|
public function dissociate($instances = array()) { |
|
140
|
|
|
$ids = array(); |
|
141
|
|
|
|
|
142
|
|
|
$successful = 0; |
|
143
|
|
|
|
|
144
|
|
|
foreach (static::arrayify($instances) as $instance) { |
|
145
|
|
|
$this->verify($instance); |
|
146
|
|
|
$instance->set($this->foreignKey, 0); |
|
147
|
|
|
|
|
148
|
|
|
if ($instance->save()) { |
|
149
|
|
|
$ids[] = $instance->id(); |
|
150
|
|
|
$successful++; |
|
151
|
|
|
} |
|
152
|
|
|
} |
|
153
|
|
|
|
|
154
|
|
|
$relatedIds = array(); |
|
155
|
|
|
|
|
156
|
|
|
foreach ($this->related as $related) { |
|
157
|
|
|
$relatedIds[] = $related->id(); |
|
158
|
|
|
} |
|
159
|
|
|
|
|
160
|
|
|
$this->reduce(array_diff($relatedIds, $ids)); |
|
161
|
|
|
|
|
162
|
|
|
return $successful; |
|
163
|
|
|
} |
|
164
|
|
|
|
|
165
|
|
|
/** |
|
166
|
|
|
* Dissociate all currently associated models. |
|
167
|
|
|
* |
|
168
|
|
|
* Returns the number of models successfully dissociated. |
|
169
|
|
|
* |
|
170
|
|
|
* TODO: Consider constraints |
|
171
|
|
|
* |
|
172
|
|
|
* @return int |
|
173
|
|
|
*/ |
|
174
|
|
|
public function purge() { |
|
175
|
|
|
$this->related = array(); |
|
176
|
|
|
|
|
177
|
|
|
return (int) $this->storage()->update($this->target->table(), array( |
|
178
|
|
|
$this->foreignKey => 0 |
|
179
|
|
|
), array( |
|
180
|
|
|
$this->foreignKey => $this->parent->get($this->localKey) |
|
181
|
|
|
)); |
|
182
|
|
|
} |
|
183
|
|
|
|
|
184
|
|
|
} |
|
185
|
|
|
|
If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.
Let’s take a look at an example:
Our function
my_functionexpects aPostobject, and outputs the author of the post. The base classPostreturns a simple string and outputting a simple string will work just fine. However, the child classBlogPostwhich is a sub-type ofPostinstead decided to return anobject, and is therefore violating the SOLID principles. If aBlogPostwere passed tomy_function, PHP would not complain, but ultimately fail when executing thestrtouppercall in its body.