Element::__construct()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 4
c 0
b 0
f 0
nc 2
nop 1
dl 0
loc 9
ccs 5
cts 5
cp 1
crap 2
rs 10
1
<?php
2
3
/**
4
 * @author Marwan Al-Soltany <[email protected]>
5
 * @copyright Marwan Al-Soltany 2021
6
 * For the full copyright and license information, please view
7
 * the LICENSE file that was distributed with this source code.
8
 */
9
10
declare(strict_types=1);
11
12
namespace MAKS\Velox\Backend\Model;
13
14
use MAKS\Velox\Backend\Exception;
15
use MAKS\Velox\Backend\Model\DBAL;
16
use MAKS\Velox\Helper\Misc;
17
18
/**
19
 * An abstract class that holds the base functionality of a model.
20
 * NOTE: This class is not meant to be used directly.
21
 *
22
 * @package Velox\Backend\Model
23
 * @since 1.5.1
24
 */
25
abstract class Element extends DBAL implements \ArrayAccess, \Traversable, \IteratorAggregate
26
{
27
    /**
28
     * Model attributes. Corresponds to table columns.
29
     */
30
    protected array $attributes;
31
32
33
    /**
34
     * Class constructor.
35
     *
36
     * Keep all constructor arguments optional when extending the class.
37
     * Or use `self::bootstrap()` instead.
38
     *
39
     * @param array $attributes [optional] The attributes to set on the model.
40
     */
41 41
    public function __construct(?array $attributes = [])
42
    {
43 41
        $this->attributes = array_merge(array_fill_keys($this->getColumns(), null), $attributes ?? []);
44
45 41
        if ($this->isMigrated() === false) {
46 40
            $this->migrate();
47
        }
48
49 41
        $this->bootstrap();
50
    }
51
52
    /**
53
     * Override this method to add your own bootstrap code to the modal.
54
     *
55
     * @return void
56
     */
57
    protected function bootstrap(): void
58
    {
59
        // implemented as needed
60
    }
61
62
    /**
63
     * Asserts that the model attribute name is valid.
64
     *
65
     * @param mixed $name The name to validate.
66
     *
67
     * @return void
68
     *
69
     * @throws \OutOfBoundsException If attribute name is not a part of model `$columns`.
70
     */
71 32
    protected static function assertAttributeExists($name): void
72
    {
73
        $columns = static::getColumns();
74
75 32
        if (!in_array((string)$name, $columns)) {
76 2
            Exception::throw(
77
                'UnknownAttributeException:OutOfBoundsException',
78
                sprintf('Cannot find attribute with the name "%s". %s model table does not consist of this column', $name, static::class)
79 32
            );
80 1
        }
81
    }
82 1
83
    /**
84
     * Gets the specified model attribute.
85
     *
86
     * @param string $name Attribute name as specified in `$columns`.
87
     *
88
     * @return mixed Attribute value.
89
     *
90
     * @throws \OutOfBoundsException If the attribute does not exists.
91
     */
92
    public function get(string $name)
93
    {
94
        $this->assertAttributeExists($name);
95
96 31
        return $this->attributes[$name];
97
    }
98 31
99
    /**
100 31
     * Sets the specified model attribute.
101
     *
102
     * @param string $name Attribute name as specified in `$columns`.
103
     * @param mixed $value Attribute value.
104
     *
105
     * @return $this
106
     *
107
     * @throws \OutOfBoundsException If the attribute does not exists.
108
     */
109
    public function set(string $name, $value): self
110
    {
111
        $this->assertAttributeExists($name);
112
113 32
        $this->attributes[$name] = $value;
114
115 32
        return $this;
116
    }
117 32
118
    /**
119 32
     * Gets all model attributes.
120
     *
121
     * @return array Model attributes.
122
     */
123
    public function getAttributes(): array
124
    {
125
        return $this->attributes;
126
    }
127 30
128
    /**
129 30
     * Sets all or a subset of model attributes.
130
     *
131
     * @param array $attributes Model attributes.
132
     *
133
     * @return $this
134
     */
135
    public function setAttributes(array $attributes): self
136
    {
137
        foreach ($attributes as $key => $value) {
138
            in_array($key, $this->getColumns()) && $this->set($key, $value);
139 32
        }
140
141 32
        return $this;
142 32
    }
143
144
145 32
    /**
146
     * Makes attributes accessible via public property access notation.
147
     * Examples: `model_id` as `$model->modelId`
148
     */
149
    public function __get(string $name)
150
    {
151
        $name = Misc::transform($name, 'snake');
152
153 9
        return $this->get($name);
154
    }
155 9
156
    /**
157 9
     * Makes attributes accessible via public property assignment notation.
158
     * Examples: `model_id` as `$model->modelId`
159
     */
160
    public function __set(string $name, $value)
161
    {
162
        $name = Misc::transform($name, 'snake');
163
164 27
        return $this->set($name, $value);
165
    }
166 27
167
    /**
168 27
     * Makes attributes consumable via `isset()`.
169
     */
170
    public function __isset(string $name)
171
    {
172
        $name = Misc::transform($name, 'snake');
173
174 1
        return $this->get($name) !== null;
175
    }
176 1
177
    /**
178 1
     * Makes attributes consumable via `unset()`.
179
     */
180
    public function __unset(string $name)
181
    {
182
        $name = Misc::transform($name, 'snake');
183
184 1
        return $this->set($name, null);
185
    }
186 1
187
    /**
188 1
     * Makes the model safely cloneable via the `clone` keyword.
189
     */
190
    public function __clone()
191
    {
192
        $this->set($this->getPrimaryKey(), null);
193
    }
194 1
195
    /**
196 1
     * Makes the model safely consumable via `serialize()`.
197
     */
198
    public function __sleep()
199
    {
200
        return ['attributes'];
201
    }
202 1
203
204 1
    /**
205
     * `ArrayAccess::offsetGet()` interface implementation.
206
     */
207
    #[\ReturnTypeWillChange]
208
    public function offsetGet($offset)
209
    {
210
        return $this->get($offset);
211 1
    }
212
213 1
    /**
214
     * `ArrayAccess::offsetSet()` interface implementation.
215
     */
216
    #[\ReturnTypeWillChange]
217
    public function offsetSet($offset, $value): void
218
    {
219 1
        $this->set($offset, $value);
220
    }
221 1
222
    /**
223
     * `ArrayAccess::offsetExists()` interface implementation.
224
     */
225
    #[\ReturnTypeWillChange]
226
    public function offsetExists($offset): bool
227 1
    {
228
        return $this->get($offset) !== null;
229 1
    }
230
231
    /**
232
     * `ArrayAccess::offsetUnset()` interface implementation.
233
     */
234
    #[\ReturnTypeWillChange]
235 1
    public function offsetUnset($offset): void
236
    {
237 1
        $this->set($offset, null);
238
    }
239
240
241
    /**
242
     * `IteratorAggregate::getIterator()` interface implementation.
243
     */
244 1
    public function getIterator(): \Traversable
245
    {
246 1
        return new \ArrayIterator($this->attributes);
247
    }
248
}
249