SQLAnywhereStatement.php 9.79 KB
Newer Older
1 2
<?php

Michael Moravec's avatar
Michael Moravec committed
3 4
declare(strict_types=1);

5 6
namespace Doctrine\DBAL\Driver\SQLAnywhere;

7
use Doctrine\DBAL\Driver\Statement;
8
use Doctrine\DBAL\Driver\StatementIterator;
9
use Doctrine\DBAL\Exception\GetVariableType;
10
use Doctrine\DBAL\Exception\InvalidColumnIndex;
11 12
use Doctrine\DBAL\FetchMode;
use Doctrine\DBAL\ParameterType;
13
use IteratorAggregate;
14 15 16
use ReflectionClass;
use ReflectionObject;
use stdClass;
17 18
use const SASQL_BOTH;
use function array_key_exists;
19
use function assert;
20
use function count;
21 22
use function func_get_args;
use function is_array;
Sergei Morozov's avatar
Sergei Morozov committed
23
use function is_int;
24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
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_execute;
use function sasql_stmt_field_count;
use function sasql_stmt_reset;
use function sasql_stmt_result_metadata;
use function sprintf;
39 40 41 42 43 44

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

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

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

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

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

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

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

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

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

86
        if (! is_resource($this->stmt)) {
87 88 89 90 91 92 93 94 95
            throw SQLAnywhereException::fromSQLAnywhereError($conn);
        }
    }

    /**
     * {@inheritdoc}
     *
     * @throws SQLAnywhereException
     */
96
    public function bindParam($param, &$variable, int $type = ParameterType::STRING, ?int $length = null) : void
97
    {
98 99
        assert(is_int($param));

100
        switch ($type) {
101 102
            case ParameterType::INTEGER:
            case ParameterType::BOOLEAN:
103 104
                $type = 'i';
                break;
105 106

            case ParameterType::LARGE_OBJECT:
107 108
                $type = 'b';
                break;
109 110 111

            case ParameterType::NULL:
            case ParameterType::STRING:
112
            case ParameterType::BINARY:
113 114
                $type = 's';
                break;
115

116
            default:
117
                throw new SQLAnywhereException(sprintf('Unknown type %d.', $type));
118 119
        }

120
        $this->boundValues[$param] =& $variable;
121

122
        if (! sasql_stmt_bind_param_ex($this->stmt, $param - 1, $variable, $type, $variable === null)) {
123 124 125 126 127 128 129
            throw SQLAnywhereException::fromSQLAnywhereError($this->conn, $this->stmt);
        }
    }

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

    /**
     * {@inheritdoc}
     */
138
    public function closeCursor() : void
139
    {
140
        sasql_stmt_reset($this->stmt);
141 142 143 144 145
    }

    /**
     * {@inheritdoc}
     */
146
    public function columnCount() : int
147 148 149 150 151 152 153 154 155
    {
        return sasql_stmt_field_count($this->stmt);
    }

    /**
     * {@inheritdoc}
     *
     * @throws SQLAnywhereException
     */
156
    public function execute(?array $params = null) : void
157
    {
Steve Müller's avatar
Steve Müller committed
158
        if (is_array($params)) {
159
            $hasZeroIndex = array_key_exists(0, $params);
Steve Müller's avatar
Steve Müller committed
160

161
            foreach ($params as $key => $val) {
Sergei Morozov's avatar
Sergei Morozov committed
162 163 164 165 166
                if ($hasZeroIndex && is_int($key)) {
                    $this->bindValue($key + 1, $val);
                } else {
                    $this->bindValue($key, $val);
                }
167 168 169
            }
        }

170
        if (! sasql_stmt_execute($this->stmt)) {
171 172 173 174 175 176 177 178 179 180 181
            throw SQLAnywhereException::fromSQLAnywhereError($this->conn, $this->stmt);
        }

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

    /**
     * {@inheritdoc}
     *
     * @throws SQLAnywhereException
     */
182
    public function fetch(?int $fetchMode = null, ...$args)
183
    {
184
        if (! is_resource($this->result)) {
185 186 187 188 189 190
            return false;
        }

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

        switch ($fetchMode) {
191
            case FetchMode::COLUMN:
192 193
                return $this->fetchColumn();

194
            case FetchMode::ASSOCIATIVE:
195
                return sasql_fetch_assoc($this->result);
196 197

            case FetchMode::MIXED:
198
                return sasql_fetch_array($this->result, SASQL_BOTH);
199 200

            case FetchMode::CUSTOM_OBJECT:
201
                $className = $this->defaultFetchClass;
Steve Müller's avatar
Steve Müller committed
202
                $ctorArgs  = $this->defaultFetchClassCtorArgs;
203

204 205 206
                if (count($args) > 0) {
                    $className = $args[0];
                    $ctorArgs  = $args[1] ?? [];
207 208 209 210
                }

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

211
                if ($result instanceof stdClass) {
212 213 214 215
                    $result = $this->castObject($result, $className, $ctorArgs);
                }

                return $result;
216 217

            case FetchMode::NUMERIC:
218
                return sasql_fetch_row($this->result);
219 220

            case FetchMode::STANDARD_OBJECT:
221
                return sasql_fetch_object($this->result);
222

223
            default:
224
                throw new SQLAnywhereException(sprintf('Fetch mode is not supported %d.', $fetchMode));
225 226 227 228 229 230
        }
    }

    /**
     * {@inheritdoc}
     */
231
    public function fetchAll(?int $fetchMode = null, ...$args) : array
232
    {
233
        $rows = [];
234 235

        switch ($fetchMode) {
236
            case FetchMode::CUSTOM_OBJECT:
237
                while (($row = $this->fetch(...func_get_args())) !== false) {
238 239 240
                    $rows[] = $row;
                }
                break;
241 242

            case FetchMode::COLUMN:
243
                while (($row = $this->fetchColumn()) !== false) {
244 245 246
                    $rows[] = $row;
                }
                break;
247

248
            default:
249
                while (($row = $this->fetch($fetchMode)) !== false) {
250 251 252 253 254 255 256 257 258 259
                    $rows[] = $row;
                }
        }

        return $rows;
    }

    /**
     * {@inheritdoc}
     */
260
    public function fetchColumn(int $columnIndex = 0)
261
    {
262
        $row = $this->fetch(FetchMode::NUMERIC);
263

264
        if ($row === false) {
265
            return false;
266 267
        }

268
        if (! array_key_exists($columnIndex, $row)) {
269
            throw InvalidColumnIndex::new($columnIndex, count($row));
270
        }
271 272

        return $row[$columnIndex];
273 274 275 276 277 278 279
    }

    /**
     * {@inheritdoc}
     */
    public function getIterator()
    {
280
        return new StatementIterator($this);
281 282 283 284 285
    }

    /**
     * {@inheritdoc}
     */
286
    public function rowCount() : int
287 288 289 290 291 292 293
    {
        return sasql_stmt_affected_rows($this->stmt);
    }

    /**
     * {@inheritdoc}
     */
294
    public function setFetchMode(int $fetchMode, ...$args) : void
295
    {
296 297 298 299 300 301
        $this->defaultFetchMode = $fetchMode;

        if (isset($args[0])) {
            $this->defaultFetchClass = $args[0];
        }

302 303
        if (! isset($args[1])) {
            return;
304 305
        }

306
        $this->defaultFetchClassCtorArgs = (array) $args[1];
307 308 309 310 311
    }

    /**
     * Casts a stdClass object to the given class name mapping its' properties.
     *
312
     * @param stdClass      $sourceObject     Object to cast from.
313
     * @param string|object $destinationClass Name of the class or class instance to cast to.
314
     * @param mixed[]       $ctorArgs         Arguments to use for constructing the destination class instance.
315 316 317
     *
     * @throws SQLAnywhereException
     */
318
    private function castObject(stdClass $sourceObject, $destinationClass, array $ctorArgs = []) : object
319
    {
320 321
        if (! is_string($destinationClass)) {
            if (! is_object($destinationClass)) {
322
                throw new SQLAnywhereException(sprintf(
323 324
                    'Destination class has to be of type string or object, "%s" given.',
                    (new GetVariableType())->__invoke($destinationClass)
325 326 327
                ));
            }
        } else {
328
            $destinationClass = new ReflectionClass($destinationClass);
329 330 331
            $destinationClass = $destinationClass->newInstanceArgs($ctorArgs);
        }

332 333
        $sourceReflection           = new ReflectionObject($sourceObject);
        $destinationClassReflection = new ReflectionObject($destinationClass);
334 335 336

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

            $name  = $sourceProperty->getName();
339 340 341 342
            $value = $sourceProperty->getValue($sourceObject);

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

344 345 346 347 348 349 350 351 352 353
                $destinationProperty->setAccessible(true);
                $destinationProperty->setValue($destinationClass, $value);
            } else {
                $destinationClass->$name = $value;
            }
        }

        return $destinationClass;
    }
}