DB2Statement.php 10.7 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
<?php
/*
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * This software consists of voluntary contributions made by many individuals
Benjamin Eberlei's avatar
Benjamin Eberlei committed
16
 * and is licensed under the MIT license. For more information, see
17
 * <http://www.doctrine-project.org>.
Benjamin Morel's avatar
Benjamin Morel committed
18
 */
19

Benjamin Eberlei's avatar
Benjamin Eberlei committed
20
namespace Doctrine\DBAL\Driver\IBMDB2;
21

Steve Müller's avatar
Steve Müller committed
22
use Doctrine\DBAL\Driver\Statement;
23
use Doctrine\DBAL\Driver\StatementIterator;
24 25
use Doctrine\DBAL\FetchMode;
use Doctrine\DBAL\ParameterType;
26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
use const DB2_CHAR;
use const DB2_LONG;
use const DB2_PARAM_IN;
use function array_change_key_case;
use function call_user_func_array;
use function db2_bind_param;
use function db2_execute;
use function db2_fetch_array;
use function db2_fetch_assoc;
use function db2_fetch_both;
use function db2_fetch_object;
use function db2_free_result;
use function db2_num_fields;
use function db2_num_rows;
use function db2_stmt_error;
use function db2_stmt_errormsg;
use function func_get_args;
use function func_num_args;
use function gettype;
use function is_object;
use function is_string;
use function ksort;
use function sprintf;
use function strtolower;
50 51

class DB2Statement implements \IteratorAggregate, Statement
52
{
Benjamin Morel's avatar
Benjamin Morel committed
53 54 55
    /**
     * @var resource
     */
56
    private $_stmt;
57

Benjamin Morel's avatar
Benjamin Morel committed
58 59 60
    /**
     * @var array
     */
61
    private $_bindParam = [];
62

63
    /**
64
     * @var string Name of the default class to instantiate when fetching class instances.
65 66 67 68
     */
    private $defaultFetchClass = '\stdClass';

    /**
69
     * @var string Constructor arguments for the default class to instantiate when fetching class instances.
70
     */
71
    private $defaultFetchClassCtorArgs = [];
72

Benjamin Morel's avatar
Benjamin Morel committed
73
    /**
74
     * @var int
Benjamin Morel's avatar
Benjamin Morel committed
75
     */
76
    private $_defaultFetchMode = FetchMode::MIXED;
77

78 79 80 81 82 83 84
    /**
     * Indicates whether the statement is in the state when fetching results is possible
     *
     * @var bool
     */
    private $result = false;

85
    /**
86
     * DB2_BINARY, DB2_CHAR, DB2_DOUBLE, or DB2_LONG
Benjamin Morel's avatar
Benjamin Morel committed
87
     *
88
     * @var array
89
     */
90
    static private $_typeMap = [
91
        ParameterType::INTEGER => DB2_LONG,
Sergei Morozov's avatar
Sergei Morozov committed
92
        ParameterType::STRING  => DB2_CHAR,
93
    ];
94

Benjamin Morel's avatar
Benjamin Morel committed
95 96 97
    /**
     * @param resource $stmt
     */
98 99 100 101 102 103
    public function __construct($stmt)
    {
        $this->_stmt = $stmt;
    }

    /**
104
     * {@inheritdoc}
105
     */
106
    public function bindValue($param, $value, $type = ParameterType::STRING)
107
    {
108
        return $this->bindParam($param, $value, $type);
109 110 111
    }

    /**
112
     * {@inheritdoc}
113
     */
114
    public function bindParam($column, &$variable, $type = ParameterType::STRING, $length = null)
115
    {
116 117 118
        $this->_bindParam[$column] =& $variable;

        if ($type && isset(self::$_typeMap[$type])) {
119 120 121 122 123 124
            $type = self::$_typeMap[$type];
        } else {
            $type = DB2_CHAR;
        }

        if (!db2_bind_param($this->_stmt, $column, "variable", DB2_PARAM_IN, $type)) {
Benjamin Eberlei's avatar
Benjamin Eberlei committed
125
            throw new DB2Exception(db2_stmt_errormsg());
126
        }
Benjamin Morel's avatar
Benjamin Morel committed
127

128
        return true;
129 130 131
    }

    /**
132
     * {@inheritdoc}
133
     */
134
    public function closeCursor()
135
    {
136
        if ( ! $this->_stmt) {
137 138 139
            return false;
        }

140
        $this->_bindParam = [];
Benjamin Morel's avatar
Benjamin Morel committed
141

142 143 144 145 146 147 148
        if (!db2_free_result($this->_stmt)) {
            return false;
        }

        $this->result = false;

        return true;
149 150 151
    }

    /**
152
     * {@inheritdoc}
153
     */
154
    public function columnCount()
155
    {
156
        if ( ! $this->_stmt) {
157 158
            return false;
        }
Benjamin Morel's avatar
Benjamin Morel committed
159

160 161 162 163
        return db2_num_fields($this->_stmt);
    }

    /**
164
     * {@inheritdoc}
165
     */
166
    public function errorCode()
167 168 169 170 171
    {
        return db2_stmt_error();
    }

    /**
172
     * {@inheritdoc}
173
     */
174
    public function errorInfo()
175
    {
176
        return [
177 178
            db2_stmt_errormsg(),
            db2_stmt_error(),
179
        ];
180 181 182
    }

    /**
183
     * {@inheritdoc}
184
     */
185
    public function execute($params = null)
186
    {
187
        if ( ! $this->_stmt) {
188 189 190
            return false;
        }

191 192
        if ($params === null) {
            ksort($this->_bindParam);
193

194
            $params = [];
195 196 197 198

            foreach ($this->_bindParam as $column => $value) {
                $params[] = $value;
            }
199
        }
200

201
        $retval = db2_execute($this->_stmt, $params);
202 203

        if ($retval === false) {
Benjamin Eberlei's avatar
Benjamin Eberlei committed
204
            throw new DB2Exception(db2_stmt_errormsg());
205
        }
Benjamin Morel's avatar
Benjamin Morel committed
206

207 208
        $this->result = true;

209 210 211
        return $retval;
    }

212 213 214
    /**
     * {@inheritdoc}
     */
215
    public function setFetchMode($fetchMode, $arg2 = null, $arg3 = null)
216
    {
217 218 219
        $this->_defaultFetchMode         = $fetchMode;
        $this->defaultFetchClass         = $arg2 ? $arg2 : $this->defaultFetchClass;
        $this->defaultFetchClassCtorArgs = $arg3 ? (array) $arg3 : $this->defaultFetchClassCtorArgs;
Benjamin Morel's avatar
Benjamin Morel committed
220 221

        return true;
222 223 224 225 226 227 228
    }

    /**
     * {@inheritdoc}
     */
    public function getIterator()
    {
229
        return new StatementIterator($this);
230 231
    }

232
    /**
233
     * {@inheritdoc}
234
     */
235
    public function fetch($fetchMode = null, $cursorOrientation = \PDO::FETCH_ORI_NEXT, $cursorOffset = 0)
236
    {
237 238 239 240 241 242
        // do not try fetching from the statement if it's not expected to contain result
        // in order to prevent exceptional situation
        if (!$this->result) {
            return false;
        }

243 244
        $fetchMode = $fetchMode ?: $this->_defaultFetchMode;
        switch ($fetchMode) {
245
            case FetchMode::COLUMN:
246 247
                return $this->fetchColumn();

248
            case FetchMode::MIXED:
249
                return db2_fetch_both($this->_stmt);
250 251

            case FetchMode::ASSOCIATIVE:
252
                return db2_fetch_assoc($this->_stmt);
253 254

            case FetchMode::CUSTOM_OBJECT:
255 256 257 258 259 260
                $className = $this->defaultFetchClass;
                $ctorArgs  = $this->defaultFetchClassCtorArgs;

                if (func_num_args() >= 2) {
                    $args      = func_get_args();
                    $className = $args[1];
261
                    $ctorArgs  = $args[2] ?? [];
262 263 264 265 266 267 268 269 270
                }

                $result = db2_fetch_object($this->_stmt);

                if ($result instanceof \stdClass) {
                    $result = $this->castObject($result, $className, $ctorArgs);
                }

                return $result;
271 272

            case FetchMode::NUMERIC:
273
                return db2_fetch_array($this->_stmt);
274 275

            case FetchMode::STANDARD_OBJECT:
276
                return db2_fetch_object($this->_stmt);
277

278
            default:
279
                throw new DB2Exception('Given Fetch-Style ' . $fetchMode . ' is not supported.');
280 281 282 283
        }
    }

    /**
284
     * {@inheritdoc}
285
     */
286
    public function fetchAll($fetchMode = null, $fetchArgument = null, $ctorArgs = null)
287
    {
288
        $rows = [];
289 290

        switch ($fetchMode) {
291
            case FetchMode::CUSTOM_OBJECT:
292
                while ($row = call_user_func_array([$this, 'fetch'], func_get_args())) {
293 294 295
                    $rows[] = $row;
                }
                break;
296
            case FetchMode::COLUMN:
297 298 299 300 301 302 303 304
                while ($row = $this->fetchColumn()) {
                    $rows[] = $row;
                }
                break;
            default:
                while ($row = $this->fetch($fetchMode)) {
                    $rows[] = $row;
                }
305
        }
Benjamin Morel's avatar
Benjamin Morel committed
306

307 308 309 310
        return $rows;
    }

    /**
311
     * {@inheritdoc}
312
     */
313
    public function fetchColumn($columnIndex = 0)
314
    {
315
        $row = $this->fetch(FetchMode::NUMERIC);
316 317 318

        if (false === $row) {
            return false;
319
        }
Benjamin Morel's avatar
Benjamin Morel committed
320

321
        return $row[$columnIndex] ?? null;
322 323 324
    }

    /**
325
     * {@inheritdoc}
326
     */
327
    public function rowCount()
328
    {
329
        return (@db2_num_rows($this->_stmt)) ? : 0;
330
    }
331 332 333 334 335 336 337 338 339 340 341 342

    /**
     * Casts a stdClass object to the given class name mapping its' properties.
     *
     * @param \stdClass     $sourceObject     Object to cast from.
     * @param string|object $destinationClass Name of the class or class instance to cast to.
     * @param array         $ctorArgs         Arguments to use for constructing the destination class instance.
     *
     * @return object
     *
     * @throws DB2Exception
     */
343
    private function castObject(\stdClass $sourceObject, $destinationClass, array $ctorArgs = [])
344 345 346 347 348 349 350 351 352 353 354 355 356 357
    {
        if ( ! is_string($destinationClass)) {
            if ( ! is_object($destinationClass)) {
                throw new DB2Exception(sprintf(
                    'Destination class has to be of type string or object, %s given.', gettype($destinationClass)
                ));
            }
        } else {
            $destinationClass = new \ReflectionClass($destinationClass);
            $destinationClass = $destinationClass->newInstanceArgs($ctorArgs);
        }

        $sourceReflection           = new \ReflectionObject($sourceObject);
        $destinationClassReflection = new \ReflectionObject($destinationClass);
358 359
        /** @var \ReflectionProperty[] $destinationProperties */
        $destinationProperties      = array_change_key_case($destinationClassReflection->getProperties(), \CASE_LOWER);
360 361 362 363 364 365 366

        foreach ($sourceReflection->getProperties() as $sourceProperty) {
            $sourceProperty->setAccessible(true);

            $name  = $sourceProperty->getName();
            $value = $sourceProperty->getValue($sourceObject);

367
            // Try to find a case-matching property.
368 369 370 371 372
            if ($destinationClassReflection->hasProperty($name)) {
                $destinationProperty = $destinationClassReflection->getProperty($name);

                $destinationProperty->setAccessible(true);
                $destinationProperty->setValue($destinationClass, $value);
373 374

                continue;
375
            }
376 377 378 379 380 381 382 383 384 385 386 387 388 389 390

            $name = strtolower($name);

            // Try to find a property without matching case.
            // Fallback for the driver returning either all uppercase or all lowercase column names.
            if (isset($destinationProperties[$name])) {
                $destinationProperty = $destinationProperties[$name];

                $destinationProperty->setAccessible(true);
                $destinationProperty->setValue($destinationClass, $value);

                continue;
            }

            $destinationClass->$name = $value;
391 392 393 394
        }

        return $destinationClass;
    }
395
}