Passed
Push — main ( 9a75d9...911b53 )
by Pranjal
03:04
created

Database::createRecords()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 2
eloc 3
c 2
b 0
f 0
nc 2
nop 1
dl 0
loc 6
rs 10
1
<?php
2
declare(strict_types=1);
3
4
namespace Scrawler\Arca;
5
use Doctrine\DBAL\Types\Type;
6
use Dunglas\DoctrineJsonOdm\Serializer;
7
use Dunglas\DoctrineJsonOdm\Type\JsonDocumentType;
8
use Symfony\Component\Serializer\Encoder\JsonEncoder;
9
use Symfony\Component\Serializer\Normalizer\ArrayDenormalizer;
10
use Symfony\Component\Serializer\Normalizer\BackedEnumNormalizer;
11
use Symfony\Component\Serializer\Normalizer\UidNormalizer;
12
use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer;
13
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
14
15
16
/**
17
 * 
18
 * Class that manages all interaction with database
19
 */
20
class Database
21
{
22
    /**
23
     * Store the instance of current connection
24
     * @var \Scrawler\Arca\Connection
25
     */
26
    private Connection $connection;
27
    /**
28
     * When $isFrozen is set to true tables are not updated/created
29
     * @var bool
30
     */
31
    private bool $isFroozen = false;
32
    
33
34
    /**
35
     * Create a new Database instance
36
     * @param \Scrawler\Arca\Connection $connection
37
     */
38
    public function __construct(Connection $connection)
39
    {
40
        $this->connection = $connection;
41
        $this->registerEvents();
42
        $this->registerJsonDocumentType();
43
44
    }
45
46
    private function registerJsonDocumentType(): void
47
    {
48
        if (!Type::hasType('json_document')) {
49
            Type::addType('json_document', JsonDocumentType::class);
50
            Type::getType('json_document')->setSerializer(
0 ignored issues
show
Bug introduced by
The method setSerializer() does not exist on Doctrine\DBAL\Types\Type. It seems like you code against a sub-type of Doctrine\DBAL\Types\Type such as Dunglas\DoctrineJsonOdm\Type\JsonDocumentType. ( Ignorable by Annotation )

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

50
            Type::getType('json_document')->/** @scrutinizer ignore-call */ setSerializer(
Loading history...
51
                new Serializer([new BackedEnumNormalizer(), new UidNormalizer(), new DateTimeNormalizer(), new ArrayDenormalizer(), new ObjectNormalizer()], [new JsonEncoder()])
52
            );
53
        }
54
    }
55
56
    /**
57
     * Register events
58
     * @return void
59
     */
60
    public function registerEvents(): void
61
    {
62
        Event::subscribeTo('__arca.model.save.'.$this->connection->getConnectionId(), function ($model) {
63
            return $this->save($model);
64
        });
65
        Event::subscribeTo('__arca.model.delete.'.$this->connection->getConnectionId(), function ($model) {
66
            return $this->delete($model);
67
        });
68
    }
69
70
    /**
71
     * Executes an SQL query and returns the number of row affected
72
     *
73
     * @param string $sql
74
     * @param array<mixed> $params
75
     * @return int|numeric-string
0 ignored issues
show
Documentation Bug introduced by
The doc comment int|numeric-string at position 2 could not be parsed: Unknown type name 'numeric-string' at position 2 in int|numeric-string.
Loading history...
76
     */
77
    public function exec(string $sql, array $params=array()): int|string
78
    {
79
        return  $this->connection->executeStatement($sql, $params);
0 ignored issues
show
Bug introduced by
The method executeStatement() does not exist on Scrawler\Arca\Connection. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

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

79
        return  $this->connection->/** @scrutinizer ignore-call */ executeStatement($sql, $params);
Loading history...
80
    }
81
82
    /**
83
     * Returns array of data from SQL select statement
84
     *
85
     * @param string $sql
86
     * @param array<mixed> $params
87
     * @return array<int,array<string,mixed>>
88
     */
89
    public function getAll(string $sql, array $params=[]): array
90
    {
91
        return  $this->connection->executeQuery($sql, $params)->fetchAllAssociative();
0 ignored issues
show
Bug introduced by
The method executeQuery() does not exist on Scrawler\Arca\Connection. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

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

91
        return  $this->connection->/** @scrutinizer ignore-call */ executeQuery($sql, $params)->fetchAllAssociative();
Loading history...
92
    }
93
94
    /**
95
     * Creates model from name
96
     *
97
     * @param string $name
98
     * @return \Scrawler\Arca\Model
99
     */
100
    public function create(string $name) : Model
101
    {
102
        return $this->connection->getModelManager()->create($name);
103
    }
104
105
    /**
106
     * Save model into database
107
     *
108
     * @param \Scrawler\Arca\Model $model
109
     * @return mixed returns int for id and string for uuid
110
     */
111
    public function save(\Scrawler\Arca\Model $model) : mixed
112
    {
113
        if ($model->hasForeign('oto')) {
114
            $this->saveForeignOto($model);
115
        }
116
        
117
        $this->createTables($model);
118
        $this->connection->beginTransaction();
0 ignored issues
show
Bug introduced by
The method beginTransaction() does not exist on Scrawler\Arca\Connection. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

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

118
        $this->connection->/** @scrutinizer ignore-call */ 
119
                           beginTransaction();
Loading history...
119
120
        try {
121
            $id = $this->createRecords($model);
122
            $model->set('id',$id);
123
            $model->setLoaded();
124
            $this->connection->commit();
0 ignored issues
show
Bug introduced by
The method commit() does not exist on Scrawler\Arca\Connection. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

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

124
            $this->connection->/** @scrutinizer ignore-call */ 
125
                               commit();
Loading history...
125
        } catch (\Exception $e) {
126
            $this->connection->rollBack();
0 ignored issues
show
Bug introduced by
The method rollBack() does not exist on Scrawler\Arca\Connection. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

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

126
            $this->connection->/** @scrutinizer ignore-call */ 
127
                               rollBack();
Loading history...
127
            throw $e;
128
        }
129
        
130
        if ($model->hasForeign('otm')) {
131
            $this->saveForeignOtm($model, $id);
132
        }
133
134
        if ($model->hasForeign('mtm')) {
135
            $this->saveForeignMtm($model, $id);
136
        }
137
138
        return $id;
139
    }
140
141
    /**
142
     * Create tables
143
     * @param \Scrawler\Arca\Model $model
144
     * @return void
145
     */
146
    private function createTables($model): void
147
    {
148
        if (!$this->isFroozen) {
149
            $table = $this->connection->getTableManager()->createTable($model);
150
            $this->connection->getTableManager()->saveOrUpdateTable($model->getName(), $table);
151
        }
152
    }
153
154
    /**
155
     * Create records
156
     * @param \Scrawler\Arca\Model $model
157
     * @return mixed
158
     */
159
    private function createRecords(Model $model) : mixed
160
    {
161
        if ($model->isLoaded()) {
162
            return $this->connection->getRecordManager()->update($model);
163
        }
164
        return $this->connection->getRecordManager()->insert($model);
165
    }
166
167
168
    /**
169
     * Save One to One related model into database
170
     * @param Model $model
171
     * @return void
172
     */
173
    private function saveForeignOto(\Scrawler\Arca\Model $model): void
174
    {
175
        foreach ($model->getForeignModels('oto') as $foreign) {
176
            $this->createTables($foreign);
177
        }
178
179
        $this->connection->beginTransaction();
180
        try {
181
            foreach ($model->getForeignModels('oto') as $foreign) {
182
                $id = $this->createRecords($foreign);
183
                $name = $foreign->getName().'_id';
184
                $model->$name = $id;
185
            }
186
            $this->connection->commit();
187
        } catch (\Exception $e) {
188
            $this->connection->rollBack();
189
            throw $e;
190
        }
191
    }
192
193
194
    /**
195
     * Save One to Many related model into database
196
     * @param Model $model
197
     * @param mixed $id
198
     * @return void
199
     */
200
    private function saveForeignOtm(\Scrawler\Arca\Model $model, mixed $id): void
201
    {
202
        foreach ($model->getForeignModels('otm') as $foreigns) {
203
            foreach ($foreigns as $foreign) {
204
                $key = $model->getName().'_id';
205
                $foreign->$key = $id;
206
                $this->createTables($foreign);
207
            }
208
        }
209
        $this->connection->beginTransaction();
210
        try {
211
            foreach ($model->getForeignModels('otm') as $foreigns) {
212
                foreach ($foreigns as $foreign) {
213
                    $this->createRecords($foreign);
214
                }
215
            }
216
            $this->connection->commit();
217
        } catch (\Exception $e) {
218
            $this->connection->rollBack();
219
            throw $e;
220
        }
221
    }
222
223
224
    /**
225
     * Save Many to Many related model into database
226
     * @param Model $model
227
     * @param mixed $id
228
     * @return void
229
     */
230
    private function saveForeignMtm(\Scrawler\Arca\Model $model, mixed $id): void
231
    {
232
        foreach ($model->getForeignModels('mtm') as $foreigns) {
233
            foreach ($foreigns as $foreign) {
234
                $model_id = $model->getName().'_id';
235
                $foreign_id = $foreign->getName().'_id';
236
                $relational_table = $this->create($model->getName().'_'.$foreign->getName());
237
                if ($this->isUsingUUID()) {
238
                    $relational_table->$model_id = "";
239
                    $relational_table->$foreign_id = "";
240
                } else {
241
                    $relational_table->$model_id = 0;
242
                    $relational_table->$foreign_id = 0;
243
                }
244
                $this->createTables($relational_table);
245
                $this->createTables($foreign);
246
            }
247
        }
248
        $this->connection->beginTransaction();
249
        try {
250
            foreach ($model->getForeignModels('mtm') as $foreigns) {
251
                foreach ($foreigns as $foreign) {
252
                    $rel_id = $this->createRecords($foreign);
253
                    $model_id = $model->getName().'_id';
254
                    $foreign_id = $foreign->getName().'_id';
255
                    $relational_table = $this->create($model->getName().'_'.$foreign->getName());
256
                    $relational_table->$model_id = $id;
257
                    $relational_table->$foreign_id = $rel_id;
258
                    $this->createRecords($relational_table);
259
                }
260
            }
261
            $this->connection->commit();
262
        } catch (\Exception $e) {
263
            $this->connection->rollBack();
264
            throw $e;
265
        }
266
    }
267
268
    /**
269
     * Delete record from database
270
     *
271
     * @param \Scrawler\Arca\Model $model
272
     * @return mixed
273
     */
274
    public function delete(\Scrawler\Arca\Model $model) : mixed
275
    {
276
        return $this->connection->getRecordManager()->delete($model);
277
    }
278
279
    /**
280
     * Get collection of all records from table
281
     * @param string $table
282
     * @return Collection
283
     */
284
    public function get(string $table) : Collection
285
    {
286
       
287
        return $this->connection->getRecordManager()->getAll($table);
288
    }
289
290
    /**
291
     * Get single record
292
     * @param string $table
293
     * @param mixed $id
294
     * @return Model
295
     */
296
    public function getOne(string $table, mixed $id) : Model|null
297
    {
298
        return $this->connection->getRecordManager()->getById($table, $id);
299
    }
300
301
    /**
302
     * Returns QueryBuilder to build query for finding data
303
     * Eg: db()->find('user')->where('active = 1')->get();
304
     *
305
     * @param string $name
306
     * @return QueryBuilder
307
     */
308
    public function find(string $name) : QueryBuilder
309
    {
310
        return $this->connection->getRecordManager()->find($name);
311
    }
312
313
    /**
314
     * Freezes table for production
315
     * @return void
316
     */
317
    public function freeze() : void
318
    {
319
        $this->isFroozen = true;
320
    }
321
322
    /**
323
     * Helper function to unfreeze table
324
     * @return void
325
     */
326
    public function unfreeze() : void
327
    {
328
        $this->isFroozen = false;
329
    }
330
331
    /**
332
     * Checks if database is currently using uuid rather than id
333
     * @return bool
334
     */
335
    public function isUsingUUID() : bool
336
    {
337
        return $this->connection->isUsingUUID();
338
    }
339
340
    /**
341
     * Returns the current connection
342
     * @return Connection
343
     */
344
    public function getConnection() : Connection
345
    {
346
        return $this->connection;
347
    }
348
349
    /**
350
     * Check if tables exist
351
     * @param array<int,string> $tables
352
     * @return bool
353
     */
354
    public function tablesExist(array $tables) : bool
355
    {
356
357
        return $this->connection->getSchemaManager()->tablesExist($tables);
358
    }
359
360
    /**
361
     * Check if table exists
362
     * @param string $table
363
     * @return bool
364
     */
365
    public function tableExists(string $table) : bool
366
    {
367
      
368
        return $this->connection->getSchemaManager()->tableExists($table);
369
    }
370
}
371