Passed
Push — main ( abdbfe...29aefa )
by Pranjal
02:38
created

WriteManager   A

Complexity

Total Complexity 30

Size/Duplication

Total Lines 252
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 104
c 1
b 0
f 0
dl 0
loc 252
rs 10
wmc 30

14 Methods

Rating   Name   Duplication   Size   Complexity  
A handleRelations() 0 8 3
A createRecords() 0 11 3
A saveForeignOneToMany() 0 13 3
A executeInTransaction() 0 11 2
A saveForeignOneToOne() 0 12 3
A ensureTableExists() 0 7 2
A createConstraintsOto() 0 12 2
A finalizeModel() 0 4 1
A save() 0 19 2
A saveForeignManyToMany() 0 36 4
A createTable() 0 5 2
A createConstraintsOtm() 0 7 1
A createConstraintsMtm() 0 12 1
A __construct() 0 7 1
1
<?php
2
3
/*
4
 * This file is part of the Scrawler package.
5
 *
6
 * (c) Pranjal Pandey <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
declare(strict_types=1);
12
13
namespace Scrawler\Arca\Manager;
14
15
use Doctrine\DBAL\Connection;
16
use Scrawler\Arca\Config;
17
use Scrawler\Arca\Exception\InvalidIdException;
18
use Scrawler\Arca\Model;
19
20
final class WriteManager
21
{
22
    private const RELATION_ONE_TO_ONE = 'oto';
23
    private const RELATION_ONE_TO_MANY = 'otm';
24
    private const RELATION_MANY_TO_MANY = 'mtm';
25
    private const ID_FIELD = 'id';
26
    private const ID_SUFFIX = '_id';
27
28
    public function __construct(
29
        private readonly Connection $connection,
30
        private readonly TableManager $tableManager,
31
        private readonly RecordManager $recordManager,
32
        private readonly ModelManager $modelManager,
33
        private readonly Config $config,
34
    ) {
35
    }
36
37
    /**
38
     * Save model into database.
39
     *
40
     * @template T of mixed
41
     *
42
     * @return T
43
     *
44
     * @throws InvalidIdException
45
     */
46
    public function save(Model $model): mixed
47
    {
48
        if ($model->hasForeign(self::RELATION_ONE_TO_ONE)) {
49
            $this->saveForeignOneToOne($model);
50
        }
51
52
        $this->ensureTableExists($model);
53
54
        $id = $this->executeInTransaction(function () use ($model) {
55
            $id = $this->createRecords($model);
56
            $model->set(self::ID_FIELD, $id);
57
58
            return $id;
59
        });
60
61
        $this->handleRelations($model);
62
        $this->finalizeModel($model);
63
64
        return $id;
65
    }
66
67
    private function handleRelations(Model $model): void
68
    {
69
        if ($model->hasForeign(self::RELATION_ONE_TO_MANY)) {
70
            $this->saveForeignOneToMany($model);
71
        }
72
73
        if ($model->hasForeign(self::RELATION_MANY_TO_MANY)) {
74
            $this->saveForeignManyToMany($model);
75
        }
76
    }
77
78
    private function finalizeModel(Model $model): void
79
    {
80
        $model->cleanModel();
81
        $model->setLoaded();
82
    }
83
84
    private function executeInTransaction(callable $callback): mixed
85
    {
86
        $this->connection->beginTransaction();
87
        try {
88
            $result = $callback();
89
            $this->connection->commit();
90
91
            return $result;
92
        } catch (\Exception $e) {
93
            $this->connection->rollBack();
94
            throw $e;
95
        }
96
    }
97
98
    /**
99
     * Create tables.
100
     *
101
     * @param array<TableConstraint> $constraints
102
     */
103
    private function createTable(Model $model, array $constraints = []): void
104
    {
105
        if (!$this->config->isFrozen()) {
106
            $table = $this->tableManager->createTable($model, $constraints);
107
            $this->tableManager->saveOrUpdateTable($model->getName(), $table);
108
        }
109
    }
110
111
    /**
112
     * Add constraint to table.
113
     *
114
     * @return array<TableConstraint>
115
     */
116
    private function createConstraintsOto(Model $model): array
117
    {
118
        $constraints = [];
119
        foreach ($model->getForeignModels('oto') as $foreign) {
120
            $constraints[] = new TableConstraint(
121
                $foreign->getName(),
0 ignored issues
show
Bug introduced by
The method getName() does not exist on Traversable. It seems like you code against a sub-type of Traversable such as SimpleXMLElement or Scrawler\Arca\Model or Pest\Arch\Layer or RectorPrefix202503\Nette\Utils\Html or RectorPrefix202503\Nette\Utils\ArrayList or RectorPrefix202503\Nette\Utils\Finder or PHPUnit\Architecture\Elements\Layer\Layer or SimpleXMLElement or RectorPrefix202503\Nette\Iterators\CachingIterator or SimpleXMLElement or SimpleXMLIterator. ( Ignorable by Annotation )

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

121
                $foreign->/** @scrutinizer ignore-call */ 
122
                          getName(),
Loading history...
122
                $foreign->getName().'_id',
123
                'id',
124
            );
125
        }
126
127
        return $constraints;
128
    }
129
130
    /**
131
     * Add constraint to table.
132
     *
133
     * @return array<TableConstraint>
134
     */
135
    private function createConstraintsOtm(Model $model): array
136
    {
137
        return [
138
            new TableConstraint(
139
                $model->getName(),
140
                $model->getName().'_id',
141
                'id'
142
            ),
143
        ];
144
    }
145
146
    /**
147
     * Add constraint to table.
148
     *
149
     * @return array<TableConstraint>
150
     */
151
    private function createConstraintsMtm(Model $model, Model $foreign): array
152
    {
153
        return [
154
            new TableConstraint(
155
                $model->getName(),
156
                $model->getName().'_id',
157
                'id'
158
            ),
159
            new TableConstraint(
160
                $foreign->getName(),
161
                $foreign->getName().'_id',
162
                'id'
163
            ),
164
        ];
165
    }
166
167
    /**
168
     * Create records.
169
     */
170
    private function createRecords(Model $model): mixed
171
    {
172
        if ($model->isLoaded()) {
173
            return $this->recordManager->update($model);
174
        }
175
176
        if ($model->hasIdError()) {
177
            throw new InvalidIdException();
178
        }
179
180
        return $this->recordManager->insert($model);
181
    }
182
183
    /**
184
     * Save One to One related model into database.
185
     */
186
    private function saveForeignOneToOne(Model $model): void
187
    {
188
        foreach ($model->getForeignModels(self::RELATION_ONE_TO_ONE) as $foreign) {
189
            $this->createTable($foreign);
190
        }
191
192
        $this->executeInTransaction(function () use ($model) {
193
            foreach ($model->getForeignModels(self::RELATION_ONE_TO_ONE) as $foreign) {
194
                $id = $this->createRecords($foreign);
195
                $this->finalizeModel($foreign);
196
                $name = $foreign->getName().self::ID_SUFFIX;
197
                $model->$name = $id;
198
            }
199
        });
200
    }
201
202
    /**
203
     * Save One to Many related model into database.
204
     */
205
    private function saveForeignOneToMany(Model $model): void
206
    {
207
        $id = $model->getId();
208
        foreach ($model->getForeignModels(self::RELATION_ONE_TO_MANY) as $foreign) {
209
            $key = $model->getName().self::ID_SUFFIX;
210
            $foreign->$key = $id;
211
            $this->createTable($foreign, $this->createConstraintsOtm($model));
212
        }
213
214
        $this->executeInTransaction(function () use ($model) {
215
            foreach ($model->getForeignModels(self::RELATION_ONE_TO_MANY) as $foreign) {
216
                $this->createRecords($foreign);
217
                $this->finalizeModel($foreign);
218
            }
219
        });
220
    }
221
222
    /**
223
     * Save Many to Many related model into database.
224
     */
225
    private function saveForeignManyToMany(Model $model): void
226
    {
227
        $id = $model->getId();
228
        foreach ($model->getForeignModels(self::RELATION_MANY_TO_MANY) as $foreign) {
229
            $model_id = $model->getName().self::ID_SUFFIX;
230
            $foreign_id = $foreign->getName().self::ID_SUFFIX;
231
            $relational_table = $this->modelManager->create(
232
                $model->getName().'_'.$foreign->getName()
233
            );
234
235
            $default_id = $this->config->isUsingUUID() ? '' : 0;
236
            $relational_table->$model_id = $default_id;
237
            $relational_table->$foreign_id = $default_id;
238
239
            $this->createTable($foreign);
240
            $this->createTable(
241
                $relational_table,
242
                $this->createConstraintsMtm($model, $foreign)
243
            );
244
        }
245
246
        $this->executeInTransaction(function () use ($model, $id) {
247
            foreach ($model->getForeignModels(self::RELATION_MANY_TO_MANY) as $foreign) {
248
                $rel_id = $this->createRecords($foreign);
249
                $this->finalizeModel($foreign);
250
251
                $model_id = $model->getName().self::ID_SUFFIX;
252
                $foreign_id = $foreign->getName().self::ID_SUFFIX;
253
254
                $relational_table = $this->modelManager->create(
255
                    $model->getName().'_'.$foreign->getName()
256
                );
257
                $relational_table->$model_id = $id;
258
                $relational_table->$foreign_id = $rel_id;
259
260
                $this->createRecords($relational_table);
261
            }
262
        });
263
    }
264
265
    private function ensureTableExists(Model $model): void
266
    {
267
        $constraints = [];
268
        if ($model->hasForeign(self::RELATION_ONE_TO_ONE)) {
269
            $constraints = $this->createConstraintsOto($model);
270
        }
271
        $this->createTable($model, $constraints);
272
    }
273
}
274