Completed
Pull Request — master (#2)
by René
04:42
created

Entity::toArrayFromColumns()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 15
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6
Metric Value
dl 0
loc 15
ccs 0
cts 8
cp 0
rs 9.4285
cc 2
eloc 8
nc 2
nop 1
crap 6
1
<?php
2
declare(strict_types = 1);
3
4
namespace Zortje\MVC\Model\Table\Entity;
5
6
use Zortje\MVC\Model\Table\Entity\Exception\InvalidEntityPropertyException;
7
use Zortje\MVC\Model\Table\Entity\Exception\InvalidValueTypeForEntityPropertyException;
8
9
/**
10
 * Class Entity
11
 *
12
 * @package Zortje\MVC\Model\Table\Entity
13
 */
14
abstract class Entity
15
{
16
17
    /**
18
     * @var array Columns
19
     */
20
    protected static $columns = [];
21
22
    /**
23
     * @var array Internal entity properties
24
     */
25
    protected $properties = [];
26
27
    /**
28
     * @var array Internal altered columns
29
     */
30
    protected $alteredColumns = [];
31
32
    /**
33
     * @param int|null  $id       Entity ID
34
     * @param \DateTime $modified Datetime of last modification
35
     * @param \DateTime $created  Datetime of creation
36
     */
37
    public function __construct($id, \DateTime $modified, \DateTime $created)
38
    {
39
        $this->set('id', $id);
40
        $this->set('modified', $modified);
41
        $this->set('created', $created);
42
    }
43
44
    /**
45
     * Get entity columns
46
     *
47
     * @return array Entity columns
48
     */
49 1
    public static function getColumns(): array
50
    {
51 1
        $columns = array_merge([
52
            'id' => 'integer'
53 1
        ], static::$columns);
54
55 1
        $columns = array_merge($columns, [
56 1
            'modified' => 'datetime',
57
            'created'  => 'datetime'
58
        ]);
59
60 1
        return $columns;
61
    }
62
63
    /**
64
     * Set entity property
65
     *
66
     * @param string                                      $key   Entity property name
67
     * @param object|int|double|string|array|boolean|null $value Entity property value
68
     *
69
     * @throws InvalidEntityPropertyException If entity does not have that property
70
     * @throws InvalidValueTypeForEntityPropertyException If value is of the wrong type
71
     */
72 2
    public function set(string $key, $value)
73
    {
74 2 View Code Duplication
        if (isset(self::getColumns()[$key]) === false) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
75 1
            throw new InvalidEntityPropertyException([get_class($this), $key]);
76
        }
77
78 2
        $newValue = $this->validatePropertyValueType($key, $value);
79
80 2
        if (!isset($this->properties[$key]) || $this->properties[$key] !== $newValue) {
81
            /**
82
             * Set internal property
83
             */
84 2
            $this->properties[$key] = $newValue;
85
86
            /**
87
             * Set altered column
88
             */
89 2
            $this->alteredColumns[$key] = true;
90
        }
91 2
    }
92
93
    /**
94
     * Get entity property
95
     *
96
     * @param string $key Entity property
97
     *
98
     * @return object|int|double|string|array|boolean|null Entity property value for given key
99
     *
100
     * @throws InvalidEntityPropertyException If entity does not have that property
101
     */
102 2
    public function get(string $key)
103
    {
104 2 View Code Duplication
        if (!isset(self::getColumns()[$key])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
105 1
            throw new InvalidEntityPropertyException([get_class($this), $key]);
106
        }
107
108 1
        return $this->properties[$key];
109
    }
110
111
    /**
112
     * Validate type for given property value
113
     *
114
     * @param string $key   Entity property name
115
     * @param mixed  $value Entity property value
116
     *
117
     * @return object|int|double|string|array|boolean|null Value
118
     *
119
     * @throws InvalidEntityPropertyException If entity does not have that property
120
     * @throws InvalidValueTypeForEntityPropertyException If value is of the wrong type
121
     */
122 3
    public function validatePropertyValueType(string $key, $value)
123
    {
124 3 View Code Duplication
        if (!isset(self::getColumns()[$key])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
125 1
            throw new InvalidEntityPropertyException([get_class($this), $key]);
126
        }
127
128
        /**
129
         * Allow NULL
130
         */
131 3
        if ($value !== null) {
132 3
            $valueType = strtolower(gettype($value));
133
134
            /**
135
             * Get class if object
136
             */
137 3
            if ($valueType === 'object') {
138 3
                $valueType = strtolower(get_class($value));
139
            }
140
141
            /**
142
             * Handle alias types
143
             */
144 3
            $columnType = self::getColumns()[$key];
145
146
            switch ($columnType) {
147 3
                case 'date':
148 3
                    $columnType = 'datetime';
149 3
                    break;
150
151 3
                case 'varbinary':
152
                    $columnType = 'string';
153
                    break;
154
            }
155
156
            /**
157
             * Validate type
158
             */
159 3
            if ($valueType !== $columnType) {
160 1
                throw new InvalidValueTypeForEntityPropertyException([
161 1
                    get_class($this),
162 1
                    $key,
163 1
                    $columnType,
164 1
                    $valueType
165
                ]);
166
            }
167
        }
168
169 3
        return $value;
170
    }
171
172
    /**
173
     * Check if entity has been altered
174
     *
175
     * @return bool True if altered, otherwise false
176
     */
177 1
    public function isAltered(): bool
178
    {
179 1
        return count($this->alteredColumns) > 0;
180
    }
181
182
    /**
183
     * Marks the entity as unaltered
184
     */
185 1
    public function setUnaltered()
186
    {
187 1
        $this->alteredColumns = [];
188 1
    }
189
190
    /**
191
     * Get altered columns
192
     *
193
     * @return array
194
     */
195 1
    public function getAlteredColumns(): array
196
    {
197 1
        return $this->alteredColumns;
198
    }
199
200
    /**
201
     * Return table structure for saving
202
     * Example: `[':{table_field_name}' => $this->fieldName]`
203
     *
204
     * @param bool $includeId Should the ID column be included
205
     *
206
     * @return array
207
     */
208 2
    public function toArray(bool $includeId): array
209
    {
210 2
        $columns = self::getColumns();
211
212 2
        if (!$includeId) {
213 1
            unset($columns['id']);
214
        }
215
216 2
        return $this->toArrayFromColumns($columns);
217
    }
218
219
    /**
220
     * Return table structure for saving just altered columns
221
     *
222
     * @param bool $includeId Should the ID column be included
223
     *
224
     * @return array
225
     */
226 2
    public function alteredToArray(bool $includeId): array
227
    {
228 2
        $alteredColumns = $this->alteredColumns;
229
230 2
        if ($includeId) {
231 1
            $alteredColumns['id'] = true;
232
        }
233
234 2
        return $this->toArrayFromColumns(array_intersect_key(self::getColumns(), $alteredColumns));
235
    }
236
237
    /**
238
     * Return columns in structure for saving
239
     *
240
     * @param array $columns Columns to include
241
     *
242
     * @return array
243
     */
244
    protected function toArrayFromColumns(array $columns): array
245
    {
246
        $array = [];
247
248
        foreach ($columns as $column => $type) {
249
            $property = new EntityProperty($type);
250
251
            $value = $this->get($column);
252
            $value = $property->formatValueForDatabase($value);
253
254
            $array[":$column"] = $value;
255
        }
256
257
        return $array;
258
    }
259
}
260