Passed
Push — main ( b19326...f308aa )
by Pranjal
03:30
created

Model::getDbValue()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
eloc 3
nc 3
nop 2
dl 0
loc 7
rs 10
c 1
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 (\Safe\preg_match('/[A-Z]/', $key) !== 0) {
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 (\Safe\preg_match('/[A-Z]/', $key) !== 0) {
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
                return $result;
154
            }
155
            if ('shared' === strtolower((string) $parts[0]) && 'list' === strtolower((string) $parts[2])) {
156
                $rel_table = $this->tableManager->tableExists($this->table.'_'.strtolower((string) $parts[1])) ? $this->table.'_'.strtolower((string) $parts[1]) : strtolower((string) $parts[1]).'_'.$this->table;
157
                $relations = $this->recordManager->find($rel_table)->where($this->getName().'_id = "'.$this->__meta['id'].'"')->get();
158
                $rel_ids = '';
159
                foreach ($relations as $relation) {
160
                    $key = strtolower((string) $parts[1]).'_id';
161
                    $rel_ids .= "'".$relation->$key."',";
162
                }
163
                $rel_ids = substr($rel_ids, 0, -1);
164
                $result = $this->recordManager->find(strtolower((string) $parts[1]))->where('id IN ('.$rel_ids.')')->get();
165
                $this->set($key, $result);
166
                return $result;
167
            }
168
        }
169
170
        if (array_key_exists($key.'_id', $this->__properties['self'])) {
171
            $result = $this->recordManager->getById($key, $this->__properties['self'][$key.'_id']);
172
            $this->set($key, $result);
173
174
            return $result;
175
        }
176
177
        throw new Exception\KeyNotFoundException();
178
    }
179
180
    /**
181
     * Eager Load relation variable.
182
     *
183
     * @param array<string> $relations
184
     */
185
    public function with(array $relations): Model
186
    {
187
        foreach ($relations as $relation) {
188
            $this->get($relation);
189
        }
190
191
        return $this;
192
    }
193
194
    /**
195
     * Unset a property from model.
196
     */
197
    public function __unset(string $key): void
198
    {
199
        $this->unset($key);
200
    }
201
202
    /**
203
     * Unset a property from model.
204
     */
205
    public function unset(string $key): void
206
    {
207
        unset($this->__properties['self'][$key]);
208
        unset($this->__properties['all'][$key]);
209
        unset($this->__properties['type'][$key]);
210
    }
211
212
    /**
213
     * Check if property exists.
214
     */
215
    public function __isset(string $key): bool
216
    {
217
        return $this->isset($key);
218
    }
219
220
    /**
221
     * Check if property exists.
222
     */
223
    public function isset(string $key): bool
224
    {
225
        return array_key_exists($key, $this->__properties['all']);
226
    }
227
228
    /**
229
     * check if model loaded from db.
230
     */
231
    public function isLoaded(): bool
232
    {
233
        return $this->__meta['is_loaded'];
234
    }
235
236
    /**
237
     * Check if model has id error.
238
     */
239
    public function hasIdError(): bool
240
    {
241
        return $this->__meta['id_error'];
242
    }
243
244
    /**
245
     * Save model to database.
246
     */
247
    public function save(): mixed
248
    {
249
        $this->id = $this->writeManager->save($this);
250
251
        return $this->getId();
252
    }
253
254
    /**
255
     * Cleans up model internal state to be consistent after save.
256
     */
257
    public function cleanModel(): void
258
    {
259
        $this->__properties['all'] = $this->__properties['self'];
260
        $this->__meta['id_error'] = false;
261
        $this->__meta['foreign_models']['otm'] = null;
262
        $this->__meta['foreign_models']['oto'] = null;
263
        $this->__meta['foreign_models']['mtm'] = null;
264
    }
265
266
    /**
267
     * Delete model data.
268
     */
269
    public function delete(): void
270
    {
271
        $this->recordManager->delete($this);
272
    }
273
274
    /**
275
     * Function used to compare to models.
276
     */
277
    public function equals(self $other): bool
278
    {
279
        return $this->getId() === $other->getId() && $this->toString() === $other->toString();
280
    }
281
282
    /**
283
     * Check if model has any relations.
284
     */
285
    public function hasForeign(string $type): bool
286
    {
287
        return !is_null($this->__meta['foreign_models'][$type]);
288
    }
289
290
    /**
291
     * Get the type of value.
292
     */
293
    private function getDataType(mixed $value): string
294
    {
295
        $type = gettype($value);
296
297
        if ('array' === $type || 'object' === $type) {
298
            return 'json_document';
299
        }
300
301
        if ('string' === $type) {
302
            return 'text';
303
        }
304
305
        return $type;
306
    }
307
308
    /**
309
     * Check if array passed is instance of model.
310
     *
311
     * @param array<mixed>|Collection $models
312
     *
313
     * @throws Exception\InvalidModelException
314
     */
315
    private function createCollection(?Collection $collection, array|Collection $models): Collection
316
    {
317
        if (is_null($collection)) {
318
            $collection = Collection::fromIterable([]);
319
        }
320
321
        if ($models instanceof Collection) {
0 ignored issues
show
introduced by
$models is never a sub-type of Scrawler\Arca\Collection.
Loading history...
322
            return $collection->merge($models);
323
        }
324
325
        if (array_filter($models, fn ($d): bool => !$d instanceof Model) !== []) {
326
            throw new Exception\InvalidModelException();
327
        }
328
329
        return $collection->merge(Collection::fromIterable($models));
330
    }
331
332
    /**
333
     * Get the database value from PHP value.
334
     */
335
    private function getDbValue(mixed $val,string $type): mixed
336
    {
337
        if ('boolean' === $type) {
338
            return ($val) ?  1 :  0;
339
        }
340
341
        return Type::getType($type)->convertToDatabaseValue($val, $this->connection->getDatabasePlatform());
342
343
       
344
    }
345
}
346