Completed
Branch master (6f5620)
by Anton
02:08
created

Relations   A

Complexity

Total Complexity 19

Size/Duplication

Total Lines 270
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 4

Test Coverage

Coverage 13.41%

Importance

Changes 0
Metric Value
dl 0
loc 270
ccs 11
cts 82
cp 0.1341
rs 10
c 0
b 0
f 0
wmc 19
lcom 1
cbo 4

9 Methods

Rating   Name   Duplication   Size   Complexity  
A setRelation() 0 5 1
A setRelations() 0 8 1
A getRelations() 0 8 1
B findRelation() 0 24 3
A findRelations() 0 63 3
A addClassMap() 0 4 1
A getModelClass() 0 13 3
A createRow() 0 7 1
B fetch() 0 20 5
1
<?php
2
/**
3
 * Bluz Framework Component
4
 *
5
 * @copyright Bluz PHP Team
6
 * @link      https://github.com/bluzphp/framework
7
 */
8
9
declare(strict_types=1);
10
11
namespace Bluz\Db;
12
13
use Bluz\Db\Exception\RelationNotFoundException;
14
15
/**
16
 * Relations map of Db tables
17
 *
18
 * @package  Bluz\Db
19
 * @author   Anton Shevchuk
20
 */
21
class Relations
22
{
23
    /**
24
     * Relation stack, i.e.
25
     * <code>
26
     *     [
27
     *         'Model1:Model2' => ['Model1'=>'foreignKey', 'Model2'=>'primaryKey'],
28
     *         'Pages:Users' => ['Pages'=>'userId', 'Users'=>'id'],
29
     *         'PagesTags:Pages' => ['PagesTags'=>'pageId', 'Pages'=>'id'],
30
     *         'PagesTags:Tags' => ['PagesTags'=>'tagId', 'Tags'=>'id'],
31
     *         'Pages:Tags' => ['PagesTags'],
32
     *     ]
33
     * </code>
34
     *
35
     * @var array
36
     */
37
    protected static $relations;
38
39
    /**
40
     * Class map, i.e.
41
     * <code>
42
     *     [
43
     *         'Pages' => '\Application\Pages\Table',
44
     *         'Users' => '\Application\Users\Table',
45
     *     ]
46
     * </code>
47
     *
48
     * @var array
49
     */
50
    protected static $modelClassMap;
51
52
    /**
53
     * Setup relation between two models
54
     *
55
     * @param  string $modelOne
56
     * @param  string $keyOne
57
     * @param  string $modelTwo
58
     * @param  string $keyTwo
59
     *
60
     * @return void
61
     */
62
    public static function setRelation($modelOne, $keyOne, $modelTwo, $keyTwo)
63
    {
64
        $relations = [$modelOne => $keyOne, $modelTwo => $keyTwo];
65
        self::setRelations($modelOne, $modelTwo, $relations);
66
    }
67
68
    /**
69
     * Setup multi relations
70
     *
71
     * @param  string $modelOne
72
     * @param  string $modelTwo
73
     * @param  array  $relations
74
     *
75
     * @return void
76
     */
77
    public static function setRelations($modelOne, $modelTwo, $relations)
78
    {
79
        $name = [$modelOne, $modelTwo];
80
        sort($name);
81
        $name = implode(':', $name);
82
        // create record in static variable
83
        self::$relations[$name] = $relations;
84
    }
85
86
    /**
87
     * Get relations
88
     *
89
     * @param  string $modelOne
90
     * @param  string $modelTwo
91
     *
92
     * @return array|false
93
     */
94
    public static function getRelations($modelOne, $modelTwo)
95
    {
96
        $name = [$modelOne, $modelTwo];
97
        sort($name);
98
        $name = implode(':', $name);
99
100
        return self::$relations[$name] ?? false;
101
    }
102
103
    /**
104
     * findRelation
105
     *
106
     * @param  Row    $row
107
     * @param  string $relation
108
     *
109
     * @return array
110
     * @throws Exception\RelationNotFoundException
111
     */
112 1
    public static function findRelation($row, $relation)
113
    {
114 1
        $model = $row->getTable()->getModel();
115
116
        /** @var \Bluz\Db\Table $relationTable */
117 1
        $relationTable = Relations::getModelClass($relation);
118
        $relationTable::getInstance();
119
120
        if (!$relations = Relations::getRelations($model, $relation)) {
121
            throw new RelationNotFoundException(
122
                "Relations between model `$model` and `$relation` is not defined"
123
            );
124
        }
125
126
        // check many-to-many relations
127
        if (count($relations) == 1) {
128
            $relations = Relations::getRelations($model, current($relations));
129
        }
130
131
        $field = $relations[$model];
132
        $key = $row->{$field};
133
134
        return Relations::findRelations($model, $relation, [$key]);
135
    }
136
137
    /**
138
     * Find Relations between two tables
139
     *
140
     * @param  string $modelOne Table
141
     * @param  string $modelTwo Target table
142
     * @param  array  $keys     Keys from first table
143
     *
144
     * @return array
145
     * @throws Exception\RelationNotFoundException
146
     */
147
    public static function findRelations($modelOne, $modelTwo, $keys)
148
    {
149
        $keys = (array)$keys;
150
        if (!$relations = self::getRelations($modelOne, $modelTwo)) {
151
            throw new RelationNotFoundException("Relations between model `$modelOne` and `$modelTwo` is not defined");
152
        }
153
154
        /* @var Table $tableOneClass name */
155
        $tableOneClass = self::getModelClass($modelOne);
156
157
        /* @var string $tableOneName */
158
        $tableOneName = $tableOneClass::getInstance()->getName();
159
160
        /* @var Table $tableTwoClass name */
161
        $tableTwoClass = self::getModelClass($modelTwo);
162
163
        /* @var string $tableTwoName */
164
        $tableTwoName = $tableTwoClass::getInstance()->getName();
165
166
        /* @var Query\Select $tableTwoSelect */
167
        $tableTwoSelect = $tableTwoClass::getInstance()->select();
168
169
        // check many to many relation
170
        if (is_int(array_keys($relations)[0])) {
171
            // many to many relation over third table
172
            $modelThree = $relations[0];
173
174
            // relations between target table and third table
175
            $relations = self::getRelations($modelTwo, $modelThree);
176
177
            /* @var Table $tableThreeClass name */
178
            $tableThreeClass = self::getModelClass($modelThree);
179
180
            /* @var string $tableTwoName */
181
            $tableThreeName = $tableThreeClass::getInstance()->getName();
182
183
            // join it to query
184
            $tableTwoSelect->join(
185
                $tableTwoName,
186
                $tableThreeName,
187
                $tableThreeName,
188
                $tableTwoName . '.' . $relations[$modelTwo] . '=' . $tableThreeName . '.' . $relations[$modelThree]
189
            );
190
191
            // relations between source table and third table
192
            $relations = self::getRelations($modelOne, $modelThree);
193
194
            // join it to query
195
            $tableTwoSelect->join(
196
                $tableThreeName,
197
                $tableOneName,
198
                $tableOneName,
199
                $tableThreeName . '.' . $relations[$modelThree] . '=' . $tableOneName . '.' . $relations[$modelOne]
200
            );
201
202
            // set source keys
203
            $tableTwoSelect->where($tableOneName . '.' . $relations[$modelOne] . ' IN (?)', $keys);
204
        } else {
205
            // set source keys
206
            $tableTwoSelect->where($relations[$modelTwo] . ' IN (?)', $keys);
207
        }
208
        return $tableTwoSelect->execute();
209
    }
210
211
    /**
212
     * Add information about model's classes
213
     *
214
     * @param  string $model
215
     * @param  string $className
216
     *
217
     * @return void
218
     */
219 2
    public static function addClassMap($model, $className)
220
    {
221 2
        self::$modelClassMap[$model] = $className;
222 2
    }
223
224
    /**
225
     * Get information about Model classes
226
     *
227
     * @param  string $model
228
     *
229
     * @return string
230
     * @throws Exception\RelationNotFoundException
231
     */
232 1
    public static function getModelClass($model)
233
    {
234 1
        if (!isset(self::$modelClassMap[$model])) {
235
            // try to detect
236 1
            $className = '\\Application\\' . $model . '\\Table';
237
238 1
            if (!class_exists($className)) {
239 1
                throw new RelationNotFoundException("Related class for model `$model` not found");
240
            }
241
            self::$modelClassMap[$model] = $className;
242
        }
243
        return self::$modelClassMap[$model];
244
    }
245
246
    /**
247
     * Get information about Table classes
248
     *
249
     * @param  string $modelName
250
     * @param  array  $data
251
     *
252
     * @return Row
253
     * @throws Exception\RelationNotFoundException
254
     */
255
    public static function createRow($modelName, $data)
256
    {
257
        $tableClass = self::getModelClass($modelName);
258
259
        /* @var Table $tableClass name */
260
        return $tableClass::getInstance()->create($data);
261
    }
262
263
    /**
264
     * Fetch by Divider
265
     *
266
     * @param  array $input
267
     *
268
     * @return array
269
     */
270
    public static function fetch($input)
271
    {
272
        $output = [];
273
        $map = [];
274
        foreach ($input as $i => $row) {
275
            $model = '';
276
            foreach ($row as $key => $value) {
277
                if (strpos($key, '__') === 0) {
278
                    $model = substr($key, 2);
279
                    continue;
280
                }
281
                $map[$i][$model][$key] = $value;
282
            }
283
            foreach ($map[$i] as $model => &$data) {
284
                $data = self::createRow($model, $data);
285
            }
286
            $output[] = $map;
287
        }
288
        return $output;
289
    }
290
}
291