Passed
Push — main ( aadf6d...11ec11 )
by Pranjal
04:10
created

Model::refresh()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 4
nc 1
nop 0
dl 0
loc 5
rs 10
c 0
b 0
f 0
1
<?php
2
/*
3
 * This file is part of the Scrawler package.
4
 *
5
 * (c) Pranjal Pandey <[email protected]>
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 */
10
11
declare(strict_types=1);
12
13
namespace Scrawler\Arca;
14
15
use Doctrine\DBAL\Connection;
16
use Doctrine\DBAL\Types\Type;
17
use Scrawler\Arca\Manager\RecordManager;
18
use Scrawler\Arca\Manager\TableManager;
19
use Scrawler\Arca\Manager\WriteManager;
20
use Scrawler\Arca\Traits\Model\ArrayAccess;
21
use Scrawler\Arca\Traits\Model\Getter;
22
use Scrawler\Arca\Traits\Model\Iterator;
23
use Scrawler\Arca\Traits\Model\Setter;
24
use Scrawler\Arca\Traits\Model\Stringable;
25
26
/**
27
 * Model class that represents single record in database.
28
 *
29
 * @property int|string $id
30
 */
31
class Model implements \Stringable, \IteratorAggregate, \ArrayAccess
32
{
33
    use Iterator;
34
    use Stringable;
35
    use ArrayAccess;
36
    use Getter;
37
    use Setter;
38
39
    /**
40
     * Store all properties of model.
41
     *
42
     * @var array<string,array<string,mixed>>
43
     */
44
    private array $__properties = [];
45
46
    /**
47
     * Store the table name of model.
48
     */
49
    private string $table;
50
    /**
51
     * Store the metadata of model.
52
     *
53
     * @var array<string,mixed>
54
     */
55
    private array $__meta = [];
56
57
    /**
58
     * Create a new model.
59
     */
60
    public function __construct(
61
        string $name,
62
        private Connection $connection,
63
        private RecordManager $recordManager,
64
        private TableManager $tableManager,
65
        private WriteManager $writeManager,
66
    ) {
67
        $this->table = $name;
68
        $this->__properties['all'] = [];
69
        $this->__properties['self'] = [];
70
        $this->__meta['is_loaded'] = false;
71
        $this->__meta['id_error'] = false;
72
        $this->__meta['foreign_models']['otm'] = null;
73
        $this->__meta['foreign_models']['oto'] = null;
74
        $this->__meta['foreign_models']['mtm'] = null;
75
        $this->__meta['id'] = 0;
76
    }
77
78
    /**
79
     * adds the key to properties.
80
     */
81
    public function __set(string $key, mixed $val): void
82
    {
83
        $this->set($key, $val);
84
    }
85
86
    /**
87
     * Adds the key to properties.
88
     */
89
    public function set(string $key, mixed $val): void
90
    {
91
        if ('id' === $key) {
92
            $this->__meta['id'] = $val;
93
            $this->__meta['id_error'] = true;
94
        }
95
96
        if (0 !== \Safe\preg_match('/[A-Z]/', $key)) {
97
            $parts = \Safe\preg_split('/(?=[A-Z])/', $key, -1, PREG_SPLIT_NO_EMPTY);
98
            if ('own' === strtolower((string) $parts[0])) {
99
                $this->__meta['foreign_models']['otm'] = $this->createCollection($this->__meta['foreign_models']['otm'], $val);
100
                $this->__properties['all'][$key] = $val;
101
102
                return;
103
            }
104
            if ('shared' === strtolower((string) $parts[0])) {
105
                $this->__meta['foreign_models']['mtm'] = $this->createCollection($this->__meta['foreign_models']['mtm'], $val);
106
                $this->__properties['all'][$key] = $val;
107
108
                return;
109
            }
110
        }
111
        if ($val instanceof Model) {
112
            if (isset($this->__properties['all'][$key.'_id'])) {
113
                unset($this->__properties['all'][$key.'_id']);
114
            }
115
            $this->__meta['foreign_models']['oto'] = $this->createCollection($this->__meta['foreign_models']['oto'], Collection::fromIterable([$val]));
116
            $this->__properties['all'][$key] = $val;
117
118
            return;
119
        }
120
121
        $type = $this->getDataType($val);
122
123
        $this->__properties['self'][$key] = $this->getDbValue($val, $type);
124
        $this->__properties['all'][$key] = $val;
125
        $this->__properties['type'][$key] = $type;
126
    }
127
128
    /**
129
     * Get a key from properties, keys can be relational
130
     * like sharedList,ownList or foreign table.
131
     */
132
    public function __get(string $key): mixed
133
    {
134
        return $this->get($key);
135
    }
136
137
    /**
138
     * Get a key from properties, keys can be relational
139
     * like sharedList,ownList or foreign table.
140
     */
141
    public function get(string $key): mixed
142
    {
143
        // retrun if alraedy loaded
144
        if (array_key_exists($key, $this->__properties['all'])) {
145
            return $this->__properties['all'][$key];
146
        }
147
148
        if (0 !== \Safe\preg_match('/[A-Z]/', $key)) {
149
            $parts = \Safe\preg_split('/(?=[A-Z])/', $key, -1, PREG_SPLIT_NO_EMPTY);
150
            if ('own' === strtolower((string) $parts[0]) && 'list' === strtolower((string) $parts[2])) {
151
                $result = $this->recordManager->find(strtolower((string) $parts[1]))->where($this->getName().'_id = "'.$this->__meta['id'].'"')->get();
152
                $this->set($key, $result);
153
154
                return $result;
155
            }
156
            if ('shared' === strtolower((string) $parts[0]) && 'list' === strtolower((string) $parts[2])) {
157
                $rel_table = $this->tableManager->tableExists($this->table.'_'.strtolower((string) $parts[1])) ? $this->table.'_'.strtolower((string) $parts[1]) : strtolower((string) $parts[1]).'_'.$this->table;
158
                $relations = $this->recordManager->find($rel_table)->where($this->getName().'_id = "'.$this->__meta['id'].'"')->get();
159
                $rel_ids = '';
160
                foreach ($relations as $relation) {
161
                    $key = strtolower((string) $parts[1]).'_id';
162
                    $rel_ids .= "'".$relation->$key."',";
163
                }
164
                $rel_ids = substr($rel_ids, 0, -1);
165
                $result = $this->recordManager->find(strtolower((string) $parts[1]))->where('id IN ('.$rel_ids.')')->get();
166
                $this->set($key, $result);
167
168
                return $result;
169
            }
170
        }
171
172
        if (array_key_exists($key.'_id', $this->__properties['self'])) {
173
            $result = $this->recordManager->getById($key, $this->__properties['self'][$key.'_id']);
174
            $this->set($key, $result);
175
176
            return $result;
177
        }
178
179
        throw new Exception\KeyNotFoundException();
180
    }
181
182
    /**
183
     * Eager Load relation variable.
184
     *
185
     * @param array<string> $relations
186
     */
187
    public function with(array $relations): Model
188
    {
189
        foreach ($relations as $relation) {
190
            $this->get($relation);
191
        }
192
193
        return $this;
194
    }
195
196
    /**
197
     * Refresh the current model from database.
198
     */
199
    public function refresh(): void{
200
        $model = $this->recordManager->getById($this->getName(), $this->getId());
201
        $this->cleanModel();
202
        $this->setLoadedProperties($model->getSelfProperties());
203
        $this->setLoaded();
204
    }
205
206
    /**
207
     * Unset a property from model.
208
     */
209
    public function __unset(string $key): void
210
    {
211
        $this->unset($key);
212
    }
213
214
    /**
215
     * Unset a property from model.
216
     */
217
    public function unset(string $key): void
218
    {
219
        unset($this->__properties['self'][$key]);
220
        unset($this->__properties['all'][$key]);
221
        unset($this->__properties['type'][$key]);
222
    }
223
224
    /**
225
     * Check if property exists.
226
     */
227
    public function __isset(string $key): bool
228
    {
229
        return $this->isset($key);
230
    }
231
232
    /**
233
     * Check if property exists.
234
     */
235
    public function isset(string $key): bool
236
    {
237
        return array_key_exists($key, $this->__properties['all']);
238
    }
239
240
    /**
241
     * check if model loaded from db.
242
     */
243
    public function isLoaded(): bool
244
    {
245
        return $this->__meta['is_loaded'];
246
    }
247
248
    /**
249
     * Check if model has id error.
250
     */
251
    public function hasIdError(): bool
252
    {
253
        return $this->__meta['id_error'];
254
    }
255
256
    /**
257
     * Save model to database.
258
     */
259
    public function save(): mixed
260
    {
261
        $this->id = $this->writeManager->save($this);
262
263
        return $this->getId();
264
    }
265
266
    /**
267
     * Cleans up model internal state to be consistent after save.
268
     */
269
    public function cleanModel(): void
270
    {
271
        $this->__properties['all'] = $this->__properties['self'];
272
        $this->__meta['id_error'] = false;
273
        $this->__meta['foreign_models']['otm'] = null;
274
        $this->__meta['foreign_models']['oto'] = null;
275
        $this->__meta['foreign_models']['mtm'] = null;
276
    }
277
278
    /**
279
     * Delete model data.
280
     */
281
    public function delete(): void
282
    {
283
        $this->recordManager->delete($this);
284
    }
285
286
    /**
287
     * Function used to compare to models.
288
     */
289
    public function equals(self $other): bool
290
    {
291
        return $this->getId() === $other->getId() && $this->toString() === $other->toString();
292
    }
293
294
    /**
295
     * Check if model has any relations.
296
     */
297
    public function hasForeign(string $type): bool
298
    {
299
        return !is_null($this->__meta['foreign_models'][$type]);
300
    }
301
302
    /**
303
     * Get the type of value.
304
     */
305
    private function getDataType(mixed $value): string
306
    {
307
        $type = gettype($value);
308
309
        if ('array' === $type || 'object' === $type) {
310
            return 'json_document';
311
        }
312
313
        if ('string' === $type) {
314
            return 'text';
315
        }
316
317
        return $type;
318
    }
319
320
    /**
321
     * Check if array passed is instance of model.
322
     *
323
     * @param array<mixed>|Collection $models
324
     *
325
     * @throws Exception\InvalidModelException
326
     */
327
    private function createCollection(?Collection $collection, array|Collection $models): Collection
328
    {
329
        if (is_null($collection)) {
330
            $collection = Collection::fromIterable([]);
331
        }
332
333
        if ($models instanceof Collection) {
0 ignored issues
show
introduced by
$models is never a sub-type of Scrawler\Arca\Collection.
Loading history...
334
            return $collection->merge($models);
335
        }
336
337
        if ([] !== array_filter($models, fn ($d): bool => !$d instanceof Model)) {
338
            throw new Exception\InvalidModelException();
339
        }
340
341
        return $collection->merge(Collection::fromIterable($models));
342
    }
343
344
    /**
345
     * Get the database value from PHP value.
346
     */
347
    private function getDbValue(mixed $val, string $type): mixed
348
    {
349
        if ('boolean' === $type) {
350
            return ($val) ? 1 : 0;
351
        }
352
353
        return Type::getType($type)->convertToDatabaseValue($val, $this->connection->getDatabasePlatform());
354
    }
355
}
356