Issues (65)

src/Result.php (2 issues)

Labels
Severity
1
<?php
2
3
4
/**
5
 * Provides a simple interface to run sql statements
6
 * Main methods: run, ran, ret
7
 * 
8
 * Constructor
9
 *      allows statement as string, PDOStatements or QueryInterface
10
 *      calls run() with provided bindings (optionals)
11
 * 
12
 * All PDOStatement methods are available by encapsulation through the magic __call method.
13
 * 
14
 * Fetching:
15
 *      ret     wrapper for fetchAll()
16
 *      retOne  wrapper for fetch()
17
 *      retObject  fetchAll(\PDO::FETCH_OBJ) unless a class name is provided then fetchAll(\PDO::FETCH_CLASS | \PDO::FETCH_PROPS_LATE)
18
 * 
19
 * Here is a list of all the PDO fetch constants:
20
 *
21
 * PDO::FETCH_ASSOC     : Returns rows as an associative array, with column names as keys.
22
 * PDO::FETCH_NUM       : Returns rows as a numeric array, with column indexes as keys.
23
 * PDO::FETCH_BOTH      : Default fetch style; returns rows as both an associative and numeric array.
24
 * PDO::FETCH_OBJ       : Returns rows as an object with property names corresponding to column names.
25
 * PDO::FETCH_LAZY      : Combines PDO::FETCH_BOTH, PDO::FETCH_OBJ, and PDO::FETCH_BOUND. Allows accessing columns in multiple ways.
26
 * PDO::FETCH_BOUND     : Assigns columns to PHP variables using bindColumn().
27
 * PDO::FETCH_CLASS     : Maps rows to a specified class, optionally calling a constructor.
28
 * PDO::FETCH_INTO      : Updates an existing object with column values.
29
 * PDO::FETCH_GROUP     : Groups rows by the first column's values.
30
 * PDO::FETCH_UNIQUE    : Uses the first column's values as keys, ensuring unique rows.
31
 * PDO::FETCH_COLUMN    : Returns a single column from each row.
32
 * PDO::FETCH_KEY_PAIR  : Fetches rows as key-value pairs, using the first two columns.
33
 * PDO::FETCH_FUNC      : Passes each row's data to a user-defined function and returns the result.
34
 * PDO::FETCH_NAMED     : Similar to PDO::FETCH_ASSOC, but handles duplicate column names by returning an array of values for each name.
35
 * PDO::FETCH_CLASS | PDO::FETCH_PROPS_LATE : Used with PDO::FETCH_CLASS, delays property population until after the constructor has been called.
36
 * PDO::FETCH_SERIALIZE : Serializes the returned data (requires specific driver support).
37
 */
38
39
namespace HexMakina\Crudites;
40
41
use HexMakina\BlackBox\Database\QueryInterface;
42
use HexMakina\BlackBox\Database\ResultInterface;
0 ignored issues
show
The type HexMakina\BlackBox\Database\ResultInterface was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
43
44
class Result implements ResultInterface
45
{
46
    private \PDO $pdo;
47
    private \PDOStatement $prepared;
48
    private ?\PDOStatement $executed = null;
49
50
    // string or PDOStatement
51
    private $statement;
52
53
    private array $bindings = [];
54
55
56
    public const PDO_STATE_SUCCESS = '00000'; //PDO "error" code for "all is fine"
57
58
59
    /**
60
     * @param \PDO $pdo
61
     * @param string|\PDOStatement|QueryInterface $statement, the SQL statement to run, raw or prepared
62
     * @param array $bindings, optional bindings for the prepared statement's execution
63
     */
64
    public function __construct(\PDO $pdo, $statement, array $bindings = [])
65
    {
66
        $this->pdo = $pdo;
67
68
        if ($statement instanceof QueryInterface) {
69
            $this->statement = (string)$statement;
70
            $bindings = $statement->bindings();
0 ignored issues
show
The method bindings() does not exist on HexMakina\BlackBox\Database\QueryInterface. It seems like you code against a sub-type of HexMakina\BlackBox\Database\QueryInterface such as HexMakina\Crudites\Grammar\Query\Query. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

70
            /** @scrutinizer ignore-call */ 
71
            $bindings = $statement->bindings();
Loading history...
71
        } else {
72
            $this->statement = $statement;
73
        }
74
75
        if ($this->statement instanceof \PDOStatement)
76
            $this->prepared = $this->statement;
77
78
        $this->run($bindings);
79
    }
80
81
    /**
82
     * transfers calls to the executed (or prepared) PDOStatement instance, if the method exists
83
     * if no statements are available, it is transferred to the PDO instance, if the method exists
84
     * if no call is possible, a BadMethodCallException is thrown
85
     * @param string $method
86
     * @param array $args
87
     * @return mixed
88
     * @throws \BadMethodCallException
89
     */
90
    public function __call($method, $args)
91
    {
92
        // first use the executed instance, then the prepared one
93
        // make senses for chronology and error handling
94
        $pdo_cascade = $this->executed ?? $this->prepared;
95
96
        if ($pdo_cascade === null || !method_exists($pdo_cascade, $method)) {
97
            $pdo_cascade = $this->pdo;
98
        }
99
        // two time testing is necessary, f.i. lastInsertId is a PDO method, not a PDOStatement method
100
        if ($pdo_cascade === null || !method_exists($pdo_cascade, $method))
101
            throw new \BadMethodCallException("__call($method) not possible in PDO or PDOStatement");
102
103
        return call_user_func_array([$pdo_cascade, $method], $args);
104
    }
105
106
    public function run(array $bindings = [])
107
    {
108
        // (re)set the executed PDOStatement instance
109
        $this->executed = null;
110
        $this->bindings = $bindings;
111
        if (isset($this->prepared)) {
112
            return $this->executePrepared($bindings);
113
        }
114
115
        if (empty($bindings)) {
116
            return $this->queryStatement();
117
        }
118
119
        return $this->prepareAndRun($bindings);
120
    }
121
122
    public function ran(): bool
123
    {
124
        return $this->executed !== null && $this->executed->errorCode() === self::PDO_STATE_SUCCESS;
125
    }
126
127
    public function ret($mode = \PDO::FETCH_ASSOC, $fetch_argument = null, $ctor_args = null)
128
    {
129
        if (!$this->ran()) {
130
            throw new CruditesException('No executed statement available for fetching results');
131
        }
132
133
        if ($mode === \PDO::FETCH_CLASS)
134
            return $this->executed->fetchAll($mode, $fetch_argument, $ctor_args);
135
136
        return $this->executed->fetchAll($mode);
137
    }
138
139
    public function retOne($mode = \PDO::FETCH_ASSOC, $orientation = null, $offset = null)
140
    {
141
        if (!$this->ran()) {
142
            throw new CruditesException('No executed statement available for fetching');
143
        }
144
        return $this->executed->fetch($mode, $orientation, $offset);
145
    }
146
147
    public function retObject(string $class = null)
148
    {
149
        return $class === null ? $this->ret(\PDO::FETCH_OBJ) : $this->ret(\PDO::FETCH_CLASS | \PDO::FETCH_PROPS_LATE, $class);
150
    }
151
152
    // wrapper for rowCount()
153
    public function count(): int
154
    {
155
        if (!$this->ran()) {
156
            throw new CruditesException('No executed statement available for counting results');
157
        }
158
        return $this->executed->rowCount();
159
    }
160
161
    public function errorInfo(): array
162
    {
163
        return ($this->executed ?? $this->prepared ?? $this->pdo)->errorInfo();
164
    }
165
166
    /**
167
     * Returns a formatted error message of the last operation (execution, preparation or query)
168
     * Format is: "message (state: code)"
169
     * 
170
     * @return string
171
     */
172
    public function errorMessageWithCodes(): string
173
    {
174
        // magic call to errorInfo()
175
        list($state, $code, $message) = $this->errorInfo();
176
        return sprintf('%s (state: %s, code: %s)', $message, $state, $code);
177
    }
178
179
180
    private function executePrepared(array $bindings): self
181
    {
182
        if ($this->prepared->execute($bindings) !== false) {
183
            $this->executed = $this->prepared;
184
            $this->bindings = $bindings;
185
            return $this;
186
        }
187
188
        throw new CruditesException('PDOSTATEMENT_EXECUTE');
189
    }
190
191
    private function queryStatement(): self
192
    {
193
        if (($res = $this->pdo->query((string)$this->statement)) !== false) {
194
            $this->executed = $res;
195
            return $this;
196
        }
197
198
        throw new CruditesException('PDO_QUERY_STRING');
199
    }
200
201
    private function prepareAndRun(array $bindings): self
202
    {
203
        if (($res = $this->pdo->prepare((string)$this->statement)) !== false) {
204
            $this->prepared = $res;
205
            return $this->run($bindings);
206
        }
207
208
        throw new CruditesException('PDO_PREPARE_STRING');
209
    }
210
}
211