Passed
Pull Request — master (#5)
by
unknown
01:49
created

DataLayer::resolvePK()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

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

131
            parse_str($params, /** @scrutinizer ignore-type */ $this->params);
Loading history...
132
            return $this;
133
        }
134
135
        $this->statement = "SELECT {$columns} FROM {$this->entity}";
136
        return $this;
137
    }
138
139
    /**
140
     * @param int $id
141
     * @param string $columns
142
     * @return DataLayer|null
143
     */
144
    public function findById(int $id, string $columns = "*"): ?DataLayer
145
    {
146
        $find = $this->find($this->primary . " = :id", "id={$id}", $columns);
147
        return $find->fetch();
0 ignored issues
show
Bug Best Practice introduced by
The expression return $find->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...
148
    }
149
150
    /**
151
     * @param string $columnOrder
152
     * @return DataLayer|null
153
     */
154
    public function order(string $columnOrder): ?DataLayer
155
    {
156
        $this->order = " ORDER BY {$columnOrder}";
0 ignored issues
show
Documentation Bug introduced by
The property $order was declared of type integer, but ' ORDER BY '.$columnOrder 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...
157
        return $this;
158
    }
159
160
    /**
161
     * @param int $limit
162
     * @return DataLayer|null
163
     */
164
    public function limit(int $limit): ?DataLayer
165
    {
166
        $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...
167
        return $this;
168
    }
169
170
    /**
171
     * @param int $offset
172
     * @return DataLayer|null
173
     */
174
    public function offset(int $offset): ?DataLayer
175
    {
176
        $this->offset = " OFFSET {$offset}";
177
        return $this;
178
    }
179
180
    /**
181
     * @param bool $all
182
     * @return array|mixed|null
183
     */
184
    public function fetch(bool $all = false)
185
    {
186
        try {
187
            $stmt = Connect::getInstance()->prepare($this->statement . $this->order . $this->limit . $this->offset);
188
            $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 $input_parameters 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

188
            $stmt->execute(/** @scrutinizer ignore-type */ $this->params);
Loading history...
189
190
            if (!$stmt->rowCount()) {
191
                return null;
192
            }
193
194
            if ($all) {
195
                return $stmt->fetchAll(PDO::FETCH_CLASS, static::class);
196
            }
197
198
            return $stmt->fetchObject(static::class);
199
        } catch (PDOException $exception) {
200
            $this->fail = $exception;
201
            return null;
202
        }
203
    }
204
205
    /**
206
     * @return int
207
     */
208
    public function count(): int
209
    {
210
        $stmt = Connect::getInstance()->prepare($this->statement);
211
        $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 $input_parameters 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

211
        $stmt->execute(/** @scrutinizer ignore-type */ $this->params);
Loading history...
212
        return $stmt->rowCount();
213
    }
214
215
    /**
216
     * @return bool
217
     */
218
    public function save(): bool
219
    {
220
        $primary = $this->primary;
221
        $id = null;
222
223
        try {
224
            if (!$this->required()) {
225
                throw new Exception("Preencha os campos necessários");
226
            }
227
228
            /** Update */
229
            if (!empty($this->data->$primary)) {
230
                $id = $this->data->$primary;
231
                $this->update($this->safe(), $this->primary . " = :id", "id={$id}");
232
            }
233
234
            /** Create */
235
            if (empty($this->data->$primary)) {
236
                $id = $this->create($this->safe());
237
            }
238
239
            if (!$id) {
240
                return false;
241
            }
242
243
            $this->data = $this->findById($id)->data();
244
            return true;
245
        } catch (Exception $exception) {
246
            $this->fail = $exception;
0 ignored issues
show
Documentation Bug introduced by
$exception is of type Exception, but the property $fail was declared to be of type PDOException|null. Are you sure that you always receive this specific sub-class here, or does it make sense to add an instanceof check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a given class or a super-class is assigned to a property that is type hinted more strictly.

Either this assignment is in error or an instanceof check should be added for that assignment.

class Alien {}

class Dalek extends Alien {}

class Plot
{
    /** @var  Dalek */
    public $villain;
}

$alien = new Alien();
$plot = new Plot();
if ($alien instanceof Dalek) {
    $plot->villain = $alien;
}
Loading history...
247
            return false;
248
        }
249
    }
250
251
    /**
252
     * @return bool
253
     */
254
    public function destroy(): bool
255
    {
256
        $primary = $this->primary;
257
        $id = $this->data->$primary;
258
259
        if (empty($id)) {
260
            return false;
261
        }
262
263
        $destroy = $this->delete($this->primary . " = :id", "id={$id}");
264
        return $destroy;
265
    }
266
267
    /**
268
     * @return bool
269
     */
270
    protected function required(): bool
271
    {
272
        $data = (array)$this->data();
273
        foreach ($this->required as $field) {
274
            if (empty($data[$field])) {
275
                return false;
276
            }
277
        }
278
        return true;
279
    }
280
281
    /**
282
     * General resolve worker.
283
     * @return void
284
     */
285
    protected function resolve(): void
286
    {
287
        $this->resolveEntity();
288
        $this->resolveRequired();
289
        $this->resolveTimestamps();
290
        $this->resolvePK();
291
    }
292
293
    /**
294
     * Checking for default 'entity(table name)' property.
295
     * @return void
296
     */
297
    protected function resolveEntity(): void
298
    {
299
        // Entity name which comes from model as property,
300
        // could not be converted to lower case because
301
        // user registered it manually.
302
        if (empty($this->entity)) {
303
            // Getting called class name which is always Model.
304
            $segments = explode("\\", get_called_class());
305
            $len = count($segments);
306
            if ($len > 0) {
307
                // model(entity) name is default lower case.
308
                $name = strtolower($segments[$len - 1]);
309
310
                $this->entity = $name;
311
            }
312
        }
313
    }
314
315
    /**
316
     * Checking for default 'required' property.
317
     * @return void
318
     */
319
    protected function resolveRequired(): void
320
    {
321
        // Type of string is not handled for now.
322
        // But it can be done soon.
323
        if (empty($this->required)) {
324
            $this->required = [];
325
        }
326
    }
327
328
    /**
329
     * Checking for default 'primary' property.
330
     * @return void
331
     */
332
    protected function resolvePK(): void
333
    {
334
        if (empty($this->primary)) {
335
            $this->primary = "id";
336
        }
337
    }
338
339
    /**
340
     * Checking for default 'timestamps' property.
341
     * @return void
342
     */
343
    protected function resolveTimestamps(): void
344
    {
345
        if (empty($this->timestamps)) {
346
            $this->timestamps = true;
0 ignored issues
show
Documentation Bug introduced by
The property $timestamps was declared of type string, but true is of type true. 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...
347
        }
348
    }
349
350
    /**
351
     * @return array|null
352
     */
353
    protected function safe(): ?array
354
    {
355
        $safe = (array)$this->data;
356
        unset($safe[$this->primary]);
357
358
        return $safe;
359
    }
360
}
361