Model::create()   A
last analyzed

Complexity

Conditions 2
Paths 6

Size

Total Lines 13
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 9
dl 0
loc 13
rs 9.9666
c 0
b 0
f 0
cc 2
nc 6
nop 1
1
<?php
2
3
namespace Source\Core;
4
5
abstract class Model
6
{
7
    /** @var object|null */
8
    protected $data;
9
10
    /** @var \PDOException|null */
11
    protected $fail;
12
13
    /** @var string|null */
14
    protected $error;
15
16
    /** @var string */
17
18
    protected $query;
19
    /** @var string */
20
21
    protected $params;
22
    /** @var string */
23
24
    /** @var string */
25
    protected $order;
26
27
    /** @var int */
28
    protected $limit;
29
30
    /** @var int */
31
    protected $offset;
32
33
    /** @var string $entity database table */
34
    protected static $entity;
35
36
    /** @var array $protected no update or create */
37
    protected static $protected;
38
39
    /** @var array $entity database table */
40
    protected static $required;
41
42
    /**
43
     * Model constructor.
44
     * @param string $entity database table name
45
     * @param array $protected table protected columns
46
     * @param array $required table required columns
47
     */
48
    public function __construct(string $entity, array $protected, array $required)
49
    {
50
        self::$entity = $entity;
51
        self::$protected = array_merge($protected, ['created_at', "updated_at"]);
52
        self::$required = $required;
53
    }
54
55
    /**
56
     * @param $name
57
     * @param $value
58
     */
59
    public function __set($name, $value)
60
    {
61
        if (empty($this->data)) {
62
            $this->data = new \stdClass();
63
        }
64
65
        $this->data->$name = $value;
66
    }
67
68
    /**
69
     * @param $name
70
     * @return bool
71
     */
72
    public function __isset($name)
73
    {
74
        return isset($this->data->$name);
75
    }
76
77
    /**
78
     * @param $name
79
     * @return null
80
     */
81
    public function __get($name)
82
    {
83
        return ($this->data->$name ?? null);
84
    }
85
86
    /**
87
     * @return null|object
88
     */
89
    public function data(): ?object
90
    {
91
        return $this->data;
92
    }
93
94
    /**
95
     * @return \PDOException
96
     */
97
    public function fail(): ?\PDOException
98
    {
99
        return $this->fail;
100
    }
101
102
    /**
103
     * @return null|string
104
     */
105
    public function message(): ?string
106
    {
107
        return $this->error;
108
    }
109
110
    /**
111
     * @param
112
     * @return Model|mixed
113
     */
0 ignored issues
show
Documentation Bug introduced by
The doc comment @return at position 0 could not be parsed: Unknown type name '@return' at position 0 in @return.
Loading history...
114
    public function find(?string $terms = null, ?string $params = null, string $columns = "*")
115
    {
116
        if ($terms) {
117
            $this->query = "SELECT {$columns} FROM " . static::$entity . " WHERE {$terms}";
118
119
            parse_str($params, $this->params);
0 ignored issues
show
Bug introduced by
$this->params of type string is incompatible with the type array expected by parameter $result of parse_str(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

119
            parse_str($params, /** @scrutinizer ignore-type */ $this->params);
Loading history...
Bug introduced by
It seems like $params can also be of type null; however, parameter $string of parse_str() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

119
            parse_str(/** @scrutinizer ignore-type */ $params, $this->params);
Loading history...
120
            return $this;
121
        }
122
123
        $this->query = "SELECT {$columns} FROM " . static::$entity;
124
        return $this;
125
    }
126
127
    /**
128
     * @param int $id
129
     * @param string $columns
130
     * @return null|mixed|Model
131
     */
132
    public function findById(int $id, string $columns = "*"): ?Model
133
    {
134
        $find = $this->find("id = :id", "id={$id}", $columns);
135
136
        return $find->fetch();
137
    }
138
139
    public function findAll(int $limit = null, int $offset = null): ?array
140
    {
141
        $find = $this->find();
142
143
        if (!empty($limit)) {
144
            $find = $find->limit($limit);
145
        }
146
147
        if (!empty($offset)) {
148
            $find = $find->offset($offset);
149
        }
150
151
        $find = $find->fetch(true);
152
153
        if (empty($find)) {
154
            return null;
155
        }
156
157
        $return = [];
158
        foreach ($find as $key => $value) {
159
            $return[] = $value->data();
160
        }
161
162
        return $return;
163
    }
164
165
    public function order(string $columnOrder): Model
166
    {
167
        $this->order = " ORDER BY {$columnOrder}";
168
169
        return $this;
170
    }
171
172
    public function limit(int $limit): Model
173
    {
174
        $this->limit = " LIMIT {$limit}";
0 ignored issues
show
Documentation Bug introduced by
The property $limit was declared of type integer, but ' LIMIT '.$limit is of type string. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
175
176
        return $this;
177
    }
178
179
    public function offset(int $offset): Model
180
    {
181
        $this->offset = " OFFSET {$offset}";
0 ignored issues
show
Documentation Bug introduced by
The property $offset was declared of type integer, but ' OFFSET '.$offset is of type string. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
182
183
        return $this;
184
    }
185
186
    public function fetch(bool $all = false)
187
    {
188
        try {
189
            $stmt = Connect::getInstance()->prepare($this->query . $this->order . $this->limit . $this->offset);
190
            $stmt->execute($this->params);
0 ignored issues
show
Bug introduced by
$this->params of type string is incompatible with the type array expected by parameter $params of PDOStatement::execute(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

190
            $stmt->execute(/** @scrutinizer ignore-type */ $this->params);
Loading history...
191
192
            if (!$stmt->rowCount()) {
193
                return null;
194
            }
195
196
            if ($all) {
197
                return $stmt->fetchAll(\PDO::FETCH_CLASS, static::class);
198
            }
199
200
            return $stmt->fetchObject(static::class);
201
        } catch (\PDOException $exception) {
202
            $this->fail = $exception;
203
            return null;
204
        }
205
    }
206
207
    public function count(string $key = "id"): int
0 ignored issues
show
Unused Code introduced by
The parameter $key is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

207
    public function count(/** @scrutinizer ignore-unused */ string $key = "id"): int

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
208
    {
209
        $stmt = Connect::getInstance()->prepare($this->query);
210
        $stmt->execute($this->params);
0 ignored issues
show
Bug introduced by
$this->params of type string is incompatible with the type array expected by parameter $params of PDOStatement::execute(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

210
        $stmt->execute(/** @scrutinizer ignore-type */ $this->params);
Loading history...
211
212
        return $stmt->rowCount();
213
    }
214
215
    public function rawQuery(string $query): self
216
    {
217
        $this->query = $query;
218
        return $this;
219
    }
220
221
    /**
222
     * @param string $entity
223
     * @param array $data
224
     * @return int|null
225
     */
226
    protected function create(array $data): ?int
227
    {
228
        try {
229
            $columns = implode(", ", array_keys($data));
230
            $values = ":" . implode(", :", array_keys($data));
231
232
            $stmt = Connect::getInstance()->prepare("INSERT INTO " . static::$entity . " ({$columns}) VALUES ({$values})");
233
            $stmt->execute($this->filter($data));
234
235
            return Connect::getInstance()->lastInsertId();
236
        } catch (\PDOException $exception) {
237
            $this->fail = $exception;
238
            return null;
239
        }
240
    }
241
242
    /**
243
     * @param string $entity
244
     * @param array $data
245
     * @param string $terms
246
     * @param string $params
247
     * @return int|null
248
     */
249
    protected function update(array $data, string $terms, string $params): ?int
250
    {
251
        try {
252
            $dateSet = [];
253
            foreach ($data as $bind => $value) {
254
                $dateSet[] = "{$bind} = :{$bind}";
255
            }
256
            $dateSet = implode(", ", $dateSet);
257
            parse_str($params, $params);
0 ignored issues
show
Bug introduced by
$params of type string is incompatible with the type array expected by parameter $result of parse_str(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

257
            parse_str($params, /** @scrutinizer ignore-type */ $params);
Loading history...
258
259
            $stmt = Connect::getInstance()->prepare("UPDATE " . static::$entity . " SET {$dateSet} WHERE {$terms}");
260
            $stmt->execute($this->filter(array_merge($data, $params)));
261
            return ($stmt->rowCount() ?? 1);
262
        } catch (\PDOException $exception) {
263
            $this->fail = $exception;
264
            return null;
265
        }
266
    }
267
268
    public function delete(string $key, string $value): bool
269
    {
270
        try {
271
            $stmt = Connect::getInstance()->prepare("DELETE FROM " . static::$entity . " WHERE {$key} = :key");
272
            $stmt->bindValue("key", $value, \PDO::PARAM_STR);
273
            $stmt->execute();
274
275
            return true;
276
        } catch (\PDOException $exception) {
277
            $this->fail = $exception;
278
            return false;
279
        }
280
    }
281
282
    /**
283
     * @return array|null
284
     */
285
    protected function safe(): ?array
286
    {
287
        $safe = (array) $this->data;
288
289
        foreach (static::$protected as $unset) {
290
            unset($safe[$unset]);
291
        }
292
293
        return $safe;
294
    }
295
296
    /**
297
     * @param array $data
298
     * @return array|null
299
     */
300
    private function filter(array $data): ?array
301
    {
302
        $filter = [];
303
        foreach ($data as $key => $value) {
304
            $filter[$key] = (is_null($value) ? null : filter_var($value, FILTER_DEFAULT));
305
        }
306
307
        return $filter;
308
    }
309
310
    /**
311
     * @return bool
312
     */
313
    protected function required(): bool
314
    {
315
        $data = (array) $this->data();
316
317
        foreach (static::$required as $field) {
318
            if (empty($data[$field])) {
319
                return false;
320
            }
321
        }
322
323
        return true;
324
    }
325
326
    public function getRequired(): array
327
    {
328
        $data = [];
329
        foreach (static::$required as $field) {
330
            $data[] = $field;
331
        }
332
333
        return $data;
334
    }
335
}
336