Passed
Push — master ( 89564b...fcde6a )
by Robson
01:38
created

DataLayer::save()   A

Complexity

Conditions 6
Paths 17

Size

Total Lines 32
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 7
Bugs 0 Features 0
Metric Value
cc 6
eloc 19
c 7
b 0
f 0
nc 17
nop 0
dl 0
loc 32
rs 9.0111
1
<?php
2
3
namespace CoffeeCode\DataLayer;
4
5
use PDO;
6
use PDOException;
7
use stdClass;
8
9
/**
10
 * Class DataLayer
11
 * @package CoffeeCode\DataLayer
12
 */
13
abstract class DataLayer
14
{
15
    use CrudTrait;
16
17
    /** @var string $entity database table */
18
    private $entity;
19
20
    /** @var string $primary table primary key field */
21
    private $primary;
22
23
    /** @var array $required table required fields */
24
    private $required;
25
26
    /** @var string $timestamps control created and updated at */
27
    private $timestamps;
28
29
    /** @var string */
30
    protected $statement;
31
32
    /** @var string */
33
    protected $params;
34
35
    /** @var string */
36
    protected $group;
37
38
    /** @var string */
39
    protected $order;
40
41
    /** @var int */
42
    protected $limit;
43
44
    /** @var int */
45
    protected $offset;
46
47
    /** @var PDOException|null */
48
    protected $fail;
49
50
    /** @var object|null */
51
    protected $data;
52
53
    /**
54
     * DataLayer constructor.
55
     * @param string $entity
56
     * @param array $required
57
     * @param string $primary
58
     * @param bool $timestamps
59
     */
60
    public function __construct(string $entity, array $required, string $primary = 'id', bool $timestamps = true)
61
    {
62
        $this->entity = $entity;
63
        $this->primary = $primary;
64
        $this->required = $required;
65
        $this->timestamps = $timestamps;
0 ignored issues
show
Documentation Bug introduced by
The property $timestamps was declared of type string, but $timestamps is of type boolean. 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...
66
    }
67
68
    /**
69
     * @param $name
70
     * @param $value
71
     */
72
    public function __set($name, $value)
73
    {
74
        if (empty($this->data)) {
75
            $this->data = new stdClass();
76
        }
77
78
        $this->data->$name = $value;
79
    }
80
81
    /**
82
     * @param $name
83
     * @return bool
84
     */
85
    public function __isset($name)
86
    {
87
        return isset($this->data->$name);
88
    }
89
90
    /**
91
     * @param $name
92
     * @return string|null
93
     */
94
    public function __get($name)
95
    {
96
        $method = $this->toCamelCase($name);
97
        if (method_exists($this, $method)) {
98
            return $this->$method();
99
        }
100
101
        if (method_exists($this, $name)) {
102
            return $this->$name();
103
        }
104
105
        return ($this->data->$name ?? null);
106
    }
107
108
    /**
109
     * @param int $mode
110
     * @return array
111
     */
112
    public function columns($mode = PDO::FETCH_OBJ): ?array
113
    {
114
        $stmt = Connect::getInstance()->prepare("DESCRIBE {$this->entity}");
115
        $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

115
        $stmt->execute(/** @scrutinizer ignore-type */ $this->params);
Loading history...
116
        return $stmt->fetchAll($mode);
117
    }
118
119
120
    /**
121
     * @return object|null
122
     */
123
    public function data(): ?object
124
    {
125
        return $this->data;
126
    }
127
128
    /**
129
     * @return PDOException|null
130
     */
131
    public function fail(): ?PDOException
132
    {
133
        return $this->fail;
134
    }
135
136
    /**
137
     * @param string|null $terms
138
     * @param string|null $params
139
     * @param string $columns
140
     * @return DataLayer
141
     */
142
    public function find(?string $terms = null, ?string $params = null, string $columns = "*"): DataLayer
143
    {
144
        if ($terms) {
145
            $this->statement = "SELECT {$columns} FROM {$this->entity} WHERE {$terms}";
146
            parse_str($params, $this->params);
0 ignored issues
show
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

146
            parse_str(/** @scrutinizer ignore-type */ $params, $this->params);
Loading history...
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

146
            parse_str($params, /** @scrutinizer ignore-type */ $this->params);
Loading history...
147
            return $this;
148
        }
149
150
        $this->statement = "SELECT {$columns} FROM {$this->entity}";
151
        return $this;
152
    }
153
154
    /**
155
     * @param int $id
156
     * @param string $columns
157
     * @return DataLayer|null
158
     */
159
    public function findById(int $id, string $columns = "*"): ?DataLayer
160
    {
161
        return $this->find("{$this->primary} = :id", "id={$id}", $columns)->fetch();
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->find($this...$id, $columns)->fetch() could return the type array which is incompatible with the type-hinted return CoffeeCode\DataLayer\DataLayer|null. Consider adding an additional type-check to rule them out.
Loading history...
162
    }
163
164
    /**
165
     * @param string $column
166
     * @return DataLayer|null
167
     */
168
    public function group(string $column): ?DataLayer
169
    {
170
        $this->group = " GROUP BY {$column}";
171
        return $this;
172
    }
173
174
    /**
175
     * @param string $columnOrder
176
     * @return DataLayer|null
177
     */
178
    public function order(string $columnOrder): ?DataLayer
179
    {
180
        $this->order = " ORDER BY {$columnOrder}";
181
        return $this;
182
    }
183
184
    /**
185
     * @param int $limit
186
     * @return DataLayer|null
187
     */
188
    public function limit(int $limit): ?DataLayer
189
    {
190
        $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...
191
        return $this;
192
    }
193
194
    /**
195
     * @param int $offset
196
     * @return DataLayer|null
197
     */
198
    public function offset(int $offset): ?DataLayer
199
    {
200
        $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...
201
        return $this;
202
    }
203
204
    /**
205
     * @param bool $all
206
     * @return array|mixed|null
207
     */
208
    public function fetch(bool $all = false)
209
    {
210
        try {
211
            $stmt = Connect::getInstance()->prepare($this->statement . $this->group . $this->order . $this->limit . $this->offset);
212
            $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

212
            $stmt->execute(/** @scrutinizer ignore-type */ $this->params);
Loading history...
213
214
            if (!$stmt->rowCount()) {
215
                return null;
216
            }
217
218
            if ($all) {
219
                return $stmt->fetchAll(PDO::FETCH_CLASS, static::class);
220
            }
221
222
            return $stmt->fetchObject(static::class);
223
        } catch (PDOException $exception) {
224
            $this->fail = $exception;
225
            return null;
226
        }
227
    }
228
229
    /**
230
     * @return int
231
     */
232
    public function count(): int
233
    {
234
        $stmt = Connect::getInstance()->prepare($this->statement);
235
        $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

235
        $stmt->execute(/** @scrutinizer ignore-type */ $this->params);
Loading history...
236
        return $stmt->rowCount();
237
    }
238
239
    /**
240
     * @return bool
241
     */
242
    public function save(): bool
243
    {
244
        $primary = $this->primary;
245
        $id = null;
246
        $save = null;
247
248
        try {
249
            if (!$this->required()) {
250
                throw new PDOException("Preencha os campos necessários");
251
            }
252
253
            /** Update */
254
            if (!empty($this->data->$primary)) {
255
                $id = $this->data->$primary;
256
                $save = $this->update($this->safe(), "{$this->primary} = :id", "id={$id}");
257
            }
258
259
            /** Create */
260
            if (empty($this->data->$primary)) {
261
                $id = $this->create($this->safe());
262
                $save = $id;
263
            }
264
265
            if ($save === null) {
266
                return false;
267
            }
268
269
            $this->data = $this->findById($id)->data();
270
            return true;
271
        } catch (PDOException $exception) {
272
            $this->fail = $exception;
273
            return false;
274
        }
275
    }
276
277
    /**
278
     * @return bool
279
     */
280
    public function destroy(): bool
281
    {
282
        $primary = $this->primary;
283
        $id = $this->data->$primary;
284
285
        if (empty($id)) {
286
            return false;
287
        }
288
289
        return $this->delete("{$this->primary} = :id", "id={$id}");
290
    }
291
292
    /**
293
     * @return bool
294
     */
295
    protected function required(): bool
296
    {
297
        $data = (array)$this->data();
298
        foreach ($this->required as $field) {
299
            if (empty($data[$field])) {
300
                if (!is_int($data[$field])) {
301
                    return false;
302
                }
303
            }
304
        }
305
        return true;
306
    }
307
308
    /**
309
     * @return array|null
310
     */
311
    protected function safe(): ?array
312
    {
313
        $safe = (array)$this->data;
314
        unset($safe[$this->primary]);
315
        return $safe;
316
    }
317
318
319
    /**
320
     * @param string $string
321
     * @return string
322
     */
323
    protected function toCamelCase(string $string): string
324
    {
325
        $camelCase = str_replace(' ', '', ucwords(str_replace('_', ' ', $string)));
326
        $camelCase[0] = strtolower($camelCase[0]);
327
        return $camelCase;
328
    }
329
}
330