PluginProbe ʕ •ᴥ•ʔ
Kirki – Freeform Page Builder, Website Builder & Customizer / 6.1.1
Kirki – Freeform Page Builder, Website Builder & Customizer v6.1.1
6.1.1 6.1.0 6.0.14 6.0.13 6.0.12 6.0.11 6.0.10 6.0.9 6.0.8 6.0.7 6.0.6 6.0.5 6.0.4 6.0.3 6.0.2 6.0.1 3.1.3 3.1.4 3.1.5 3.1.6 3.1.7 3.1.8 3.1.9 4.0.19 4.0.20 4.0.21 4.0.22 4.0.23 4.0.24 4.1 4.2.0 5.0.0 5.1.0 5.1.1 5.2.0 5.2.1 5.2.2 5.2.3 6.0.0 trunk 3.0.40 3.0.41 3.0.42 3.0.43 3.0.44 3.0.45 3.1.0 3.1.1 3.1.2
kirki / libraries / framework / Database / Query / Relations / HasMany.php
kirki / libraries / framework / Database / Query / Relations Last commit date
BelongsTo.php 3 weeks ago BelongsToMany.php 3 weeks ago HasMany.php 3 weeks ago HasOne.php 3 weeks ago Relation.php 3 weeks ago
HasMany.php
325 lines
1 <?php
2
3 /**
4 * Define a one-to-many relation between parent and related models.
5 * Constrains the related query to records whose foreign key points to the parent's local key.
6 * Supports lazy loading, eager loading with where-in, and matching results back to parents as collections.
7 *
8 * @package Framework
9 * @subpackage Database\Query\Relations
10 * @since 1.0.0
11 */
12 namespace Kirki\Framework\Database\Query\Relations;
13
14 \defined('ABSPATH') || exit;
15 use Kirki\Framework\Database\Query\Model;
16 use Kirki\Framework\Database\Query\Collection;
17 use Kirki\Framework\Database\Concerns\HasDictionary;
18 use Kirki\Framework\Database\Query\QueryBuilder;
19 use function Kirki\Framework\Polyfill\array_first;
20 use function Kirki\Framework\Polyfill\array_last;
21 class HasMany extends Relation
22 {
23 use HasDictionary;
24 /**
25 * The foreign key on the related model that references the parent model.
26 *
27 * @var string
28 *
29 * @since 1.0.0
30 */
31 protected $foreign_key;
32 /**
33 * The local key on the parent model that is referenced by the foreign key.
34 *
35 * @var string
36 *
37 * @since 1.0.0
38 */
39 protected $local_key;
40 /**
41 * Create a new has-many relation instance.
42 *
43 * Stores keys and applies base constraints so that subsequent queries
44 * return only the child records belonging to the given parent.
45 *
46 * @param Model $related The related model instance
47 * @param Model $parent The parent model instance
48 * @param mixed $foreign_key The related table's foreign key
49 * @param mixed $local_key The parent's local key
50 *
51 * @return void No return value
52 *
53 * @since 1.0.0
54 */
55 public function __construct(Model $related, Model $parent, $foreign_key, $local_key)
56 {
57 $this->foreign_key = $foreign_key;
58 $this->local_key = $local_key;
59 parent::__construct($related, $parent);
60 }
61 /**
62 * Apply base constraints using the parent's local key.
63 *
64 * When the parent has a local key value, the query is limited to rows
65 * whose foreign key equals that value to ensure only belonging children are
66 * retrieved.
67 *
68 * @return void
69 *
70 * @since 1.0.0
71 */
72 public function add_constraints()
73 {
74 if (static::$constraints) {
75 $this->query->where($this->foreign_key, '=', $this->get_parent_key_value());
76 $this->query->where_not_null($this->foreign_key);
77 }
78 }
79 /**
80 * Get the parent key value.
81 *
82 * @return mixed The parent key value
83 *
84 * @since 1.0.0
85 */
86 public function get_parent_key_value()
87 {
88 return $this->parent->get_attribute($this->local_key);
89 }
90 /**
91 * Get the aggregate query for the relation.
92 *
93 * @param QueryBuilder $query The query builder instance
94 * @param QueryBuilder $parent The parent query builder instance
95 * @param mixed $columns The columns to select
96 *
97 * @return QueryBuilder The aggregate query
98 *
99 * @since 1.0.0
100 */
101 public function get_relation_existence_query(QueryBuilder $query, QueryBuilder $parent, $columns = ['*'])
102 {
103 if ($query->from === $parent->from) {
104 return $this->get_relation_existence_query_for_self_relation($query, $parent, $columns);
105 }
106 return parent::get_relation_existence_query($query, $parent, $columns);
107 }
108 /**
109 * Get the aggregate query for the relation when the query and parent are the same.
110 *
111 * @param QueryBuilder $query The query builder instance
112 * @param QueryBuilder $parent The parent query builder instance
113 * @param mixed $columns The columns to select
114 *
115 * @return QueryBuilder The aggregate query
116 *
117 * @since 1.0.0
118 */
119 public function get_relation_existence_query_for_self_relation(QueryBuilder $query, QueryBuilder $parent, $columns = ['*'])
120 {
121 $query->from($query->get_model()->get_table() . ' as ' . ($hash = $this->get_relation_count_hash()));
122 $query->get_model()->set_table($hash);
123 return $query->select($columns)->where_column($this->get_qualified_parent_key_name(), '=', $hash . '.' . $this->get_foreign_key_name());
124 }
125 /**
126 * Get the existence compare key for the relation.
127 *
128 * @return string The existence compare key
129 *
130 * @since 1.0.0
131 */
132 public function get_existence_compare_key()
133 {
134 return $this->qualify_column($this->foreign_key);
135 }
136 /**
137 * Get the qualified parent key name.
138 *
139 * @return string The qualified parent key name
140 *
141 * @since 1.0.0
142 */
143 public function get_qualified_parent_key_name()
144 {
145 return $this->parent->prepare_column($this->local_key);
146 }
147 /**
148 * Get the foreign key name.
149 *
150 * @return string The foreign key name
151 *
152 * @since 1.0.0
153 */
154 public function get_foreign_key_name()
155 {
156 $segments = \explode('.', $this->get_qualified_foreign_key_name());
157 return array_last($segments);
158 }
159 /**
160 * Get the qualified foreign key name.
161 *
162 * @return string The qualified foreign key name
163 *
164 * @since 1.0.0
165 */
166 public function get_qualified_foreign_key_name()
167 {
168 return $this->foreign_key;
169 }
170 /**
171 * Get the related results for lazy loading.
172 *
173 * Returns a collection of child models matching the applied constraints.
174 *
175 * @return Collection The collection of related models
176 *
177 * @since 1.0.0
178 */
179 public function get_results()
180 {
181 return !\is_null($this->get_parent_key_value()) ? $this->get() : $this->related->new_collection();
182 }
183 /**
184 * Add eager loading constraints across multiple parents.
185 *
186 * Collects parent local keys and scopes the query using where_in to fetch
187 * all required children in a single query.
188 *
189 * @param Collection $models The parent models
190 *
191 * @return void
192 *
193 * @since 1.0.0
194 */
195 public function add_eager_constraints(Collection $models)
196 {
197 $this->where_in_eager($this->foreign_key, $this->get_keys($models, $this->local_key), $this->get_relation_query());
198 }
199 /**
200 * @inheritDoc
201 */
202 public function init_relation(Collection $models, $relation)
203 {
204 foreach ($models as $model) {
205 $model->set_relation($relation, $this->related->new_collection());
206 }
207 return $models;
208 }
209 /**
210 * Match eager loaded results back to their parents.
211 *
212 * Groups children by their foreign key and assigns a collection to each
213 * parent under the relation name, defaulting to an empty collection when
214 * none are present.
215 *
216 * @param Collection $models The models.
217 * @param Collection $results The results.
218 * @param string $relation The relation name on the parent
219 *
220 * @return Collection The parent models with relations set
221 *
222 * @since 1.0.0
223 */
224 public function match(Collection $models, Collection $results, $relation)
225 {
226 $dictionary = $this->build_dictionary($results);
227 foreach ($models as $model) {
228 $attribute = $this->get_dictionary_key($model->get_attribute($this->local_key));
229 if ($attribute !== null && isset($dictionary[$attribute])) {
230 $model->set_relation($relation, $this->related->new_collection($dictionary[$attribute]));
231 }
232 }
233 return $models;
234 }
235 /**
236 * Build the dictionary for the relation from the results.
237 *
238 * @param Collection $results The results.
239 *
240 * @return array The dictionary
241 *
242 * @since 1.0.0
243 */
244 protected function build_dictionary(Collection $results)
245 {
246 $dictionary = [];
247 foreach ($results as $result) {
248 $attribute = $this->get_dictionary_key($result->get_attribute($this->foreign_key));
249 if ($attribute !== null) {
250 $dictionary[$attribute] ??= [];
251 $dictionary[$attribute][] = $result;
252 }
253 }
254 return $dictionary;
255 }
256 /**
257 * Create a new related model with the foreign key pre-filled.
258 *
259 * Assigns the parent's local key to the child's foreign key attribute and
260 * delegates to the related model's create method to persist the record.
261 *
262 * @param array $attributes The attributes for the new child model
263 *
264 * @return Model The created related model instance
265 *
266 * @since 1.0.0
267 */
268 public function create(array $attributes)
269 {
270 $model = $this->related->new_instance($this->set_foreign_attributes_for_create($attributes));
271 $model->save();
272 return $model;
273 }
274 /**
275 * Set the foreign key attribute for the given attributes array.
276 *
277 * @param array $attributes The attributes array to modify
278 *
279 * @return array The modified attributes array
280 *
281 * @since 1.0.0
282 */
283 protected function set_foreign_attributes_for_create(array $attributes)
284 {
285 $attributes[$this->foreign_key] = $this->parent->get_attribute($this->local_key);
286 return $attributes;
287 }
288 /**
289 * Create multiple related models with the foreign key pre-filled.
290 *
291 * Iterates over the given attributes array, creating a new related model
292 * for each set of attributes and adding it to the models array.
293 *
294 * @param array $attributes The attributes for the new child models
295 *
296 * @return array<Model> The array of created related models
297 *
298 * @since 1.0.0
299 */
300 public function create_many(array $attributes)
301 {
302 if (!\is_array(array_first($attributes))) {
303 $attributes = [$attributes];
304 }
305 $models = [];
306 foreach ($attributes as $attribute) {
307 $models[] = $this->create($attribute);
308 }
309 return $models;
310 }
311 /**
312 * Get the local key for the relation.
313 *
314 * Returns the key on the parent model that is used to match related records.
315 *
316 * @return string The local key name
317 *
318 * @since 1.0.0
319 */
320 public function get_local_key()
321 {
322 return $this->local_key;
323 }
324 }
325