Completed
Push — master ( 6d8914...a6405a )
by Alexander
02:04
created

Mysql::insert()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 15
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 12
nc 4
nop 3
dl 0
loc 15
rs 9.4285
c 0
b 0
f 0
1
<?php
2
3
namespace alkemann\h2l\data;
4
5
use alkemann\h2l\exceptions\ConnectionError;
6
use PDO;
7
use alkemann\h2l\Log;
8
9
class Mysql implements Source
10
{
11
12
    /**
13
     * @var array
14
     */
15
    protected $config = [];
16
17
    /**
18
     * @var PDO
19
     */
20
    protected $db = null;
21
22
    public function __construct(array $config = [])
23
    {
24
        $defaults = [
25
            'host' => 'localhost',
26
            'db' => 'test',
27
            'user' => null,
28
            'pass' => null
29
        ];
30
        $this->config = $config + $defaults;
31
    }
32
33
    private function handler() //: PDO
34
    {
35
        if ($this->db) {
36
            return $this->db;
37
        }
38
39
        $host = $this->config['host'];
40
        $db = $this->config['db'];
41
        $user = $this->config['user'];
42
        $pass = $this->config['pass'];
43
        $opts = [
44
            PDO::ATTR_EMULATE_PREPARES => false,
45
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
46
        ];
47
        try {
48
            $this->db = new PDO("mysql:host={$host};dbname={$db}", $user, $pass, $opts);
49
            // @TODO use this?
1 ignored issue
show
Unused Code Comprehensibility introduced by
38% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
50
            // $this->db->setAttribute( PDO::ATTR_EMULATE_PREPARES, false);
1 ignored issue
show
Unused Code Comprehensibility introduced by
57% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
51
        } catch (\PDOException $e) {
52
            throw new ConnectionError("Unable to connect to $host : $db with user $user");
1 ignored issue
show
Unused Code introduced by
The call to ConnectionError::__construct() has too many arguments starting with "Unable to connect to {$...$db} with user {$user}".

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
53
        }
54
        return $this->db;
55
    }
56
57
    public function query($query, array $params = [])
58
    {
59
        Log::debug("PDO:QUERY [$query]");
60
        $result = $this->handler()->query($query);
61
        return $result->fetchAll(PDO::FETCH_ASSOC);
62
    }
63
64
    public function one(string $table, array $conditions, array $options = []): ?array
65
    {
66
        $result = $this->find($table, $conditions, $options);
67
        $result = iterator_to_array($result);
68
        $hits = sizeof($result);
69
        if ($hits === 0) {
70
            return null;
71
        }
72
        if ($hits > 1) {
73
            throw new \Error("One request found more than 1 match!");
74
        }
75
76
        return $result[0];
77
    }
78
79
    public function find(string $table, array $conditions, array $options = []): iterable
80
    {
81
        $where = $this->where($conditions);
82
        $limit = $this->limit($options);
83
        $query = "SELECT * FROM `{$table}` {$where}{$limit};";
84
        $params = $this->boundDebugString($conditions, $options);
85
        Log::debug("PDO:QUERY [$query][$params]");
86
        $dbh = $this->handler();
87
        $stmt = $dbh->prepare($query);
88
        foreach ($conditions as $key => $value) {
89
            $stmt->bindValue(":c_{$key}", $value);
90
        }
91
        $this->bindPaginationToStatement($options, $stmt);
92
        $result = $stmt->execute();
93
        if ($result === false) {
94
            return new \EmptyIterator;
95
        }
96
        // @codeCoverageIgnoreStart
97
        if ($stmt instanceof \PDOStatement) {
98
            $stmt->setFetchMode(PDO::FETCH_ASSOC);
99
        }
100
        // @codeCoverageIgnoreEnd
101
        return $stmt;
102
    }
103
104
    private function where(array $conditions): string
105
    {
106
        if (empty($conditions)) {
107
            return "";
108
        }
109
        $fun = function ($o, $v) {
110
            return "{$o}{$v} = :c_{$v}";
111
        };
112
        $where = array_reduce(array_keys($conditions), $fun, "");
113
        return "WHERE {$where} ";
114
    }
115
116
    private function limit(array $options): string
117
    {
118
        return array_key_exists('limit', $options) ? "LIMIT :o_offset,:o_limit " : '';
119
    }
120
121
    private function boundDebugString(array $conditions, array $options, array $data = []): string
122
    {
123
        $out = [];
124
        foreach ($conditions as $k => $v) {
125
            $out[] = "c_{$k}:'{$v}'";
126
        }
127
        foreach ($data as $k => $v) {
128
            $out[] = "d_{$k}:{$v}";
129
        }
130
        foreach ($options as $k => $v) {
131
            $out[] = "o_{$k}:{$v}";
132
        }
133
        return join(", ", $out);
134
    }
135
136
    /**
137
     * @param array $options
138
     * @param \PDOStatement $stmt
139
     */
140
    private function bindPaginationToStatement(array $options, $stmt): void
141
    {
142
        if (array_key_exists('limit', $options)) {
143
            $stmt->bindValue(":o_offset", (int)($options['offset'] ?? 0), PDO::PARAM_INT);
144
            $stmt->bindValue(":o_limit", (int)$options['limit'], PDO::PARAM_INT);
145
        }
146
    }
147
148
    public function update(string $table, array $conditions, array $data, array $options = []): int
149
    {
150
        if (empty($conditions) || empty($data)) {
151
            return 0;
152
        }
153
154
        $datasql = $this->data($data);
155
        $where = $this->where($conditions);
156
        $query = "UPDATE `{$table}` SET {$datasql} {$where};";
157
158
        $params = $this->boundDebugString($conditions, $options, $data);
159
        Log::debug("PDO:QUERY [$query][$params]");
160
        $dbh = $this->handler();
161
        $stmt = $dbh->prepare($query);
162
        foreach ($data as $key => $value) {
163
            $stmt->bindValue(":d_{$key}", $value);
164
        }
165
        foreach ($conditions as $key => $value) {
166
            $stmt->bindValue(":c_{$key}", $value);
167
        }
168
        $result = $stmt->execute();
169
        return ($result === true) ? $stmt->rowCount() : 0;
170
    }
171
172
    private function data(array $data): string
173
    {
174
        $fun = function ($o, $v) {
175
            return "{$o}{$v} = :d_{$v}";
176
        };
177
        return array_reduce(array_keys($data), $fun, "");
178
    }
179
180
    public function insert(string $table, array $data, array $options = []): ?int
181
    {
182
        $keys = implode(', ', array_keys($data));
183
        $data_phs = ':d_' . implode(', :d_', array_keys($data));
184
        $query = "INSERT INTO `{$table}` ({$keys}) VALUES ({$data_phs});";
185
        $params = $this->boundDebugString([], [], $data);
186
        Log::debug("PDO:QUERY [$query][$params]");
187
        $dbh = $this->handler();
188
        $stmt = $dbh->prepare($query);
189
        foreach ($data as $key => $value) {
190
            $stmt->bindValue(':d_' . $key, $value);
191
        }
192
        $result = $stmt->execute();
193
        return ($result === true) ? $dbh->lastInsertId() : null;
194
    }
195
196
    public function delete(string $table, array $conditions, array $options = []): int
197
    {
198
        $where = $this->where($conditions);
199
        if (empty($where)) {
200
            return 0;
201
        }
202
        $limit = $this->limit($options);
203
        $query = "DELETE FROM `{$table}` {$where}{$limit};";
204
        $params = $this->boundDebugString($conditions, $options);
205
        Log::debug("PDO:QUERY [$query][$params]");
206
        $dbh = $this->handler();
207
        $stmt = $dbh->prepare($query);
208
        foreach ($conditions as $key => $value) {
209
            $stmt->bindValue(":c_{$key}", $value);
210
        }
211
        $this->bindPaginationToStatement($options, $stmt);
212
        $result = $stmt->execute();
213
        return ($result === true) ? $stmt->rowCount() : 0;
214
    }
215
}
216