SQLAnywhereStatement.php 9.9 KB
Newer Older
1 2 3 4
<?php

namespace Doctrine\DBAL\Driver\SQLAnywhere;

5
use Doctrine\DBAL\Driver\Statement;
6
use Doctrine\DBAL\Driver\StatementIterator;
7 8
use Doctrine\DBAL\FetchMode;
use Doctrine\DBAL\ParameterType;
9
use IteratorAggregate;
10 11 12 13
use PDO;
use ReflectionClass;
use ReflectionObject;
use stdClass;
14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
use const SASQL_BOTH;
use function array_key_exists;
use function func_get_args;
use function func_num_args;
use function gettype;
use function is_array;
use function is_numeric;
use function is_object;
use function is_resource;
use function is_string;
use function sasql_fetch_array;
use function sasql_fetch_assoc;
use function sasql_fetch_object;
use function sasql_fetch_row;
use function sasql_prepare;
use function sasql_stmt_affected_rows;
use function sasql_stmt_bind_param_ex;
use function sasql_stmt_errno;
use function sasql_stmt_error;
use function sasql_stmt_execute;
use function sasql_stmt_field_count;
use function sasql_stmt_reset;
use function sasql_stmt_result_metadata;
use function sprintf;
38 39 40 41 42 43

/**
 * SAP SQL Anywhere implementation of the Statement interface.
 */
class SQLAnywhereStatement implements IteratorAggregate, Statement
{
44
    /** @var resource The connection resource. */
45
    private $conn;
Steve Müller's avatar
Steve Müller committed
46

47
    /** @var string Name of the default class to instantiate when fetching class instances. */
48
    private $defaultFetchClass = '\stdClass';
Steve Müller's avatar
Steve Müller committed
49

50
    /** @var mixed[] Constructor arguments for the default class to instantiate when fetching class instances. */
51
    private $defaultFetchClassCtorArgs = [];
Steve Müller's avatar
Steve Müller committed
52

53
    /** @var int Default fetch mode to use. */
54
    private $defaultFetchMode = FetchMode::MIXED;
Steve Müller's avatar
Steve Müller committed
55

56
    /** @var resource The result set resource to fetch. */
57
    private $result;
Steve Müller's avatar
Steve Müller committed
58

59
    /** @var resource The prepared SQL statement to execute. */
60 61
    private $stmt;

62 63 64
    /** @var mixed[] The references to bound parameter values. */
    private $boundValues = [];

65 66 67 68 69 70 71 72 73 74
    /**
     * Prepares given statement for given connection.
     *
     * @param resource $conn The connection resource to use.
     * @param string   $sql  The SQL statement to prepare.
     *
     * @throws SQLAnywhereException
     */
    public function __construct($conn, $sql)
    {
75
        if (! is_resource($conn)) {
76 77 78 79 80 81
            throw new SQLAnywhereException('Invalid SQL Anywhere connection resource: ' . $conn);
        }

        $this->conn = $conn;
        $this->stmt = sasql_prepare($conn, $sql);

82
        if (! is_resource($this->stmt)) {
83 84 85 86 87 88 89 90 91
            throw SQLAnywhereException::fromSQLAnywhereError($conn);
        }
    }

    /**
     * {@inheritdoc}
     *
     * @throws SQLAnywhereException
     */
92
    public function bindParam($column, &$variable, $type = ParameterType::STRING, $length = null)
93 94
    {
        switch ($type) {
95 96
            case ParameterType::INTEGER:
            case ParameterType::BOOLEAN:
97 98
                $type = 'i';
                break;
99 100

            case ParameterType::LARGE_OBJECT:
101 102
                $type = 'b';
                break;
103 104 105

            case ParameterType::NULL:
            case ParameterType::STRING:
106
            case ParameterType::BINARY:
107 108
                $type = 's';
                break;
109

110 111 112 113
            default:
                throw new SQLAnywhereException('Unknown type: ' . $type);
        }

114 115
        $this->boundValues[$column] =& $variable;

116
        if (! sasql_stmt_bind_param_ex($this->stmt, $column - 1, $variable, $type, $variable === null)) {
117 118 119 120 121 122 123 124 125
            throw SQLAnywhereException::fromSQLAnywhereError($this->conn, $this->stmt);
        }

        return true;
    }

    /**
     * {@inheritdoc}
     */
126
    public function bindValue($param, $value, $type = ParameterType::STRING)
127 128 129 130 131 132 133 134 135 136 137
    {
        return $this->bindParam($param, $value, $type);
    }

    /**
     * {@inheritdoc}
     *
     * @throws SQLAnywhereException
     */
    public function closeCursor()
    {
138
        if (! sasql_stmt_reset($this->stmt)) {
139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175
            throw SQLAnywhereException::fromSQLAnywhereError($this->conn, $this->stmt);
        }

        return true;
    }

    /**
     * {@inheritdoc}
     */
    public function columnCount()
    {
        return sasql_stmt_field_count($this->stmt);
    }

    /**
     * {@inheritdoc}
     */
    public function errorCode()
    {
        return sasql_stmt_errno($this->stmt);
    }

    /**
     * {@inheritdoc}
     */
    public function errorInfo()
    {
        return sasql_stmt_error($this->stmt);
    }

    /**
     * {@inheritdoc}
     *
     * @throws SQLAnywhereException
     */
    public function execute($params = null)
    {
Steve Müller's avatar
Steve Müller committed
176
        if (is_array($params)) {
177
            $hasZeroIndex = array_key_exists(0, $params);
Steve Müller's avatar
Steve Müller committed
178

179
            foreach ($params as $key => $val) {
180
                $key = $hasZeroIndex && is_numeric($key) ? $key + 1 : $key;
Steve Müller's avatar
Steve Müller committed
181

182 183 184 185
                $this->bindValue($key, $val);
            }
        }

186
        if (! sasql_stmt_execute($this->stmt)) {
187 188 189 190 191 192 193 194 195 196 197 198 199
            throw SQLAnywhereException::fromSQLAnywhereError($this->conn, $this->stmt);
        }

        $this->result = sasql_stmt_result_metadata($this->stmt);

        return true;
    }

    /**
     * {@inheritdoc}
     *
     * @throws SQLAnywhereException
     */
200
    public function fetch($fetchMode = null, $cursorOrientation = PDO::FETCH_ORI_NEXT, $cursorOffset = 0)
201
    {
202
        if (! is_resource($this->result)) {
203 204 205 206 207 208
            return false;
        }

        $fetchMode = $fetchMode ?: $this->defaultFetchMode;

        switch ($fetchMode) {
209
            case FetchMode::COLUMN:
210 211
                return $this->fetchColumn();

212
            case FetchMode::ASSOCIATIVE:
213
                return sasql_fetch_assoc($this->result);
214 215

            case FetchMode::MIXED:
216
                return sasql_fetch_array($this->result, SASQL_BOTH);
217 218

            case FetchMode::CUSTOM_OBJECT:
219
                $className = $this->defaultFetchClass;
Steve Müller's avatar
Steve Müller committed
220
                $ctorArgs  = $this->defaultFetchClassCtorArgs;
221 222

                if (func_num_args() >= 2) {
Steve Müller's avatar
Steve Müller committed
223
                    $args      = func_get_args();
224
                    $className = $args[1];
225
                    $ctorArgs  = $args[2] ?? [];
226 227 228 229
                }

                $result = sasql_fetch_object($this->result);

230
                if ($result instanceof stdClass) {
231 232 233 234
                    $result = $this->castObject($result, $className, $ctorArgs);
                }

                return $result;
235 236

            case FetchMode::NUMERIC:
237
                return sasql_fetch_row($this->result);
238 239

            case FetchMode::STANDARD_OBJECT:
240
                return sasql_fetch_object($this->result);
241

242 243 244 245 246 247 248 249
            default:
                throw new SQLAnywhereException('Fetch mode is not supported: ' . $fetchMode);
        }
    }

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

        switch ($fetchMode) {
255
            case FetchMode::CUSTOM_OBJECT:
256
                while (($row = $this->fetch(...func_get_args())) !== false) {
257 258 259
                    $rows[] = $row;
                }
                break;
260 261

            case FetchMode::COLUMN:
262
                while (($row = $this->fetchColumn()) !== false) {
263 264 265
                    $rows[] = $row;
                }
                break;
266

267
            default:
268
                while (($row = $this->fetch($fetchMode)) !== false) {
269 270 271 272 273 274 275 276 277 278 279 280
                    $rows[] = $row;
                }
        }

        return $rows;
    }

    /**
     * {@inheritdoc}
     */
    public function fetchColumn($columnIndex = 0)
    {
281
        $row = $this->fetch(FetchMode::NUMERIC);
282

283
        if ($row === false) {
284
            return false;
285 286
        }

287
        return $row[$columnIndex] ?? null;
288 289 290 291 292 293 294
    }

    /**
     * {@inheritdoc}
     */
    public function getIterator()
    {
295
        return new StatementIterator($this);
296 297 298 299 300 301 302 303 304 305 306 307 308 309 310
    }

    /**
     * {@inheritdoc}
     */
    public function rowCount()
    {
        return sasql_stmt_affected_rows($this->stmt);
    }

    /**
     * {@inheritdoc}
     */
    public function setFetchMode($fetchMode, $arg2 = null, $arg3 = null)
    {
Steve Müller's avatar
Steve Müller committed
311
        $this->defaultFetchMode          = $fetchMode;
312
        $this->defaultFetchClass         = $arg2 ?: $this->defaultFetchClass;
313 314 315 316 317 318
        $this->defaultFetchClassCtorArgs = $arg3 ? (array) $arg3 : $this->defaultFetchClassCtorArgs;
    }

    /**
     * Casts a stdClass object to the given class name mapping its' properties.
     *
319
     * @param stdClass      $sourceObject     Object to cast from.
320
     * @param string|object $destinationClass Name of the class or class instance to cast to.
321
     * @param mixed[]       $ctorArgs         Arguments to use for constructing the destination class instance.
322 323 324 325 326
     *
     * @return object
     *
     * @throws SQLAnywhereException
     */
327
    private function castObject(stdClass $sourceObject, $destinationClass, array $ctorArgs = [])
328
    {
329 330
        if (! is_string($destinationClass)) {
            if (! is_object($destinationClass)) {
331
                throw new SQLAnywhereException(sprintf(
332 333
                    'Destination class has to be of type string or object, %s given.',
                    gettype($destinationClass)
334 335 336
                ));
            }
        } else {
337
            $destinationClass = new ReflectionClass($destinationClass);
338 339 340
            $destinationClass = $destinationClass->newInstanceArgs($ctorArgs);
        }

341 342
        $sourceReflection           = new ReflectionObject($sourceObject);
        $destinationClassReflection = new ReflectionObject($destinationClass);
343 344 345

        foreach ($sourceReflection->getProperties() as $sourceProperty) {
            $sourceProperty->setAccessible(true);
Steve Müller's avatar
Steve Müller committed
346 347

            $name  = $sourceProperty->getName();
348 349 350 351
            $value = $sourceProperty->getValue($sourceObject);

            if ($destinationClassReflection->hasProperty($name)) {
                $destinationProperty = $destinationClassReflection->getProperty($name);
Steve Müller's avatar
Steve Müller committed
352

353 354 355 356 357 358 359 360 361 362
                $destinationProperty->setAccessible(true);
                $destinationProperty->setValue($destinationClass, $value);
            } else {
                $destinationClass->$name = $value;
            }
        }

        return $destinationClass;
    }
}