Passed
Pull Request — master (#29)
by
unknown
01:56
created

DataLayer::join()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 12
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 3
c 1
b 0
f 0
nc 1
nop 5
dl 0
loc 12
rs 10
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
    private $entity;
20
21
    /** @var string $primary table primary key field */
22
    private $primary;
23
24
    /** @var array $required table required fields */
25
    private $required;
26
27
    /** @var string $timestamps control created and updated at */
28
    private $timestamps;
29
30
    /** @var string */
31
    protected $statement;
32
33
    /** @var string */
34
    protected $join;
35
36
    /** @var string */
37
    protected $params;
38
39
    /** @var string */
40
    protected $group;
41
42
    /** @var string */
43
    protected $order;
44
45
    /** @var int */
46
    protected $limit;
47
48
    /** @var int */
49
    protected $offset;
50
51
    /** @var \PDOException|null */
52
    protected $fail;
53
54
    /** @var object|null */
55
    protected $data;
56
57
    /**
58
     * DataLayer constructor.
59
     * @param string $entity
60
     * @param array $required
61
     * @param string $primary
62
     * @param bool $timestamps
63
     */
64
    public function __construct(string $entity, array $required, string $primary = 'id', bool $timestamps = true)
65
    {
66
        $this->entity = $entity;
67
        $this->primary = $primary;
68
        $this->required = $required;
69
        $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...
70
    }
71
72
    /**
73
     * @param $name
74
     * @param $value
75
     */
76
    public function __set($name, $value)
77
    {
78
        if (empty($this->data)) {
79
            $this->data = new stdClass();
80
        }
81
82
        $this->data->$name = $value;
83
    }
84
85
    /**
86
     * @param $name
87
     * @return bool
88
     */
89
    public function __isset($name)
90
    {
91
        return isset($this->data->$name);
92
    }
93
94
    /**
95
     * @param $name
96
     * @return string|null
97
     */
98
    public function __get($name)
99
    {
100
        $method = $this->toCamelCase($name);
101
        if (method_exists($this, $method)) {
102
            return $this->$method();
103
        }
104
105
        if (method_exists($this, $name)) {
106
            return $this->$name();
107
        }
108
109
        return ($this->data->$name ?? null);
110
    }
111
112
    /**
113
     * @return object|null
114
     */
115
    public function data(): ?object
116
    {
117
        return $this->data;
118
    }
119
120
    /**
121
     * @return PDOException|Exception|null
122
     */
123
    public function fail()
124
    {
125
        return $this->fail;
126
    }
127
128
    /**
129
     * @param string|null $terms
130
     * @param string|null $params
131
     * @param string $columns
132
     * @return DataLayer
133
     */
134
    public function find(?string $terms = null, ?string $params = null, string $columns = "*"): DataLayer
135
    {
136
        if ($terms) {
137
            $this->statement = "SELECT {$columns} FROM {$this->entity} WHERE {$terms}";
138
            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

138
            parse_str($params, /** @scrutinizer ignore-type */ $this->params);
Loading history...
139
            return $this;
140
        }
141
142
        $this->statement = "SELECT {$columns} FROM {$this->entity}";
143
        return $this;
144
    }
145
146
    /**
147
     * @param int $id
148
     * @param string $columns
149
     * @return DataLayer|null
150
     */
151
    public function findById(int $id, string $columns = "*"): ?DataLayer
152
    {
153
        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...
154
    }
155
156
    /**
157
     * @param string $column
158
     * @return DataLayer|null
159
     */
160
    public function group(string $column): ?DataLayer
161
    {
162
        $this->group = " GROUP BY {$column}";
163
        return $this;
164
    }
165
166
    /**
167
     * @param string $columnOrder
168
     * @return DataLayer|null
169
     */
170
    public function order(string $columnOrder): ?DataLayer
171
    {
172
        $this->order = " ORDER BY {$columnOrder}";
173
        return $this;
174
    }
175
176
    /**
177
     * @param int $limit
178
     * @return DataLayer|null
179
     */
180
    public function limit(int $limit): ?DataLayer
181
    {
182
        $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...
183
        return $this;
184
    }
185
186
    /**
187
     * @param int $offset
188
     * @return DataLayer|null
189
     */
190
    public function offset(int $offset): ?DataLayer
191
    {
192
        $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...
193
        return $this;
194
    }
195
    
196
    /**
197
     * @param string $columns
198
     * @return DataLayer|null
199
     */
200
    public function select(string $columns = "*"): ?DataLayer
201
    {
202
        $this->statement = "SELECT {$columns} FROM {$this->entity}";
203
        return $this;
204
    }
205
    /**
206
     * @param string|null $terms
207
     * @param string|null $params
208
     * @return DataLayer|null
209
     */
210
    public function where(?string $terms = null, ?string $params = null): ?DataLayer
211
    {
212
        $this->statement .= " WHERE {$terms}";
213
        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

213
        parse_str($params, /** @scrutinizer ignore-type */ $this->params);
Loading history...
214
        return $this;
215
    }
216
217
    /**
218
     * @param DataLayer $otherEntity
219
     * @param string $column1
220
     * @param string $operation
221
     * @param string $column2
222
     * @param string $type INNER, LEFT (OUTER) or RIGHT (OUTER)
223
     * @return DataLayer|null
224
     */
225
    public function join(
226
        DataLayer $otherEntity, 
227
        string $column1,
228
        string $operation,
229
        string $column2,
230
        string $type = 'INNER'
231
    ): ?DataLayer {
232
        $join = "${type} JOIN {$otherEntity->entity} on {$column1} {$operation} {$column2}";
233
234
        $this->statement .= " " . $join;
235
236
        return $this;
237
    }
238
239
    /**
240
     * @param bool $all
241
     * @return array|mixed|null
242
     */
243
    public function fetch(bool $all = false)
244
    {
245
        try {
246
            $stmt = Connect::getInstance()->prepare($this->statement . $this->group . $this->order . $this->limit . $this->offset);
247
            $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

247
            $stmt->execute(/** @scrutinizer ignore-type */ $this->params);
Loading history...
248
249
            if (!$stmt->rowCount()) {
250
                return null;
251
            }
252
253
            if ($all) {
254
                return $stmt->fetchAll(PDO::FETCH_CLASS, static::class);
255
            }
256
            
257
            return $stmt->fetchObject(static::class);
258
        } catch (PDOException $exception) {
259
            $this->fail = $exception;
260
            return null;
261
        }
262
    }
263
264
    /**
265
     * @return int
266
     */
267
    public function count(): int
268
    {
269
        $stmt = Connect::getInstance()->prepare($this->statement);
270
        $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

270
        $stmt->execute(/** @scrutinizer ignore-type */ $this->params);
Loading history...
271
        return $stmt->rowCount();
272
    }
273
274
    /**
275
     * @return bool
276
     */
277
    public function save(): bool
278
    {
279
        $primary = $this->primary;
280
        $id = null;
281
282
        try {
283
            if (!$this->required()) {
284
                throw new Exception("Preencha os campos necessários");
285
            }
286
287
            /** Update */
288
            if (!empty($this->data->$primary)) {
289
                $id = $this->data->$primary;
290
                $this->update($this->safe(), "{$this->primary} = :id", "id={$id}");
291
            }
292
293
            /** Create */
294
            if (empty($this->data->$primary)) {
295
                $id = $this->create($this->safe());
296
            }
297
298
            if (!$id) {
299
                return false;
300
            }
301
302
            $this->data = $this->findById($id)->data();
303
            return true;
304
        } catch (Exception $exception) {
305
            $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...
306
            return false;
307
        }
308
    }
309
310
    /**
311
     * @return bool
312
     */
313
    public function destroy(): bool
314
    {
315
        $primary = $this->primary;
316
        $id = $this->data->$primary;
317
318
        if (empty($id)) {
319
            return false;
320
        }
321
322
        return $this->delete("{$this->primary} = :id", "id={$id}");
323
    }
324
325
    /**
326
     * @return bool
327
     */
328
    protected function required(): bool
329
    {
330
        $data = (array)$this->data();
331
        foreach ($this->required as $field) {
332
            if (empty($data[$field])) {
333
                return false;
334
            }
335
        }
336
        return true;
337
    }
338
339
    /**
340
     * @return array|null
341
     */
342
    protected function safe(): ?array
343
    {
344
        $safe = (array)$this->data;
345
        unset($safe[$this->primary]);
346
        return $safe;
347
    }
348
349
350
    /**
351
     * @param string $string
352
     * @return string
353
     */
354
    protected function toCamelCase(string $string): string
355
    {
356
        $camelCase = str_replace(' ', '', ucwords(str_replace('_', ' ', $string)));
357
        $camelCase[0] = strtolower($camelCase[0]);
358
        return $camelCase;
359
    }
360
}
361