Passed
Push — master ( 754829...12c5ee )
by Herberto
01:56
created

PdoClient::bindParameter()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 15
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 3

Importance

Changes 0
Metric Value
dl 0
loc 15
ccs 9
cts 9
cp 1
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 9
nc 2
nop 4
crap 3
1
<?php
2
namespace Hgraca\MicroOrm\DataSource\Pdo;
3
4
use Hgraca\MicroOrm\DataSource\ClientInterface;
5
use Hgraca\MicroOrm\DataSource\Exception\BindingMicroOrmException;
6
use Hgraca\MicroOrm\DataSource\Exception\ExecutionMicroOrmException;
7
use Hgraca\MicroOrm\DataSource\Exception\TypeResolutionMicroOrmException;
8
use PDO;
9
use PDOStatement;
10
11
class PdoClient implements ClientInterface
12
{
13
    const POSTFIX_FILTER = '_filter';
14
15
    /** @var PDO */
16
    private $pdo;
17
18 13
    public function __construct(PDO $pdo)
19
    {
20 13
        $this->pdo = $pdo;
21 13
    }
22
23
    /**
24
     * @throws BindingMicroOrmException
25
     * @throws ExecutionMicroOrmException
26
     */
27 3
    public function select(string $table, array $filter = [], array $orderBy = [], string $classFqcn = ''): array
28
    {
29 3
        $sqlSelect = "SELECT * FROM `$table`";
30 3
        $sqlFilter = $this->createSqlFilter($filter);
31 3
        $sqlOrderBy = $this->createSqlOrderBy($orderBy);
32
33 3
        $sql = $sqlSelect . ($sqlFilter ? ' ' . $sqlFilter : '') . ($sqlOrderBy ? ' ' . $sqlOrderBy : '');
34
35 3
        return $this->executeQuery($sql, $filter, $classFqcn);
36
    }
37
38
    /**
39
     * @param string $table
40
     * @param array  $data [$columnName => $value, ...]
41
     *
42
     * @return int The nbr of affected rows
43
     */
44 1
    public function insert(string $table, array $data): int
45
    {
46 1
        $sql = $this->createInsertSql($table, $data);
47
48 1
        return $this->executeCommand($sql, [], $data);
49
    }
50
51
    /**
52
     * @param string $table
53
     * @param array  $data [$columnName => $value, ...]
54
     *
55
     * @return int The nbr of affected rows
56
     */
57 1 View Code Duplication
    public function update(string $table, array $filter = [], array $data = []): int
1 ignored issue
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
58
    {
59 1
        $sqlUpdate = $this->createUpdateSql($table, $data);
60 1
        $sqlFilter = $this->createSqlFilter($filter);
61 1
        $sql = $sqlUpdate . ($sqlFilter ? ' ' . $sqlFilter : '');
62
63 1
        return $this->executeCommand($sql, $filter, $data);
64
    }
65
66
    /**
67
     * @return int The nbr of affected rows
68
     */
69 1 View Code Duplication
    public function delete(string $table, array $filter = []): int
1 ignored issue
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
70
    {
71 1
        $sqlDelete = "DELETE FROM `$table`";
72 1
        $sqlFilter = $this->createSqlFilter($filter);
73 1
        $sql = $sqlDelete . ($sqlFilter ? ' ' . $sqlFilter : '');
74
75 1
        return $this->executeCommand($sql, $filter);
76
    }
77
78
    /**
79
     * @throws BindingMicroOrmException
80
     * @throws ExecutionMicroOrmException
81
     *
82
     * @return array The result list
83
     */
84 5
    public function executeQuery(string $sql, array $filterList = [], string $classFqcn = null): array
85
    {
86 5
        return $this->fetchData($this->execute($sql, $filterList), $classFqcn);
87
    }
88
89
    /**
90
     * @throws BindingMicroOrmException
91
     * @throws ExecutionMicroOrmException
92
     *
93
     * @return int The nbr of affected rows
94
     */
95 7
    public function executeCommand(string $sql, array $filterList = [], array $dataList = []): int
96
    {
97 7
        return $this->execute($sql, $filterList, $dataList)->rowCount();
98
    }
99
100 1
    public function getLastInsertId(string $idName = null): string
101
    {
102 1
        return $this->pdo->lastInsertId($idName);
103
    }
104
105
    /**
106
     * @throws BindingMicroOrmException
107
     * @throws ExecutionMicroOrmException
108
     *
109
     * @return PDOStatement
110
     */
111 10
    private function execute(string $sql, array $filterList = [], array $dataList = []): PDOStatement
112
    {
113 10
        $stmt = $this->pdo->prepare($sql);
114
115 10
        $this->bindParameterList($stmt, $filterList, self::POSTFIX_FILTER);
116 8
        $this->bindParameterList($stmt, $dataList);
117
118 8
        $executed = $stmt->execute();
119 8
        if (false === $executed) {
120 1
            throw new ExecutionMicroOrmException(
121 1
                "Could not execute query: '$sql'"
122 1
                . " Error code: " . $stmt->errorCode()
123 1
                . " Error Info: " . json_encode($stmt->errorInfo())
124
            );
125
        }
126
127 7
        return $stmt;
128
    }
129
130 5
    private function fetchData(PDOStatement $stmt, string $classFqcn = null): array
131
    {
132 5
        if (! empty($classFqcn)) {
133 1
            return $stmt->fetchAll(PDO::FETCH_CLASS, $classFqcn);
134
        }
135
136 4
        return $stmt->fetchAll(PDO::FETCH_ASSOC);
137
    }
138
139 5
    private function createSqlFilter(array $filterByColumnNames = []): string
140
    {
141 5
        if (empty($filterByColumnNames)) {
142 1
            return '';
143
        }
144
145 4
        $sqlFilter = [];
146 4
        foreach ($filterByColumnNames as $columnName => $value) {
147 4
            $sqlFilter[] = $this->createColumnFilter($columnName, $value);
148
        }
149 4
        $sqlFilter = 'WHERE ' . implode(' AND ', $sqlFilter);
150
151 4
        return $sqlFilter;
152
    }
153
154 4
    private function createColumnFilter(string $columnName, $value, string $parameterName = null)
155
    {
156 4
        $parameterName = $parameterName ?? $columnName;
157
158 4
        if (null === $value) {
159 3
            return "`$columnName` IS :$parameterName" . self::POSTFIX_FILTER;
160
        }
161
162 4
        if (is_array($value)) {
163 1
            $filter = [];
164 1
            foreach ($value as $filterName => $filterValue) {
165 1
                $filter[] = $this->createColumnFilter($columnName, $filterValue, $filterName);
166
            }
167 1
            return empty($filter) ? '' : '(' . implode(' OR ', $filter) . ')';
168
        }
169
170 4
        return "`$columnName`=:$parameterName" . self::POSTFIX_FILTER;
171
    }
172
173 3
    private function createSqlOrderBy(array $orderBy = []): string
174
    {
175 3
        if (empty($orderBy)) {
176 1
            return '';
177
        }
178
179 2
        $orderByItems = [];
180 2
        foreach ($orderBy as $column => $direction) {
181 2
            $orderByItems[] = $column . ' ' . $direction;
182
        }
183
184 2
        return 'ORDER BY ' . implode(', ', $orderByItems);
185
    }
186
187 1
    private function createInsertSql(string $tableName, array $dataList): string
188
    {
189 1
        $columnNamesArray = array_keys($dataList);
190 1
        $columnNamesList = "`" . implode("`, `", $columnNamesArray) . "`";
191 1
        $columnPlaceholdersList = ":" . implode(", :", $columnNamesArray);
192
193 1
        return "INSERT INTO `$tableName` ($columnNamesList) VALUES ($columnPlaceholdersList)";
194
    }
195
196 1
    private function createUpdateSql(string $tableName, array $parameterList): string
197
    {
198 1
        $setColumnsList = [];
199 1
        foreach ($parameterList as $columnName => $columnValue) {
200 1
            $setColumnsList[] = '`' . $columnName . '`=:' . $columnName;
201
        }
202 1
        $setColumnsList = implode(', ', $setColumnsList);
203
204 1
        return "UPDATE `$tableName` SET $setColumnsList";
205
    }
206
207
    /**
208
     * @param mixed $value
209
     *
210
     * @throws TypeResolutionMicroOrmException
211
     */
212 8
    private function resolvePdoType($value): int
213
    {
214 8
        $type = gettype($value);
215
        switch ($type) {
216 8
            case 'boolean':
217 7
                $pdoType = PDO::PARAM_BOOL;
218 7
                break;
219 7
            case 'string' :
0 ignored issues
show
Coding Style introduced by
There must be no space before the colon in a CASE statement

As per the PSR-2 coding standard, there must not be a space in front of the colon in case statements.

switch ($selector) {
    case "A": //right
        doSomething();
        break;
    case "B" : //wrong
        doSomethingElse();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
220 7
            case 'double': // float
221 1
                $pdoType = PDO::PARAM_STR;
222 1
                break;
223 7
            case 'integer':
224 3
                $pdoType = PDO::PARAM_INT;
225 3
                break;
226 6
            case 'NULL':
227 5
                $pdoType = PDO::PARAM_NULL;
228 5
                break;
229 1
            case 'object':
230 1
                $class = get_class($value);
231 1
                throw new TypeResolutionMicroOrmException("Invalid type '$class' for query filter.");
232
            default:
233
                throw new TypeResolutionMicroOrmException("Invalid type '$type' for query filter.");
234
        }
235
236 7
        return $pdoType;
237
    }
238
239
    /**
240
     * @throws BindingMicroOrmException
241
     */
242 9
    private function bindParameterList(PDOStatement $stmt, array $parameterList, string $postfix = '')
243
    {
244 9
        foreach ($parameterList as $name => $value) {
245 8
            if (is_array($value)) {
246
                /** @var array $value */
247 1
                foreach ($value as $filterName => $filterValue) {
248 1
                    $this->bindParameter($stmt, $filterName, $filterValue, $postfix);
249
                }
250
            } else {
251 8
                $this->bindParameter($stmt, $name, $value, $postfix);
252
            }
253
        }
254 7
    }
255
256
    /**
257
     * @throws BindingMicroOrmException
258
     */
259 8
    private function bindParameter(PDOStatement $stmt, string $name, $value, string $postfix = '')
260
    {
261 8
        $pdoType = $this->resolvePdoType($value);
262 7
        $bound = $stmt->bindValue(
263 7
            ':' . $name . $postfix,
264 7
            $pdoType === PDO::PARAM_STR ? strval($value) : $value,
265
            $pdoType
266
        );
267
268 7
        if (false === $bound) {
269 1
            throw new BindingMicroOrmException(
270 1
                "Could not bind value: " . json_encode(['name' => $name, 'value' => $value, 'type' => $pdoType])
271
            );
272
        }
273 6
    }
274
}
275