Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
59.09% covered (warning)
59.09%
52 / 88
50.00% covered (warning)
50.00%
2 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
HasMany
59.09% covered (warning)
59.09%
52 / 88
50.00% covered (warning)
50.00%
2 / 4
162.77
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getParent
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getChildren
80.00% covered (success)
80.00%
8 / 10
0.00% covered (danger)
0.00%
0 / 1
5.20
 getEagerRelationships
54.67% covered (warning)
54.67%
41 / 75
0.00% covered (danger)
0.00%
0 / 1
149.13
1<?php
2/**
3 * Pop PHP Framework (http://www.popphp.org/)
4 *
5 * @link       https://github.com/popphp/popphp-framework
6 * @author     Nick Sagona, III <dev@nolainteractive.com>
7 * @copyright  Copyright (c) 2009-2024 NOLA Interactive, LLC. (http://www.nolainteractive.com)
8 * @license    http://www.popphp.org/license     New BSD License
9 */
10
11/**
12 * @namespace
13 */
14namespace Pop\Db\Record\Relationships;
15
16use Pop\Db\Record;
17use Pop\Db\Sql\Parser;
18
19/**
20 * Relationship class for "has many" relationships
21 *
22 * @category   Pop
23 * @package    Pop\Db
24 * @author     Nick Sagona, III <dev@nolainteractive.com>
25 * @copyright  Copyright (c) 2009-2024 NOLA Interactive, LLC. (http://www.nolainteractive.com)
26 * @license    http://www.popphp.org/license     New BSD License
27 * @version    6.5.0
28 */
29class HasMany extends AbstractRelationship
30{
31
32    /**
33     * Parent record
34     * @var ?Record
35     */
36    protected ?Record $parent = null;
37
38    /**
39     * Constructor
40     *
41     * Instantiate the relationship object
42     *
43     * @param Record $parent
44     * @param string $foreignTable
45     * @param string $foreignKey
46     * @param ?array $options
47     */
48    public function __construct(Record $parent, string $foreignTable, string $foreignKey, ?array $options = null)
49    {
50        parent::__construct($foreignTable, $foreignKey, $options);
51        $this->parent = $parent;
52    }
53
54    /**
55     * Get parent record
56     *
57     * @return ?Record
58     */
59    public function getParent(): ?Record
60    {
61        return $this->parent;
62    }
63
64    /**
65     * Get children
66     *
67     * @param  ?array $options
68     * @return Record\Collection
69     */
70    public function getChildren(?array $options = null): Record\Collection
71    {
72        $table  = $this->foreignTable;
73        $values = array_values($this->parent->getPrimaryValues());
74
75        if (count($values) == 1) {
76            $values = $values[0];
77        }
78
79        $columns = [$this->foreignKey => $values];
80
81        if (!empty($options) && !empty($options['columns'])) {
82            $columns = array_merge($columns, $options['columns']);
83        }
84
85        if (!empty($this->children)) {
86            return $table::with($this->children)->getBy($columns, $options);
87        } else {
88            return $table::findBy($columns, $options);
89        }
90    }
91
92    /**
93     * Get eager relationships
94     *
95     * @param  array $ids
96     * @param  bool  $toArray
97     * @throws Exception
98     * @return array
99     */
100    public function getEagerRelationships(array $ids, bool|array $toArray = false): array
101    {
102        if (($this->foreignTable === null) || ($this->foreignKey === null)) {
103            throw new Exception('Error: The foreign table and key values have not been set.');
104        }
105
106        $results = [];
107        $table   = $this->foreignTable;
108        $db      = $table::db();
109        $sql     = $db->createSql();
110        $columns = null;
111
112        if (!empty($this->options)) {
113            if (isset($this->options['select'])) {
114                $columns = $this->options['select'];
115            }
116        }
117
118        $placeholders = array_fill(0, count($ids), $sql->getPlaceholder());
119        $sql->select($columns)->from($table::table())->where->in($this->foreignKey, $placeholders);
120
121        if (!empty($this->options)) {
122            if (isset($this->options['limit'])) {
123                $sql->select()->limit((int)$this->options['limit']);
124            }
125
126            if (isset($this->options['offset'])) {
127                $sql->select()->offset((int)$this->options['offset']);
128            }
129            if (isset($this->options['join'])) {
130                $joins = (is_array($this->options['join']) && isset($this->options['join']['table'])) ?
131                    [$this->options['join']] : $this->options['join'];
132
133                foreach ($joins as $join) {
134                    if (isset($join['type']) && method_exists($sql->select(), $join['type'])) {
135                        $joinMethod = $join['type'];
136                        $sql->select()->{$joinMethod}($join['table'], $join['columns']);
137                    } else {
138                        $sql->select()->leftJoin($join['table'], $join['columns']);
139                    }
140                }
141            }
142            if (isset($this->options['order'])) {
143                if (!is_array($this->options['order'])) {
144                    $orders = (str_contains($this->options['order'], ',')) ?
145                        explode(',', $this->options['order']) : [$this->options['order']];
146                } else {
147                    $orders = $this->options['order'];
148                }
149                foreach ($orders as $order) {
150                    $ord = Parser\Order::parse(trim($order));
151                    $sql->select()->orderBy($ord['by'], $db->escape($ord['order']));
152                }
153            }
154        }
155
156        $db->prepare($sql)
157            ->bindParams($ids)
158            ->execute();
159
160        $rows               = $db->fetchAll();
161        $parentIds          = [];
162        $childRelationships = [];
163
164        $primaryKey = (new $table())->getPrimaryKeys();
165        $primaryKey = (count($primaryKey) == 1) ? reset($primaryKey) : $this->foreignKey;
166
167        foreach ($rows as $row) {
168            $parentIds[] = $row[$primaryKey];
169            if ($toArray === false) {
170                if (!isset($results[$row[$this->foreignKey]])) {
171                    $results[$row[$this->foreignKey]] = new Record\Collection();
172                }
173                $record = new $table();
174                if ($this->children !== null) {
175                    $record->addWith($this->children);
176                }
177                $record->setColumns($row);
178                $results[$row[$this->foreignKey]]->push($record);
179            } else {
180                if (!isset($results[$row[$this->foreignKey]])) {
181                    $results[$row[$this->foreignKey]] = [];
182                }
183                $results[$row[$this->foreignKey]][] = $row;
184            }
185        }
186
187        if (!empty($this->children) && !empty($parentIds)) {
188            foreach ($results as $collection) {
189                foreach ($collection as $record) {
190                    $record->getWithRelationships();
191                    foreach ($record->getRelationships() as $relationship) {
192                        $childRelationships = $relationship->getEagerRelationships($parentIds);
193                    }
194                }
195            }
196        }
197
198        if (!empty($childRelationships)) {
199            $children    = $this->children;
200            $subChildren = null;
201            if (str_contains($children, '.')) {
202                $names       = explode('.', $children);
203                $children    = array_shift($names);
204                $subChildren = implode('.', $names);
205            }
206
207            foreach ($results as $collection) {
208                foreach ($collection as $record) {
209                    if (!empty($subChildren)) {
210                        $record->addWith($subChildren);
211                    }
212                    $rel = (isset($childRelationships[$record[$primaryKey]])) ?
213                        $childRelationships[$record[$primaryKey]] : [];
214
215                    $record->setRelationship($children, $rel);
216                }
217            }
218        }
219
220        return $results;
221    }
222
223}