Passed
Pull Request — master (#461)
by Def
18:54 queued 16:18
created

AbstractCommandPDO::insertWithReturningPks()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 25
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 14
nc 4
nop 2
dl 0
loc 25
rs 9.7998
c 0
b 0
f 0

1 Method

Rating   Name   Duplication   Size   Complexity  
A AbstractCommandPDO::prepare() 0 21 3
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Db\Driver\PDO;
6
7
use PDO;
8
use PDOException;
9
use PDOStatement;
10
use Throwable;
11
use Yiisoft\Db\Cache\QueryCache;
12
use Yiisoft\Db\Command\AbstractCommand;
13
use Yiisoft\Db\Command\Param;
14
use Yiisoft\Db\Command\ParamInterface;
15
use Yiisoft\Db\Exception\Exception;
16
use Yiisoft\Db\Exception\InvalidConfigException;
17
use Yiisoft\Db\Exception\InvalidParamException;
18
use Yiisoft\Db\Query\Data\DataReader;
19
20
abstract class AbstractCommandPDO extends AbstractCommand implements CommandPDOInterface
21
{
22
    protected PDOStatement|null $pdoStatement = null;
23
24
    public function __construct(protected ConnectionPDOInterface $db, QueryCache $queryCache)
25
    {
26
        parent::__construct($queryCache);
27
    }
28
29
    /**
30
     * @inheritDoc
31
     * This method mainly sets {@see pdoStatement} to be null.
32
     */
33
    public function cancel(): void
34
    {
35
        $this->pdoStatement = null;
36
    }
37
38
    public function getPdoStatement(): PDOStatement|null
39
    {
40
        return $this->pdoStatement;
41
    }
42
43
    /**
44
     * @inheritDoc
45
     *
46
     * @link http://www.php.net/manual/en/function.PDOStatement-bindParam.php
47
     */
48
    public function bindParam(
49
        int|string $name,
50
        mixed &$value,
51
        int|null $dataType = null,
52
        int|null $length = null,
53
        mixed $driverOptions = null
54
    ): static {
55
        $this->prepare();
56
57
        if ($dataType === null) {
58
            $dataType = $this->db->getSchema()->getPdoType($value);
59
        }
60
61
        if ($length === null) {
62
            $this->pdoStatement?->bindParam($name, $value, $dataType);
63
        } elseif ($driverOptions === null) {
64
            $this->pdoStatement?->bindParam($name, $value, $dataType, $length);
65
        } else {
66
            $this->pdoStatement?->bindParam($name, $value, $dataType, $length, $driverOptions);
67
        }
68
69
        return $this;
70
    }
71
72
    public function bindValue(int|string $name, mixed $value, int|null $dataType = null): static
73
    {
74
        if ($dataType === null) {
75
            $dataType = $this->db->getSchema()->getPdoType($value);
76
        }
77
78
        $this->params[$name] = new Param($value, $dataType);
79
80
        return $this;
81
    }
82
83
    public function bindValues(array $values): static
84
    {
85
        if (empty($values)) {
86
            return $this;
87
        }
88
89
        /**
90
         * @psalm-var array<string, int>|ParamInterface|int $value
91
         */
92
        foreach ($values as $name => $value) {
93
            if ($value instanceof ParamInterface) {
94
                $this->params[$name] = $value;
95
            } else {
96
                $type = $this->db->getSchema()->getPdoType($value);
97
                $this->params[$name] = new Param($value, $type);
98
            }
99
        }
100
101
        return $this;
102
    }
103
104
    /**
105
     * @throws Exception|InvalidConfigException|PDOException
106
     */
107
    public function prepare(bool|null $forRead = null): void
108
    {
109
        if (isset($this->pdoStatement)) {
110
            $this->bindPendingParams();
111
112
            return;
113
        }
114
115
        $sql = $this->getSql();
116
117
        $pdo = $this->db->getActivePDO($sql, $forRead);
118
119
        try {
120
            $this->pdoStatement = $pdo?->prepare($sql);
121
            $this->bindPendingParams();
122
        } catch (PDOException $e) {
123
            $message = $e->getMessage() . "\nFailed to prepare SQL: $sql";
124
            /** @var array|null */
125
            $errorInfo = $e->errorInfo ?? null;
126
127
            throw new Exception($message, $errorInfo, $e);
128
        }
129
    }
130
131
    /**
132
     * Binds pending parameters that were registered via {@see bindValue()} and {@see bindValues()}.
133
     *
134
     * Note that this method requires an active {@see pdoStatement}.
135
     */
136
    protected function bindPendingParams(): void
137
    {
138
        foreach ($this->params as $name => $value) {
139
            $this->pdoStatement?->bindValue($name, $value->getValue(), $value->getType());
140
        }
141
    }
142
143
    protected function getCacheKey(int $queryMode, string $rawSql): array
144
    {
145
        return array_merge([static::class , $queryMode], $this->db->getCacheKey(), [$rawSql]);
146
    }
147
148
    /**
149
     * @throws InvalidParamException
150
     */
151
    protected function internalGetQueryResult(int $queryMode): mixed
152
    {
153
        if ($queryMode === static::QUERY_MODE_CURSOR) {
154
            return new DataReader($this);
155
        }
156
157
        if ($queryMode === static::QUERY_MODE_NONE) {
158
            return $this->pdoStatement?->rowCount() ?? 0;
159
        }
160
161
        if ($queryMode === static::QUERY_MODE_ROW) {
162
            /** @var mixed */
163
            $result = $this->pdoStatement?->fetch(PDO::FETCH_ASSOC);
164
        } elseif ($queryMode === static::QUERY_MODE_COLUMN) {
165
            /** @var mixed */
166
            $result = $this->pdoStatement?->fetchAll(PDO::FETCH_COLUMN);
167
        } else {
168
            /** @var mixed */
169
            $result = $this->pdoStatement?->fetchAll(PDO::FETCH_ASSOC);
170
        }
171
172
        $this->pdoStatement?->closeCursor();
173
174
        return $result;
175
    }
176
177
    /**
178
     * Refreshes table schema, which was marked by {@see requireTableSchemaRefresh()}.
179
     */
180
    protected function refreshTableSchema(): void
181
    {
182
        if ($this->refreshTableName !== null) {
183
            $this->db->getSchema()->refreshTableSchema($this->refreshTableName);
184
        }
185
    }
186
187
    /**
188
     * Executes a prepared statement.
189
     *
190
     * It's a wrapper around {@see PDOStatement::execute()} to support transactions and retry handlers.
191
     *
192
     * @param string|null $rawSql the rawSql if it has been created.
193
     *
194
     * @throws Exception|Throwable
195
     */
196
    abstract protected function internalExecute(string|null $rawSql): void;
197
}
198