1<?php 2 3namespace Doctrine\DBAL\Driver\SQLAnywhere; 4 5use Doctrine\DBAL\Driver\Statement; 6use Doctrine\DBAL\Driver\StatementIterator; 7use Doctrine\DBAL\FetchMode; 8use Doctrine\DBAL\ParameterType; 9use IteratorAggregate; 10use PDO; 11use ReflectionClass; 12use ReflectionObject; 13use stdClass; 14use const SASQL_BOTH; 15use function array_key_exists; 16use function func_get_args; 17use function func_num_args; 18use function gettype; 19use function is_array; 20use function is_int; 21use function is_object; 22use function is_resource; 23use function is_string; 24use function sasql_fetch_array; 25use function sasql_fetch_assoc; 26use function sasql_fetch_object; 27use function sasql_fetch_row; 28use function sasql_prepare; 29use function sasql_stmt_affected_rows; 30use function sasql_stmt_bind_param_ex; 31use function sasql_stmt_errno; 32use function sasql_stmt_error; 33use function sasql_stmt_execute; 34use function sasql_stmt_field_count; 35use function sasql_stmt_reset; 36use function sasql_stmt_result_metadata; 37use function sprintf; 38 39/** 40 * SAP SQL Anywhere implementation of the Statement interface. 41 */ 42class SQLAnywhereStatement implements IteratorAggregate, Statement 43{ 44 /** @var resource The connection resource. */ 45 private $conn; 46 47 /** @var string Name of the default class to instantiate when fetching class instances. */ 48 private $defaultFetchClass = '\stdClass'; 49 50 /** @var mixed[] Constructor arguments for the default class to instantiate when fetching class instances. */ 51 private $defaultFetchClassCtorArgs = []; 52 53 /** @var int Default fetch mode to use. */ 54 private $defaultFetchMode = FetchMode::MIXED; 55 56 /** @var resource The result set resource to fetch. */ 57 private $result; 58 59 /** @var resource The prepared SQL statement to execute. */ 60 private $stmt; 61 62 /** @var mixed[] The references to bound parameter values. */ 63 private $boundValues = []; 64 65 /** 66 * Prepares given statement for given connection. 67 * 68 * @param resource $conn The connection resource to use. 69 * @param string $sql The SQL statement to prepare. 70 * 71 * @throws SQLAnywhereException 72 */ 73 public function __construct($conn, $sql) 74 { 75 if (! is_resource($conn)) { 76 throw new SQLAnywhereException('Invalid SQL Anywhere connection resource: ' . $conn); 77 } 78 79 $this->conn = $conn; 80 $this->stmt = sasql_prepare($conn, $sql); 81 82 if (! is_resource($this->stmt)) { 83 throw SQLAnywhereException::fromSQLAnywhereError($conn); 84 } 85 } 86 87 /** 88 * {@inheritdoc} 89 * 90 * @throws SQLAnywhereException 91 */ 92 public function bindParam($column, &$variable, $type = ParameterType::STRING, $length = null) 93 { 94 switch ($type) { 95 case ParameterType::INTEGER: 96 case ParameterType::BOOLEAN: 97 $type = 'i'; 98 break; 99 100 case ParameterType::LARGE_OBJECT: 101 $type = 'b'; 102 break; 103 104 case ParameterType::NULL: 105 case ParameterType::STRING: 106 case ParameterType::BINARY: 107 $type = 's'; 108 break; 109 110 default: 111 throw new SQLAnywhereException('Unknown type: ' . $type); 112 } 113 114 $this->boundValues[$column] =& $variable; 115 116 if (! sasql_stmt_bind_param_ex($this->stmt, $column - 1, $variable, $type, $variable === null)) { 117 throw SQLAnywhereException::fromSQLAnywhereError($this->conn, $this->stmt); 118 } 119 120 return true; 121 } 122 123 /** 124 * {@inheritdoc} 125 */ 126 public function bindValue($param, $value, $type = ParameterType::STRING) 127 { 128 return $this->bindParam($param, $value, $type); 129 } 130 131 /** 132 * {@inheritdoc} 133 * 134 * @throws SQLAnywhereException 135 */ 136 public function closeCursor() 137 { 138 if (! sasql_stmt_reset($this->stmt)) { 139 throw SQLAnywhereException::fromSQLAnywhereError($this->conn, $this->stmt); 140 } 141 142 return true; 143 } 144 145 /** 146 * {@inheritdoc} 147 */ 148 public function columnCount() 149 { 150 return sasql_stmt_field_count($this->stmt); 151 } 152 153 /** 154 * {@inheritdoc} 155 */ 156 public function errorCode() 157 { 158 return sasql_stmt_errno($this->stmt); 159 } 160 161 /** 162 * {@inheritdoc} 163 */ 164 public function errorInfo() 165 { 166 return sasql_stmt_error($this->stmt); 167 } 168 169 /** 170 * {@inheritdoc} 171 * 172 * @throws SQLAnywhereException 173 */ 174 public function execute($params = null) 175 { 176 if (is_array($params)) { 177 $hasZeroIndex = array_key_exists(0, $params); 178 179 foreach ($params as $key => $val) { 180 if ($hasZeroIndex && is_int($key)) { 181 $this->bindValue($key + 1, $val); 182 } else { 183 $this->bindValue($key, $val); 184 } 185 } 186 } 187 188 if (! sasql_stmt_execute($this->stmt)) { 189 throw SQLAnywhereException::fromSQLAnywhereError($this->conn, $this->stmt); 190 } 191 192 $this->result = sasql_stmt_result_metadata($this->stmt); 193 194 return true; 195 } 196 197 /** 198 * {@inheritdoc} 199 * 200 * @throws SQLAnywhereException 201 */ 202 public function fetch($fetchMode = null, $cursorOrientation = PDO::FETCH_ORI_NEXT, $cursorOffset = 0) 203 { 204 if (! is_resource($this->result)) { 205 return false; 206 } 207 208 $fetchMode = $fetchMode ?: $this->defaultFetchMode; 209 210 switch ($fetchMode) { 211 case FetchMode::COLUMN: 212 return $this->fetchColumn(); 213 214 case FetchMode::ASSOCIATIVE: 215 return sasql_fetch_assoc($this->result); 216 217 case FetchMode::MIXED: 218 return sasql_fetch_array($this->result, SASQL_BOTH); 219 220 case FetchMode::CUSTOM_OBJECT: 221 $className = $this->defaultFetchClass; 222 $ctorArgs = $this->defaultFetchClassCtorArgs; 223 224 if (func_num_args() >= 2) { 225 $args = func_get_args(); 226 $className = $args[1]; 227 $ctorArgs = $args[2] ?? []; 228 } 229 230 $result = sasql_fetch_object($this->result); 231 232 if ($result instanceof stdClass) { 233 $result = $this->castObject($result, $className, $ctorArgs); 234 } 235 236 return $result; 237 238 case FetchMode::NUMERIC: 239 return sasql_fetch_row($this->result); 240 241 case FetchMode::STANDARD_OBJECT: 242 return sasql_fetch_object($this->result); 243 244 default: 245 throw new SQLAnywhereException('Fetch mode is not supported: ' . $fetchMode); 246 } 247 } 248 249 /** 250 * {@inheritdoc} 251 */ 252 public function fetchAll($fetchMode = null, $fetchArgument = null, $ctorArgs = null) 253 { 254 $rows = []; 255 256 switch ($fetchMode) { 257 case FetchMode::CUSTOM_OBJECT: 258 while (($row = $this->fetch(...func_get_args())) !== false) { 259 $rows[] = $row; 260 } 261 break; 262 263 case FetchMode::COLUMN: 264 while (($row = $this->fetchColumn()) !== false) { 265 $rows[] = $row; 266 } 267 break; 268 269 default: 270 while (($row = $this->fetch($fetchMode)) !== false) { 271 $rows[] = $row; 272 } 273 } 274 275 return $rows; 276 } 277 278 /** 279 * {@inheritdoc} 280 */ 281 public function fetchColumn($columnIndex = 0) 282 { 283 $row = $this->fetch(FetchMode::NUMERIC); 284 285 if ($row === false) { 286 return false; 287 } 288 289 return $row[$columnIndex] ?? null; 290 } 291 292 /** 293 * {@inheritdoc} 294 */ 295 public function getIterator() 296 { 297 return new StatementIterator($this); 298 } 299 300 /** 301 * {@inheritdoc} 302 */ 303 public function rowCount() 304 { 305 return sasql_stmt_affected_rows($this->stmt); 306 } 307 308 /** 309 * {@inheritdoc} 310 */ 311 public function setFetchMode($fetchMode, $arg2 = null, $arg3 = null) 312 { 313 $this->defaultFetchMode = $fetchMode; 314 $this->defaultFetchClass = $arg2 ?: $this->defaultFetchClass; 315 $this->defaultFetchClassCtorArgs = $arg3 ? (array) $arg3 : $this->defaultFetchClassCtorArgs; 316 } 317 318 /** 319 * Casts a stdClass object to the given class name mapping its' properties. 320 * 321 * @param stdClass $sourceObject Object to cast from. 322 * @param string|object $destinationClass Name of the class or class instance to cast to. 323 * @param mixed[] $ctorArgs Arguments to use for constructing the destination class instance. 324 * 325 * @return object 326 * 327 * @throws SQLAnywhereException 328 */ 329 private function castObject(stdClass $sourceObject, $destinationClass, array $ctorArgs = []) 330 { 331 if (! is_string($destinationClass)) { 332 if (! is_object($destinationClass)) { 333 throw new SQLAnywhereException(sprintf( 334 'Destination class has to be of type string or object, %s given.', 335 gettype($destinationClass) 336 )); 337 } 338 } else { 339 $destinationClass = new ReflectionClass($destinationClass); 340 $destinationClass = $destinationClass->newInstanceArgs($ctorArgs); 341 } 342 343 $sourceReflection = new ReflectionObject($sourceObject); 344 $destinationClassReflection = new ReflectionObject($destinationClass); 345 346 foreach ($sourceReflection->getProperties() as $sourceProperty) { 347 $sourceProperty->setAccessible(true); 348 349 $name = $sourceProperty->getName(); 350 $value = $sourceProperty->getValue($sourceObject); 351 352 if ($destinationClassReflection->hasProperty($name)) { 353 $destinationProperty = $destinationClassReflection->getProperty($name); 354 355 $destinationProperty->setAccessible(true); 356 $destinationProperty->setValue($destinationClass, $value); 357 } else { 358 $destinationClass->$name = $value; 359 } 360 } 361 362 return $destinationClass; 363 } 364} 365