WriteManager   A
last analyzed

Complexity

Total Complexity 31

Size/Duplication

Total Lines 253
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 106
dl 0
loc 253
rs 9.92
c 1
b 0
f 0
wmc 31

14 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 7 1
A createRecords() 0 11 3
A saveForeignOneToMany() 0 13 3
A saveForeignOneToOne() 0 12 3
A ensureTableExists() 0 7 2
A createConstraintsOto() 0 12 2
A saveForeignManyToMany() 0 36 4
A createTable() 0 5 2
A createConstraintsOtm() 0 7 1
A createConstraintsMtm() 0 12 1
A handleRelations() 0 8 3
A finalizeModel() 0 4 1
A executeInTransaction() 0 17 3
A save() 0 19 2
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
     * @throws InvalidIdException
41
     */
42
    public function save(Model $model): string|int
43
    {
44
        if ($model->hasForeign(self::RELATION_ONE_TO_ONE)) {
45
            $this->saveForeignOneToOne($model);
46
        }
47
48
        $this->ensureTableExists($model);
49
50
        $id = $this->executeInTransaction(function () use ($model) {
51
            $id = $this->createRecords($model);
52
            $model->set(self::ID_FIELD, $id);
53
54
            return $id;
55
        });
56
57
        $this->handleRelations($model);
58
        $this->finalizeModel($model);
59
60
        return $id;
61
    }
62
63
    private function handleRelations(Model $model): void
64
    {
65
        if ($model->hasForeign(self::RELATION_ONE_TO_MANY)) {
66
            $this->saveForeignOneToMany($model);
67
        }
68
69
        if ($model->hasForeign(self::RELATION_MANY_TO_MANY)) {
70
            $this->saveForeignManyToMany($model);
71
        }
72
    }
73
74
    private function finalizeModel(Model $model): void
75
    {
76
        $model->cleanModel();
77
        $model->setLoaded();
78
    }
79
80
    private function executeInTransaction(callable $callback): mixed
81
    {
82
        if (!$this->connection->isTransactionActive()) {
83
            $this->connection->beginTransaction();
84
            try {
85
                $result = $callback();
86
                $this->connection->commit();
87
88
                return $result;
89
            } catch (\Exception $e) {
90
                $this->connection->rollBack();
91
                throw $e;
92
            }
93
        }
94
95
        // If already in transaction, just execute the callback
96
        return $callback();
97
    }
98
99
    /**
100
     * Create tables.
101
     *
102
     * @param array<TableConstraint> $constraints
103
     */
104
    private function createTable(Model $model, array $constraints = []): void
105
    {
106
        if (!$this->config->isFrozen()) {
107
            $table = $this->tableManager->createTable($model, $constraints);
108
            $this->tableManager->saveOrUpdateTable($model->getName(), $table);
109
        }
110
    }
111
112
    /**
113
     * Add constraint to table.
114
     *
115
     * @return array<TableConstraint>
116
     */
117
    private function createConstraintsOto(Model $model): array
118
    {
119
        $constraints = [];
120
        foreach ($model->getForeignModels('oto') as $foreign) {
121
            $constraints[] = new TableConstraint(
122
                $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 RectorPrefix202509\Nette\Utils\ArrayList or RectorPrefix202509\Nette\Utils\Finder or RectorPrefix202509\Nette\Utils\Html or PHPUnit\Architecture\Elements\Layer\Layer or SimpleXMLElement or RectorPrefix202509\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

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