Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
63 / 63
100.00% covered (success)
100.00%
5 / 5
CRAP
100.00% covered (success)
100.00%
1 / 1
BelongsTo
100.00% covered (success)
100.00%
63 / 63
100.00% covered (success)
100.00%
5 / 5
27
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getChild
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getParent
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
3
 getEmptyRelationshipValue
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getEagerRelationships
100.00% covered (success)
100.00%
52 / 52
100.00% covered (success)
100.00%
1 / 1
21
1<?php
2declare(strict_types=1);
3/**
4 * Pop PHP Framework (https://www.popphp.org/)
5 *
6 * @link       https://github.com/popphp/popphp-framework
7 * @author     Nick Sagona, III <nick@popphp.org>
8 * @copyright  Copyright (c) 2009-2026 Nick Sagona, III
9 * @license    https://www.popphp.org/license     New BSD License
10 */
11
12/**
13 * @namespace
14 */
15namespace Pop\Db\Record\Relationships;
16
17use Pop\Db\Record;
18use Pop\Db\Sql\Parser;
19
20/**
21 * Relationship class for "belongs to" relationships
22 *
23 * @category   Pop
24 * @package    Pop\Db
25 * @author     Nick Sagona, III <nick@popphp.org>
26 * @copyright  Copyright (c) 2009-2026 Nick Sagona, III
27 * @license    https://www.popphp.org/license     New BSD License
28 * @version    7.0.0
29 */
30class BelongsTo extends AbstractRelationship
31{
32
33    /**
34     * Child record
35     * @var ?Record
36     */
37    protected ?Record $child = null;
38
39    /**
40     * Constructor
41     *
42     * Instantiate the relationship object
43     *
44     * @param Record $child
45     * @param string $foreignTable
46     * @param string|array $foreignKey
47     * @param ?array $options
48     */
49    public function __construct(Record $child, string $foreignTable, string|array $foreignKey, ?array $options = null)
50    {
51        parent::__construct($foreignTable, $foreignKey, $options);
52        $this->child = $child;
53    }
54
55    /**
56     * Get child record
57     *
58     * @return ?Record
59     */
60    public function getChild(): ?Record
61    {
62        return $this->child;
63    }
64
65    /**
66     * Get parent
67     *
68     * @param  ?array $options
69     * @return ?Record
70     */
71    public function getParent(?array $options = null): ?Record
72    {
73        $table = $this->foreignTable;
74
75        $this->assertKeyCardinality($this->foreignKey, (new $table())->getPrimaryKeys());
76
77        $values = is_array($this->foreignKey) ?
78            array_map(fn($col) => $this->child[$col], $this->foreignKey) : $this->child[$this->foreignKey];
79
80        if (!empty($this->children)) {
81            return $table::with($this->children)->getById($values);
82        } else {
83            return $table::findById($values, $options);
84        }
85    }
86
87    /**
88     * Get the value to use when no eager-loaded result exists for a given leaf record
89     *
90     * @return mixed
91     */
92    public function getEmptyRelationshipValue(): mixed
93    {
94        return null;
95    }
96
97    /**
98     * Get eager relationships
99     *
100     * @param  array $ids
101     * @throws Exception
102     * @return array
103     */
104    public function getEagerRelationships(array $ids): array
105    {
106        if (($this->foreignTable === null) || ($this->foreignKey === null)) {
107            throw new Exception('Error: The foreign table and key values have not been set.');
108        }
109
110        $results = [];
111        $table   = $this->foreignTable;
112        $db      = $table::db();
113        $sql     = $db->createSql();
114        $columns = null;
115
116        if (!empty($this->options)) {
117            if (isset($this->options['select'])) {
118                $columns = $this->options['select'];
119            }
120        }
121
122        $primaryKeys = (new $table())->getPrimaryKeys();
123        $this->assertKeyCardinality($this->foreignKey, $primaryKeys);
124        $parentKey = (count($primaryKeys) == 1) ? reset($primaryKeys) : $primaryKeys;
125
126        $sql->select($columns)->from($table::table());
127
128        $params = [];
129
130        $this->applyEagerIdFilter($sql, $parentKey, $ids, $params);
131
132        if (!empty($this->options)) {
133            if (isset($this->options['limit'])) {
134                $sql->select()->limit((int)$this->options['limit']);
135            }
136
137            if (isset($this->options['offset'])) {
138                $sql->select()->offset((int)$this->options['offset']);
139            }
140            if (isset($this->options['join'])) {
141                $joins = (is_array($this->options['join']) && isset($this->options['join']['table'])) ?
142                    [$this->options['join']] : $this->options['join'];
143
144                foreach ($joins as $join) {
145                    if (isset($join['type']) && method_exists($sql->select(), $join['type'])) {
146                        $joinMethod = $join['type'];
147                        $sql->select()->{$joinMethod}($join['table'], $join['columns']);
148                    } else {
149                        $sql->select()->leftJoin($join['table'], $join['columns']);
150                    }
151                }
152            }
153            if (isset($this->options['order'])) {
154                if (!is_array($this->options['order'])) {
155                    $orders = (str_contains($this->options['order'], ',')) ?
156                        explode(',', $this->options['order']) : [$this->options['order']];
157                } else {
158                    $orders = $this->options['order'];
159                }
160                foreach ($orders as $order) {
161                    $ord = Parser\Order::parse(trim($order));
162                    $sql->select()->orderBy($ord['by'], $db->escape($ord['order']));
163                }
164            }
165        }
166
167        $db->prepare($sql)
168            ->bindParams($params)
169            ->execute();
170
171        $rows        = $db->fetchAll();
172        $results     = [];
173        $leafRecords = [];
174
175        foreach ($rows as $row) {
176            $record = new $table();
177            $record->setColumns($row);
178            $key = is_array($parentKey) ?
179                self::buildCompositeKey(array_map(fn($col) => $row[$col], $parentKey)) : $row[$parentKey];
180            $results[$key] = $record;
181            $leafRecords[] = $record;
182        }
183
184        $this->hydrateChildRelationships($leafRecords, $parentKey);
185
186        return $results;
187    }
188
189}