Statement::bindNamedParameters()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 7
rs 10
c 0
b 0
f 0
cc 2
nc 2
nop 0
1
<?php
2
3
namespace BenTools\SimpleDBAL\Model\Adapter\PDO;
4
5
use BenTools\SimpleDBAL\Contract\AdapterInterface;
6
use BenTools\SimpleDBAL\Contract\StatementInterface;
7
use BenTools\SimpleDBAL\Contract\ResultInterface;
8
use BenTools\SimpleDBAL\Model\StatementTrait;
9
use PDO;
10
use PDOStatement;
11
12
class Statement implements StatementInterface
13
{
14
    use StatementTrait;
15
16
    /**
17
     * @var PDOAdapter
18
     */
19
    private $connection;
20
21
    /**
22
     * @var PDOStatement
23
     */
24
    private $stmt;
25
26
    /**
27
     * PDOStatement constructor.
28
     * @param PDOAdapter $connection
29
     * @param PDOStatement $statement
30
     * @param array $values
31
     */
32
    public function __construct(PDOAdapter $connection, PDOStatement $statement, array $values = null)
33
    {
34
        $this->connection = $connection;
35
        $this->stmt       = $statement;
36
        $this->values     = $values;
0 ignored issues
show
Documentation Bug introduced by
It seems like $values can be null. However, the property $values is declared as array. Maybe change the type of the property to array|null or add a type check?

Our type inference engine has found an assignment of a scalar value (like a string, an integer or null) to a property which is an array.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property.

To type hint that a parameter can be either an array or null, you can set a type hint of array and a default value of null. The PHP interpreter will then accept both an array or null for that parameter.

function aContainsB(array $needle = null, array  $haystack) {
    if (!$needle) {
        return false;
    }

    return array_intersect($haystack, $needle) == $haystack;
}

The function can be called with either null or an array for the parameter $needle but will only accept an array as $haystack.

Loading history...
37
    }
38
39
    /**
40
     * @inheritDoc
41
     */
42
    final public function getConnection(): AdapterInterface
43
    {
44
        return $this->connection;
45
    }
46
47
    /**
48
     * @inheritDoc
49
     */
50
    public function getQueryString(): string
51
    {
52
        return $this->stmt->queryString;
53
    }
54
55
    /**
56
     * @inheritDoc
57
     * @return PDOStatement
58
     */
59
    public function getWrappedStatement()
60
    {
61
        return $this->stmt;
62
    }
63
64
    /**
65
     * @inheritDoc
66
     */
67
    public function bind(): void
68
    {
69
        if ($this->hasValues()) {
70
            if (0 === array_keys($this->getValues())[0]) {
71
                $this->bindNumericParameters();
72
            } else {
73
                $this->bindNamedParameters();
74
            }
75
        }
76
    }
77
78
    /**
79
     * Bind :named parameters
80
     */
81
    protected function bindNamedParameters(): void
82
    {
83
        foreach ($this->getValues() as $key => $value) {
84
            $value = $this->toScalar($value);
85
            $this->getWrappedStatement()->bindValue(sprintf(':%s', $key), $value, $this->getPdoType($value));
86
        }
87
    }
88
89
    /**
90
     * Bind ? parameters
91
     */
92
    protected function bindNumericParameters(): void
93
    {
94
        $values = $this->getValues();
95
        $cnt    = count($values);
96
        for ($index0 = 0, $index1 = 1; $index0 < $cnt; $index0++, $index1++) {
97
            $value = $this->toScalar($values[$index0]);
98
            $this->getWrappedStatement()->bindValue($index1, $value, $this->getPdoType($value));
99
        }
100
    }
101
102
    /**
103
     * Attempt to convert non-scalar values.
104
     *
105
     * @param $value
106
     * @return string
107
     */
108
    protected function toScalar($value)
109
    {
110
        if (is_scalar($value) || null === $value) {
111
            return $value;
112
        } else {
113
            if (is_object($value)) {
114
                if (is_callable([$value, '__toString'])) {
115
                    return (string) $value;
116
                } elseif ($value instanceof \DateTimeInterface) {
117
                    return $value->format('Y-m-d H:i:s');
118
                } else {
119
                    throw new \InvalidArgumentException(sprintf("Cast of class %s is impossible", get_class($value)));
120
                }
121
            } else {
122
                throw new \InvalidArgumentException(sprintf("Cast of type %s is impossible", gettype($value)));
123
            }
124
        }
125
    }
126
127
    /**
128
     * @param $var
129
     * @return int
130
     */
131
    protected function getPdoType($value)
132
    {
133
        if (!is_scalar($value) && null !== $value) {
134
            throw new \InvalidArgumentException("Can only cast scalar variables.");
135
        }
136
        switch (strtolower(gettype($value))) :
137
            case 'integer':
138
                return PDO::PARAM_INT;
139
            case 'boolean':
140
                return PDO::PARAM_BOOL;
141
            case 'NULL':
142
                return PDO::PARAM_NULL;
143
            case 'double':
144
            case 'string':
145
            default:
146
                return PDO::PARAM_STR;
147
        endswitch;
148
    }
149
150
    /**
151
     * @inheritDoc
152
     */
153
    public function preview(): string
154
    {
155
        if (!$this->hasValues()) {
156
            return $this->stmt->queryString;
157
        }
158
159
        $escape = function ($value) {
160
            if (null === $value) {
161
                return 'NULL';
162
            }
163
            $value = $this->toScalar($value);
164
            $type = gettype($value);
165
            switch ($type) {
166
                case 'boolean':
167
                    return (int) $value;
168
                case 'double':
169
                case 'integer':
170
                    return $value;
171
                default:
172
                    return (string) "'" . addslashes($value) . "'";
173
            }
174
        };
175
176
        $keywords = [];
177
        $preview  = $this->stmt->queryString;
178
179
        # Case of question mark placeholders
180
        if ($this->hasAnonymousPlaceholders()) {
181
            foreach ($this->values as $value) {
182
                $preview = preg_replace("/([\?])/", $escape($value), $preview, 1);
183
            }
184
        } # Case of named placeholders
185
        else {
186
            foreach ($this->values as $key => $value) {
187
                if (!in_array($key, $keywords, true)) {
188
                    $keywords[] = $key;
189
                }
190
            }
191
            foreach ($keywords as $keyword) {
192
                $pattern = "/(\:\b" . $keyword . "\b)/i";
193
                $preview = preg_replace($pattern, $escape($this->values[$keyword]), $preview);
194
            }
195
        }
196
        return $preview;
197
    }
198
199
    /**
200
     * @inheritDoc
201
     */
202
    public function createResult(): ResultInterface
203
    {
204
        return new Result($this->getConnection()->getWrappedConnection(), $this->getWrappedStatement());
0 ignored issues
show
Bug introduced by
It seems like $this->getConnection()->getWrappedConnection() targeting BenTools\SimpleDBAL\Cont...:getWrappedConnection() can also be of type object<mysqli>; however, BenTools\SimpleDBAL\Mode...O\Result::__construct() does only seem to accept object<PDO>, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
205
    }
206
207
    /**
208
     * @inheritDoc
209
     */
210
    public function __toString(): string
211
    {
212
        return $this->getQueryString();
213
    }
214
}
215