DB2Statement.php 9.86 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 24

class DB2Statement implements \IteratorAggregate, Statement
25
{
Benjamin Morel's avatar
Benjamin Morel committed
26 27 28
    /**
     * @var resource
     */
29
    private $_stmt;
30

Benjamin Morel's avatar
Benjamin Morel committed
31 32 33
    /**
     * @var array
     */
34
    private $_bindParam = [];
35

36 37 38 39 40 41 42 43
    /**
     * @var string Name of the default class to instantiate when fetch mode is \PDO::FETCH_CLASS.
     */
    private $defaultFetchClass = '\stdClass';

    /**
     * @var string Constructor arguments for the default class to instantiate when fetch mode is \PDO::FETCH_CLASS.
     */
44
    private $defaultFetchClassCtorArgs = [];
45

Benjamin Morel's avatar
Benjamin Morel committed
46 47 48
    /**
     * @var integer
     */
49
    private $_defaultFetchMode = \PDO::FETCH_BOTH;
50

51 52 53 54 55 56 57
    /**
     * Indicates whether the statement is in the state when fetching results is possible
     *
     * @var bool
     */
    private $result = false;

58
    /**
59
     * DB2_BINARY, DB2_CHAR, DB2_DOUBLE, or DB2_LONG
Benjamin Morel's avatar
Benjamin Morel committed
60
     *
61
     * @var array
62
     */
63
    static private $_typeMap = [
64 65
        \PDO::PARAM_INT => DB2_LONG,
        \PDO::PARAM_STR => DB2_CHAR,
66
    ];
67

Benjamin Morel's avatar
Benjamin Morel committed
68 69 70
    /**
     * @param resource $stmt
     */
71 72 73 74 75 76
    public function __construct($stmt)
    {
        $this->_stmt = $stmt;
    }

    /**
77
     * {@inheritdoc}
78
     */
79
    public function bindValue($param, $value, $type = null)
80
    {
81
        return $this->bindParam($param, $value, $type);
82 83 84
    }

    /**
85
     * {@inheritdoc}
86
     */
87
    public function bindParam($column, &$variable, $type = null, $length = null)
88
    {
89 90 91
        $this->_bindParam[$column] =& $variable;

        if ($type && isset(self::$_typeMap[$type])) {
92 93 94 95 96 97
            $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
98
            throw new DB2Exception(db2_stmt_errormsg());
99
        }
Benjamin Morel's avatar
Benjamin Morel committed
100

101
        return true;
102 103 104
    }

    /**
105
     * {@inheritdoc}
106
     */
107
    public function closeCursor()
108
    {
109
        if ( ! $this->_stmt) {
110 111 112
            return false;
        }

113
        $this->_bindParam = [];
Benjamin Morel's avatar
Benjamin Morel committed
114

115 116 117 118 119 120 121
        if (!db2_free_result($this->_stmt)) {
            return false;
        }

        $this->result = false;

        return true;
122 123 124
    }

    /**
125
     * {@inheritdoc}
126
     */
127
    public function columnCount()
128
    {
129
        if ( ! $this->_stmt) {
130 131
            return false;
        }
Benjamin Morel's avatar
Benjamin Morel committed
132

133 134 135 136
        return db2_num_fields($this->_stmt);
    }

    /**
137
     * {@inheritdoc}
138
     */
139
    public function errorCode()
140 141 142 143 144
    {
        return db2_stmt_error();
    }

    /**
145
     * {@inheritdoc}
146
     */
147
    public function errorInfo()
148
    {
149
        return [
150 151
            db2_stmt_errormsg(),
            db2_stmt_error(),
152
        ];
153 154 155
    }

    /**
156
     * {@inheritdoc}
157
     */
158
    public function execute($params = null)
159
    {
160
        if ( ! $this->_stmt) {
161 162 163
            return false;
        }

164 165
        if ($params === null) {
            ksort($this->_bindParam);
166

167
            $params = [];
168 169 170 171

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

174
        $retval = @db2_execute($this->_stmt, $params);
175 176

        if ($retval === false) {
Benjamin Eberlei's avatar
Benjamin Eberlei committed
177
            throw new DB2Exception(db2_stmt_errormsg());
178
        }
Benjamin Morel's avatar
Benjamin Morel committed
179

180 181
        $this->result = true;

182 183 184
        return $retval;
    }

185 186 187
    /**
     * {@inheritdoc}
     */
188
    public function setFetchMode($fetchMode, $arg2 = null, $arg3 = null)
189
    {
190 191 192
        $this->_defaultFetchMode         = $fetchMode;
        $this->defaultFetchClass         = $arg2 ? $arg2 : $this->defaultFetchClass;
        $this->defaultFetchClassCtorArgs = $arg3 ? (array) $arg3 : $this->defaultFetchClassCtorArgs;
Benjamin Morel's avatar
Benjamin Morel committed
193 194

        return true;
195 196 197 198 199 200 201
    }

    /**
     * {@inheritdoc}
     */
    public function getIterator()
    {
202
        $data = $this->fetchAll();
Benjamin Morel's avatar
Benjamin Morel committed
203

204 205 206
        return new \ArrayIterator($data);
    }

207
    /**
208
     * {@inheritdoc}
209
     */
210
    public function fetch($fetchMode = null, $cursorOrientation = \PDO::FETCH_ORI_NEXT, $cursorOffset = 0)
211
    {
212 213 214 215 216 217
        // 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;
        }

218 219
        $fetchMode = $fetchMode ?: $this->_defaultFetchMode;
        switch ($fetchMode) {
220 221 222 223
            case \PDO::FETCH_BOTH:
                return db2_fetch_both($this->_stmt);
            case \PDO::FETCH_ASSOC:
                return db2_fetch_assoc($this->_stmt);
224 225 226 227 228 229 230
            case \PDO::FETCH_CLASS:
                $className = $this->defaultFetchClass;
                $ctorArgs  = $this->defaultFetchClassCtorArgs;

                if (func_num_args() >= 2) {
                    $args      = func_get_args();
                    $className = $args[1];
231
                    $ctorArgs  = isset($args[2]) ? $args[2] : [];
232 233 234 235 236 237 238 239 240
                }

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

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

                return $result;
241 242
            case \PDO::FETCH_NUM:
                return db2_fetch_array($this->_stmt);
243
            case \PDO::FETCH_OBJ:
244
                return db2_fetch_object($this->_stmt);
245
            default:
246
                throw new DB2Exception('Given Fetch-Style ' . $fetchMode . ' is not supported.');
247 248 249 250
        }
    }

    /**
251
     * {@inheritdoc}
252
     */
253
    public function fetchAll($fetchMode = null, $fetchArgument = null, $ctorArgs = null)
254
    {
255
        $rows = [];
256 257 258

        switch ($fetchMode) {
            case \PDO::FETCH_CLASS:
259
                while ($row = call_user_func_array([$this, 'fetch'], func_get_args())) {
260 261 262 263 264 265 266 267 268 269 270 271
                    $rows[] = $row;
                }
                break;
            case \PDO::FETCH_COLUMN:
                while ($row = $this->fetchColumn()) {
                    $rows[] = $row;
                }
                break;
            default:
                while ($row = $this->fetch($fetchMode)) {
                    $rows[] = $row;
                }
272
        }
Benjamin Morel's avatar
Benjamin Morel committed
273

274 275 276 277
        return $rows;
    }

    /**
278
     * {@inheritdoc}
279
     */
280
    public function fetchColumn($columnIndex = 0)
281 282
    {
        $row = $this->fetch(\PDO::FETCH_NUM);
283 284 285

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

288
        return isset($row[$columnIndex]) ? $row[$columnIndex] : null;
289 290 291
    }

    /**
292
     * {@inheritdoc}
293
     */
294
    public function rowCount()
295
    {
296
        return (@db2_num_rows($this->_stmt)) ? : 0;
297
    }
298 299 300 301 302 303 304 305 306 307 308 309

    /**
     * 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
     */
310
    private function castObject(\stdClass $sourceObject, $destinationClass, array $ctorArgs = [])
311 312 313 314 315 316 317 318 319 320 321 322 323 324
    {
        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);
325 326
        /** @var \ReflectionProperty[] $destinationProperties */
        $destinationProperties      = array_change_key_case($destinationClassReflection->getProperties(), \CASE_LOWER);
327 328 329 330 331 332 333

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

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

334
            // Try to find a case-matching property.
335 336 337 338 339
            if ($destinationClassReflection->hasProperty($name)) {
                $destinationProperty = $destinationClassReflection->getProperty($name);

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

                continue;
342
            }
343 344 345 346 347 348 349 350 351 352 353 354 355 356 357

            $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;
358 359 360 361
        }

        return $destinationClass;
    }
362
}