PluginProbe ʕ •ᴥ•ʔ
Kirki – Freeform Page Builder, Website Builder & Customizer / 6.1.0
Kirki – Freeform Page Builder, Website Builder & Customizer v6.1.0
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 / Relation.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
Relation.php
496 lines
1 <?php
2
3 /**
4 * Base class for ORM relationship implementations.
5 * Holds references to the parent and related models and a query builder used to retrieve related data.
6 * Concrete relations implement constraint and match logic for lazy and eager loading scenarios.
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 BadMethodCallException;
16 use Closure;
17 use Kirki\Framework\Database\Query\Collection;
18 use Kirki\Framework\Database\Query\Expression;
19 use Kirki\Framework\Database\Query\Model;
20 use Kirki\Framework\Database\Query\QueryBuilder;
21 use Kirki\Framework\Collections\Collection as BaseCollection;
22 abstract class Relation
23 {
24 /**
25 * The related model instance for the relationship.
26 *
27 * @var Model
28 *
29 * @since 1.0.0
30 */
31 protected $related;
32 /**
33 * The parent model instance holding the relationship.
34 *
35 * @var Model
36 *
37 * @since 1.0.0
38 */
39 protected $parent;
40 /**
41 * The query builder instance for retrieving related data.
42 *
43 * @var QueryBuilder
44 *
45 * @since 1.0.0
46 */
47 protected $query;
48 /**
49 * Whether to apply constraints to the relation query.
50 *
51 * @var bool
52 *
53 * @since 1.0.0
54 */
55 protected static $constraints = \true;
56 /**
57 * The count of self joins.
58 *
59 * @var int
60 *
61 * @since 1.0.0
62 */
63 protected static $self_join_count = 0;
64 /**
65 * Whether the eager keys were empty.
66 *
67 * @var bool
68 *
69 * @since 1.0.0
70 */
71 protected bool $eager_keys_were_empty = \false;
72 /**
73 * Construct a new relation for the given models.
74 *
75 * Initializes the relation context and prepares a query builder targeting
76 * the related model's table. Concrete implementations will add specific
77 * constraints appropriate for the relationship type.
78 *
79 * @param Model $related The related model instance
80 * @param Model $parent The parent model holding the relation
81 *
82 * @return void No return value
83 *
84 * @since 1.0.0
85 */
86 public function __construct(Model $related, Model $parent)
87 {
88 $this->related = $related;
89 $this->parent = $parent;
90 $this->query = $related::query();
91 $this->add_constraints();
92 }
93 /**
94 * Apply base constraints to the relation query.
95 *
96 * Concrete relations should limit the query based on the relation keys so
97 * that fetching returns only records related to the parent context.
98 *
99 * @return void
100 *
101 * @since 1.0.0
102 */
103 public abstract function add_constraints();
104 /**
105 * Retrieve the relation results for lazy loading.
106 *
107 * Should return a single model or collection depending on the relation
108 * type. Used when accessing the relation as a property on the parent model.
109 *
110 * @return mixed The relation result set
111 *
112 * @since 1.0.0
113 */
114 public abstract function get_results();
115 /**
116 * Add constraints for eager loading across multiple parents.
117 *
118 * Accepts the array of parent models and scopes the query to fetch all
119 * related records needed in a single query using where-in style logic.
120 *
121 * @param Collection $models The parent models to constrain by
122 *
123 * @return void
124 *
125 * @since 1.0.0
126 */
127 public abstract function add_eager_constraints(Collection $models);
128 /**
129 * Get the eager results.
130 *
131 * @return Collection The eager results
132 *
133 * @since 1.0.0
134 */
135 public function get_eager()
136 {
137 return $this->eager_keys_were_empty ? $this->related->new_collection() : $this->get();
138 }
139 /**
140 * Where in eager.
141 *
142 * @param string $key The key.
143 * @param array $model_keys The model keys.
144 * @param QueryBuilder $query The query.
145 *
146 * @return void
147 *
148 * @since 1.0.0
149 */
150 protected function where_in_eager(string $key, array $model_keys, ?QueryBuilder $query = null)
151 {
152 $query = $query ?? $this->query;
153 $query->where_in($key, $model_keys);
154 if ($model_keys === []) {
155 $this->eager_keys_were_empty = \true;
156 }
157 }
158 /**
159 * Get the aggregate query for the relation.
160 *
161 * Accepts the query builder for the relation and the parent model and
162 * returns the aggregate query for the relation.
163 *
164 * @param QueryBuilder $query The query builder for the relation
165 * @param QueryBuilder $parent The parent model
166 * @param mixed $columns The columns to select
167 *
168 * @return QueryBuilder The aggregate query
169 *
170 * @since 1.0.0
171 */
172 public function get_relation_existence_query(QueryBuilder $query, QueryBuilder $parent, $columns = ['*'])
173 {
174 return $query->select($columns)->where_column($this->get_qualified_parent_key_name(), '=', $this->get_existence_compare_key());
175 }
176 /**
177 * Get the qualified parent key name for the relation.
178 *
179 * @return string The qualified parent key name
180 *
181 * @since 1.0.0
182 */
183 public function get_qualified_parent_key_name()
184 {
185 return $this->parent->get_prepared_key_name();
186 }
187 /**
188 * Summary of get_relation_existence_count_query
189 *
190 * @param QueryBuilder $query The query builder instance.
191 * @param QueryBuilder $parent The parent.
192 *
193 * @return QueryBuilder
194 *
195 * @since 1.0.0
196 */
197 public function get_relation_existence_count_query(QueryBuilder $query, QueryBuilder $parent)
198 {
199 return $this->get_relation_existence_query($query, $parent, new Expression('count(*)'))->set_bindings([], 'select');
200 }
201 /**
202 * Initialize the relation.
203 *
204 * @param Collection $models The models.
205 * @param string $relation The relation.
206 *
207 * @return Collection The initialized relation.
208 *
209 * @since 1.0.0
210 */
211 public abstract function init_relation(Collection $models, $relation);
212 /**
213 * Match eager loaded results back onto their parent models.
214 *
215 * Builds a dictionary keyed by relation keys and assigns the results to
216 * the appropriate relation property on each model.
217 *
218 * @param Collection $models The parent models receiving results
219 * @param mixed $results The retrieved related results
220 * @param string $relation The relation name on the parent
221 *
222 * @return array The parent models with relations populated
223 *
224 * @since 1.0.0
225 */
226 public abstract function match(Collection $models, Collection $results, $relation);
227 /**
228 * Execute the relation query and return a collection.
229 *
230 * Delegates to the underlying query builder get method. Used by concrete
231 * relations to return multiple results.
232 *
233 * @param mixed $columns The columns.
234 *
235 * @return Collection A collection of related models
236 *
237 * @since 1.0.0
238 */
239 public function get($columns = ['*'])
240 {
241 return $this->query->get($columns);
242 }
243 /**
244 * Execute the relation query and return the first result.
245 *
246 * Delegates to the query builder first method. Used by relations that
247 * return a single related model instance.
248 *
249 * @param mixed $columns The columns.
250 *
251 * @return mixed The first related model or null when none
252 *
253 * @since 1.0.0
254 */
255 public function first($columns = ['*'])
256 {
257 return $this->query->first($columns);
258 }
259 /**
260 * Add a where clause to the relation query.
261 *
262 * Forwards the condition to the underlying query builder and returns the
263 * relation for chaining additional constraints in a fluent style.
264 *
265 * @param string $column The column to compare
266 * @param mixed $operator The comparison operator or value when two args
267 * @param mixed $value The value to compare against when operator provided
268 * @param string $boolean The boolean operator to use
269 *
270 * @return $this The relation instance for method chaining
271 *
272 * @since 1.0.0
273 */
274 public function where($column, $operator = null, $value = null, $boolean = 'and')
275 {
276 $this->query->where($column, $operator, $value, $boolean);
277 return $this;
278 }
279 /**
280 * Add a where in clause to the relation query.
281 *
282 * Filters by membership in the provided values and returns the relation to
283 * support fluent chaining of additional constraints.
284 *
285 * @param string $column The column to filter
286 * @param array $values The list of allowed values
287 * @param mixed $boolean The boolean.
288 * @param mixed $not The not.
289 *
290 * @return $this The relation instance for method chaining
291 *
292 * @since 1.0.0
293 */
294 public function where_in($column, array $values, $boolean = 'and', $not = \false)
295 {
296 $this->query->where_in($column, $values, $boolean, $not);
297 return $this;
298 }
299 /**
300 * Apply an order by clause to the relation query.
301 *
302 * Sets the sort column and direction (default ASC) and returns the
303 * relation to allow further chained modifications.
304 *
305 * @param string $column The column to sort by
306 * @param string $direction The direction, ASC or DESC
307 *
308 * @return $this The relation instance for method chaining
309 *
310 * @since 1.0.0
311 */
312 public function order_by($column, $direction = 'ASC')
313 {
314 $this->query->order_by($column, $direction);
315 return $this;
316 }
317 /**
318 * Limit the maximum number of records returned.
319 *
320 * Forwards to the underlying query builder and returns the relation for
321 * fluent chaining behavior.
322 *
323 * @param int $limit The maximum number of records to return
324 *
325 * @return $this The relation instance for method chaining
326 *
327 * @since 1.0.0
328 */
329 public function limit($limit)
330 {
331 $this->query->limit($limit);
332 return $this;
333 }
334 /**
335 * Offset the starting position for returned records.
336 *
337 * Useful in conjunction with limit for paginating related results. Returns
338 * the relation instance to continue chaining.
339 *
340 * @param int $offset The number of records to skip
341 *
342 * @return $this The relation instance for method chaining
343 *
344 * @since 1.0.0
345 */
346 public function offset($offset)
347 {
348 $this->query->offset($offset);
349 return $this;
350 }
351 /**
352 * Get the underlying query builder for the relation.
353 *
354 * Exposes the builder when direct access is necessary for advanced use
355 * cases beyond the convenience methods provided on the relation.
356 *
357 * @return QueryBuilder The relation's query builder instance
358 *
359 * @since 1.0.0
360 */
361 public function get_query()
362 {
363 return $this->query;
364 }
365 /**
366 * Get the related model instance.
367 *
368 * Provides access to the related model prototype used by the relation for
369 * constructing queries and hydrating results.
370 *
371 * @return Model The related model instance
372 *
373 * @since 1.0.0
374 */
375 public function get_related()
376 {
377 return $this->related;
378 }
379 /**
380 * Get the parent model instance.
381 *
382 * Returns the model on which this relation is defined. Useful when
383 * constructing custom constraints or accessing parent keys.
384 *
385 * @return Model The parent model instance
386 *
387 * @since 1.0.0
388 */
389 public function get_parent()
390 {
391 return $this->parent;
392 }
393 /**
394 * Get the local key for the relation.
395 *
396 * Returns the key on the parent model that is used to match related records.
397 * This is used by with_count() to match count results back to parent models.
398 *
399 * @return string The local key name
400 *
401 * @since 1.0.0
402 */
403 public abstract function get_local_key();
404 /**
405 * Get a hash for the relation count query.
406 *
407 * @param bool $increment_join_count Whether to increment the join count
408 *
409 * @return string The hash for the relation count query
410 *
411 * @since 1.0.0
412 */
413 public function get_relation_count_hash($increment_join_count = \true)
414 {
415 return 'framework_reserved_' . ($increment_join_count ? static::$self_join_count++ : static::$self_join_count);
416 }
417 /**
418 * Get the keys from the models.
419 *
420 * @param Collection $models The models.
421 * @param string $key The key.
422 *
423 * @return array The keys.
424 *
425 * @since 1.0.0
426 */
427 protected function get_keys(Collection $models, $key = null)
428 {
429 return (new BaseCollection($models))->map(function ($model) use($key) {
430 return $key ? $model->get_attribute($key) : $model->get_primary_key_value();
431 })->values()->unique(null, \true)->all();
432 }
433 /**
434 * Get the relation query.
435 *
436 * @return QueryBuilder The relation query.
437 *
438 * @since 1.0.0
439 */
440 protected function get_relation_query()
441 {
442 return $this->query;
443 }
444 /**
445 * Qualify a column name with the related table name.
446 *
447 * @param string $key The column name to qualify
448 *
449 * @return string The qualified column name
450 *
451 * @since 1.0.0
452 */
453 public function qualify_column(string $key)
454 {
455 return $this->related->prepare_column($key);
456 }
457 /**
458 * Temporarily disable constraints for the duration of the callback.
459 *
460 * @param Closure $callback The callback to execute with constraints disabled
461 *
462 * @return mixed The return value of the callback
463 *
464 * @since 1.0.0
465 */
466 public static function without_constraints(Closure $callback)
467 {
468 $previous = static::$constraints;
469 static::$constraints = \false;
470 try {
471 return $callback();
472 } finally {
473 static::$constraints = $previous;
474 }
475 }
476 /**
477 * Handle dynamic method calls into the query builder.
478 *
479 * @param string $method The method name
480 * @param array $parameters The method parameters
481 *
482 * @return mixed The result of the method call
483 *
484 * @throws \BadMethodCallException
485 *
486 * @since 1.0.0
487 */
488 public function __call($method, $parameters)
489 {
490 if (!\method_exists($this->query, $method)) {
491 throw new BadMethodCallException(\sprintf('Method %s::%s does not exist.', QueryBuilder::class, esc_html($method)));
492 }
493 return $this->query->{$method}(...$parameters);
494 }
495 }
496