Relations::setRelations()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 4
nc 1
nop 3
dl 0
loc 7
ccs 0
cts 5
cp 0
crap 2
rs 10
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * Bluz Framework Component
5
 *
6
 * @copyright Bluz PHP Team
7
 * @link      https://github.com/bluzphp/framework
8
 */
9
10
declare(strict_types=1);
11
12
namespace Bluz\Db;
13
14
use Bluz\Db\Exception\RelationNotFoundException;
15
16
/**
17
 * Relations map of Db tables
18
 *
19
 * @package  Bluz\Db
20
 * @author   Anton Shevchuk
21
 */
22
class Relations
23
{
24
    /**
25
     * Relation stack, i.e.
26
     * <code>
27
     *     [
28
     *         'Model1:Model2' => ['Model1'=>'foreignKey', 'Model2'=>'primaryKey'],
29
     *         'Pages:Users' => ['Pages'=>'userId', 'Users'=>'id'],
30
     *         'PagesTags:Pages' => ['PagesTags'=>'pageId', 'Pages'=>'id'],
31
     *         'PagesTags:Tags' => ['PagesTags'=>'tagId', 'Tags'=>'id'],
32
     *         'Pages:Tags' => ['PagesTags'],
33
     *     ]
34
     * </code>
35
     *
36
     * @var array
37
     */
38
    protected static $relations;
39
40
    /**
41
     * Class map, i.e.
42
     * <code>
43
     *     [
44
     *         'Pages' => '\Application\Pages\Table',
45
     *         'Users' => '\Application\Users\Table',
46
     *     ]
47
     * </code>
48
     *
49
     * @var array
50
     */
51
    protected static $modelClassMap;
52
53
    /**
54
     * Setup relation between two models
55
     *
56
     * @param string $modelOne
57
     * @param string $keyOne
58
     * @param string $modelTwo
59
     * @param string $keyTwo
60
     *
61
     * @return void
62
     */
63
    public static function setRelation(string $modelOne, string $keyOne, string $modelTwo, string $keyTwo): void
64
    {
65
        $relations = [$modelOne => $keyOne, $modelTwo => $keyTwo];
66
        self::setRelations($modelOne, $modelTwo, $relations);
67
    }
68
69
    /**
70
     * Setup multi relations
71
     *
72
     * @param string $modelOne
73
     * @param string $modelTwo
74
     * @param array $relations
75
     *
76
     * @return void
77
     */
78
    public static function setRelations(string $modelOne, string $modelTwo, array $relations): void
79
    {
80
        $name = [$modelOne, $modelTwo];
81
        sort($name);
82
        $name = implode(':', $name);
83
        // create record in static variable
84
        self::$relations[$name] = $relations;
85
    }
86
87
    /**
88
     * Get relations
89
     *
90
     * @param string $modelOne
91
     * @param string $modelTwo
92
     *
93
     * @return array|false
94
     */
95
    public static function getRelations(string $modelOne, string $modelTwo)
96
    {
97
        $name = [$modelOne, $modelTwo];
98
        sort($name);
99
        $name = implode(':', $name);
100
101
        return self::$relations[$name] ?? false;
102
    }
103
104
    /**
105
     * findRelation
106
     *
107
     * @param Row $row
108
     * @param string $relation
109
     *
110
     * @return array
111
     * @throws Exception\TableNotFoundException
112
     * @throws Exception\RelationNotFoundException
113
     */
114 1
    public static function findRelation(Row $row, string $relation): array
115
    {
116 1
        $model = $row->getTable()->getModel();
117
118
        /** @var \Bluz\Db\Table $relationTable */
119 1
        $relationTable = self::getModelClass($relation);
120
        $relationTable::getInstance();
121
122
        if (!$relations = self::getRelations($model, $relation)) {
123
            throw new RelationNotFoundException(
124
                "Relations between model `$model` and `$relation` is not defined"
125
            );
126
        }
127
128
        // check many-to-many relations
129
        if (count($relations) === 1) {
130
            $relations = self::getRelations($model, current($relations));
131
        }
132
133
        $field = $relations[$model];
134
        $key = $row->{$field};
135
136
        return self::findRelations($model, $relation, [$key]);
137
    }
138
139
    /**
140
     * Find Relations between two tables
141
     *
142
     * @param string $modelOne Table
143
     * @param string $modelTwo Target table
144
     * @param array $keys     Keys from first table
145
     *
146
     * @return array
147
     * @throws Exception\RelationNotFoundException
148
     */
149
    public static function findRelations(string $modelOne, string $modelTwo, array $keys): array
150
    {
151
        $keys = (array)$keys;
152
        if (!$relations = self::getRelations($modelOne, $modelTwo)) {
153
            throw new RelationNotFoundException("Relations between model `$modelOne` and `$modelTwo` is not defined");
154
        }
155
156
        /* @var Table $tableOneClass name */
157
        $tableOneClass = self::getModelClass($modelOne);
158
159
        /* @var string $tableOneName */
160
        $tableOneName = $tableOneClass::getInstance()->getName();
161
162
        /* @var Table $tableTwoClass name */
163
        $tableTwoClass = self::getModelClass($modelTwo);
164
165
        /* @var string $tableTwoName */
166
        $tableTwoName = $tableTwoClass::getInstance()->getName();
167
168
        /* @var Query\Select $tableTwoSelect */
169
        $tableTwoSelect = $tableTwoClass::getInstance()::select();
170
171
        // check many to many relation
172
        if (is_int(\array_keys($relations)[0])) {
173
            // many to many relation over third table
174
            $modelThree = $relations[0];
175
176
            // relations between target table and third table
177
            $relations = self::getRelations($modelTwo, $modelThree);
178
179
            /* @var Table $tableThreeClass name */
180
            $tableThreeClass = self::getModelClass($modelThree);
181
182
            /* @var string $tableTwoName */
183
            $tableThreeName = $tableThreeClass::getInstance()->getName();
184
185
            // join it to query
186
            $tableTwoSelect->join(
187
                $tableTwoName,
188
                $tableThreeName,
189
                $tableThreeName,
190
                $tableTwoName . '.' . $relations[$modelTwo] . '=' . $tableThreeName . '.' . $relations[$modelThree]
191
            );
192
193
            // relations between source table and third table
194
            $relations = self::getRelations($modelOne, $modelThree);
195
196
            // join it to query
197
            $tableTwoSelect->join(
198
                $tableThreeName,
199
                $tableOneName,
200
                $tableOneName,
201
                $tableThreeName . '.' . $relations[$modelThree] . '=' . $tableOneName . '.' . $relations[$modelOne]
202
            );
203
204
            // set source keys
205
            $tableTwoSelect->where($tableOneName . '.' . $relations[$modelOne] . ' IN (?)', $keys);
0 ignored issues
show
Bug introduced by
$keys of type array is incompatible with the type string expected by parameter $conditions of Bluz\Db\Query\Select::where(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

205
            $tableTwoSelect->where($tableOneName . '.' . $relations[$modelOne] . ' IN (?)', /** @scrutinizer ignore-type */ $keys);
Loading history...
206
        } else {
207
            // set source keys
208
            $tableTwoSelect->where($relations[$modelTwo] . ' IN (?)', $keys);
209
        }
210
        return $tableTwoSelect->execute();
211
    }
212
213
    /**
214
     * Add information about model's classes
215
     *
216
     * @param string $model
217
     * @param string $className
218
     *
219
     * @return void
220
     */
221 2
    public static function addClassMap(string $model, string $className): void
222
    {
223 2
        self::$modelClassMap[$model] = $className;
224 2
    }
225
226
    /**
227
     * Get information about Model classes
228
     *
229
     * @param string $model
230
     *
231
     * @return string
232
     * @throws Exception\RelationNotFoundException
233
     */
234 1
    public static function getModelClass(string $model): string
235
    {
236 1
        if (!isset(self::$modelClassMap[$model])) {
237
            // try to detect
238 1
            $className = '\\Application\\' . $model . '\\Table';
239
240 1
            if (!class_exists($className)) {
241 1
                throw new RelationNotFoundException("Related class for model `$model` not found");
242
            }
243
            self::$modelClassMap[$model] = $className;
244
        }
245
        return self::$modelClassMap[$model];
246
    }
247
248
    /**
249
     * Get information about Table classes
250
     *
251
     * @param string $modelName
252
     * @param array $data
253
     *
254
     * @return RowInterface
255
     * @throws Exception\RelationNotFoundException
256
     */
257
    public static function createRow(string $modelName, array $data): RowInterface
258
    {
259
        $tableClass = self::getModelClass($modelName);
260
261
        /* @var Table $tableClass name */
262
        return $tableClass::getInstance()::create($data);
263
    }
264
265
    /**
266
     * Fetch by Divider
267
     *
268
     * @param array $input
269
     *
270
     * @return array
271
     * @throws Exception\RelationNotFoundException
272
     */
273
    public static function fetch(array $input): array
274
    {
275
        $output = [];
276
        $map = [];
277
        foreach ($input as $i => $row) {
278
            $model = '';
279
            foreach ($row as $key => $value) {
280
                if (strpos($key, '__') === 0) {
281
                    $model = substr($key, 2);
282
                    continue;
283
                }
284
                $map[$i][$model][$key] = $value;
285
            }
286
            foreach ($map[$i] as $model => &$data) {
287
                $data = self::createRow($model, $data);
288
            }
289
            $output[] = $map;
290
        }
291
        return $output;
292
    }
293
}
294