| Total Complexity | 52 | 
| Total Lines | 485 | 
| Duplicated Lines | 0 % | 
| Changes | 0 | ||
Complex classes like OCI8Statement often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use OCI8Statement, and based on these observations, apply Extract Interface, too.
| 1 | <?php  | 
            ||
| 48 | class OCI8Statement implements IteratorAggregate, Statement  | 
            ||
| 49 | { | 
            ||
| 50 | /** @var resource */  | 
            ||
| 51 | protected $_dbh;  | 
            ||
| 52 | |||
| 53 | /** @var resource */  | 
            ||
| 54 | protected $_sth;  | 
            ||
| 55 | |||
| 56 | /** @var OCI8Connection */  | 
            ||
| 57 | protected $_conn;  | 
            ||
| 58 | |||
| 59 | /**  | 
            ||
| 60 | * @deprecated  | 
            ||
| 61 | *  | 
            ||
| 62 | * @var string  | 
            ||
| 63 | */  | 
            ||
| 64 | protected static $_PARAM = ':param';  | 
            ||
| 65 | |||
| 66 | /** @var int[] */  | 
            ||
| 67 | protected static $fetchModeMap = [  | 
            ||
| 68 | FetchMode::MIXED => OCI_BOTH,  | 
            ||
| 69 | FetchMode::ASSOCIATIVE => OCI_ASSOC,  | 
            ||
| 70 | FetchMode::NUMERIC => OCI_NUM,  | 
            ||
| 71 | FetchMode::COLUMN => OCI_NUM,  | 
            ||
| 72 | ];  | 
            ||
| 73 | |||
| 74 | /** @var int */  | 
            ||
| 75 | protected $_defaultFetchMode = FetchMode::MIXED;  | 
            ||
| 76 | |||
| 77 | /** @var string[] */  | 
            ||
| 78 | protected $_paramMap = [];  | 
            ||
| 79 | |||
| 80 | /**  | 
            ||
| 81 | * Holds references to bound parameter values.  | 
            ||
| 82 | *  | 
            ||
| 83 | * This is a new requirement for PHP7's oci8 extension that prevents bound values from being garbage collected.  | 
            ||
| 84 | *  | 
            ||
| 85 | * @var mixed[]  | 
            ||
| 86 | */  | 
            ||
| 87 | private $boundValues = [];  | 
            ||
| 88 | |||
| 89 | /**  | 
            ||
| 90 | * Indicates whether the statement is in the state when fetching results is possible  | 
            ||
| 91 | *  | 
            ||
| 92 | * @var bool  | 
            ||
| 93 | */  | 
            ||
| 94 | private $result = false;  | 
            ||
| 95 | |||
| 96 | /**  | 
            ||
| 97 | * Creates a new OCI8Statement that uses the given connection handle and SQL statement.  | 
            ||
| 98 | *  | 
            ||
| 99 | * @param resource $dbh The connection handle.  | 
            ||
| 100 | * @param string $query The SQL query.  | 
            ||
| 101 | */  | 
            ||
| 102 | public function __construct($dbh, $query, OCI8Connection $conn)  | 
            ||
| 113 | }  | 
            ||
| 114 | |||
| 115 | /**  | 
            ||
| 116 | * Converts positional (?) into named placeholders (:param<num>).  | 
            ||
| 117 | *  | 
            ||
| 118 | * Oracle does not support positional parameters, hence this method converts all  | 
            ||
| 119 | * positional parameters into artificially named parameters. Note that this conversion  | 
            ||
| 120 | * is not perfect. All question marks (?) in the original statement are treated as  | 
            ||
| 121 | * placeholders and converted to a named parameter.  | 
            ||
| 122 | *  | 
            ||
| 123 | * The algorithm uses a state machine with two possible states: InLiteral and NotInLiteral.  | 
            ||
| 124 | * Question marks inside literal strings are therefore handled correctly by this method.  | 
            ||
| 125 | * This comes at a cost, the whole sql statement has to be looped over.  | 
            ||
| 126 | *  | 
            ||
| 127 | * @param string $statement The SQL statement to convert.  | 
            ||
| 128 | *  | 
            ||
| 129 | * @return mixed[] [0] => the statement value (string), [1] => the paramMap value (array).  | 
            ||
| 130 | *  | 
            ||
| 131 | * @throws OCI8Exception  | 
            ||
| 132 | *  | 
            ||
| 133 | * @todo extract into utility class in Doctrine\DBAL\Util namespace  | 
            ||
| 134 | * @todo review and test for lost spaces. we experienced missing spaces with oci8 in some sql statements.  | 
            ||
| 135 | */  | 
            ||
| 136 | public static function convertPositionalToNamedPlaceholders($statement)  | 
            ||
| 137 |     { | 
            ||
| 138 | $fragmentOffset = $tokenOffset = 0;  | 
            ||
| 139 | $fragments = $paramMap = [];  | 
            ||
| 140 | $currentLiteralDelimiter = null;  | 
            ||
| 141 | |||
| 142 |         do { | 
            ||
| 143 |             if (! $currentLiteralDelimiter) { | 
            ||
| 144 | $result = self::findPlaceholderOrOpeningQuote(  | 
            ||
| 145 | $statement,  | 
            ||
| 146 | $tokenOffset,  | 
            ||
| 147 | $fragmentOffset,  | 
            ||
| 148 | $fragments,  | 
            ||
| 149 | $currentLiteralDelimiter,  | 
            ||
| 150 | $paramMap  | 
            ||
| 151 | );  | 
            ||
| 152 |             } else { | 
            ||
| 153 | $result = self::findClosingQuote($statement, $tokenOffset, $currentLiteralDelimiter);  | 
            ||
| 
                                                                                                    
                        
                         | 
                |||
| 154 | }  | 
            ||
| 155 | } while ($result);  | 
            ||
| 156 | |||
| 157 |         if ($currentLiteralDelimiter) { | 
            ||
| 158 | throw new OCI8Exception(sprintf(  | 
            ||
| 159 | 'The statement contains non-terminated string literal starting at offset %d',  | 
            ||
| 160 | $tokenOffset - 1  | 
            ||
| 161 | ));  | 
            ||
| 162 | }  | 
            ||
| 163 | |||
| 164 | $fragments[] = substr($statement, $fragmentOffset);  | 
            ||
| 165 |         $statement   = implode('', $fragments); | 
            ||
| 166 | |||
| 167 | return [$statement, $paramMap];  | 
            ||
| 168 | }  | 
            ||
| 169 | |||
| 170 | /**  | 
            ||
| 171 | * Finds next placeholder or opening quote.  | 
            ||
| 172 | *  | 
            ||
| 173 | * @param string $statement The SQL statement to parse  | 
            ||
| 174 | * @param string $tokenOffset The offset to start searching from  | 
            ||
| 175 | * @param int $fragmentOffset The offset to build the next fragment from  | 
            ||
| 176 | * @param string[] $fragments Fragments of the original statement not containing placeholders  | 
            ||
| 177 | * @param string|null $currentLiteralDelimiter The delimiter of the current string literal  | 
            ||
| 178 | * or NULL if not currently in a literal  | 
            ||
| 179 | * @param array<int, string> $paramMap Mapping of the original parameter positions to their named replacements  | 
            ||
| 180 | *  | 
            ||
| 181 | * @return bool Whether the token was found  | 
            ||
| 182 | */  | 
            ||
| 183 | private static function findPlaceholderOrOpeningQuote(  | 
            ||
| 184 | $statement,  | 
            ||
| 185 | &$tokenOffset,  | 
            ||
| 186 | &$fragmentOffset,  | 
            ||
| 187 | &$fragments,  | 
            ||
| 188 | &$currentLiteralDelimiter,  | 
            ||
| 189 | &$paramMap  | 
            ||
| 190 |     ) { | 
            ||
| 191 | $token = self::findToken($statement, $tokenOffset, '/[?\'"]/');  | 
            ||
| 192 | |||
| 193 |         if ($token === null) { | 
            ||
| 194 | return false;  | 
            ||
| 195 | }  | 
            ||
| 196 | |||
| 197 |         if ($token === '?') { | 
            ||
| 198 | $position = count($paramMap) + 1;  | 
            ||
| 199 | $param = ':param' . $position;  | 
            ||
| 200 | $fragments[] = substr($statement, $fragmentOffset, $tokenOffset - $fragmentOffset);  | 
            ||
| 201 | $fragments[] = $param;  | 
            ||
| 202 | $paramMap[$position] = $param;  | 
            ||
| 203 | $tokenOffset += 1;  | 
            ||
| 204 | $fragmentOffset = $tokenOffset;  | 
            ||
| 205 | |||
| 206 | return true;  | 
            ||
| 207 | }  | 
            ||
| 208 | |||
| 209 | $currentLiteralDelimiter = $token;  | 
            ||
| 210 | ++$tokenOffset;  | 
            ||
| 211 | |||
| 212 | return true;  | 
            ||
| 213 | }  | 
            ||
| 214 | |||
| 215 | /**  | 
            ||
| 216 | * Finds closing quote  | 
            ||
| 217 | *  | 
            ||
| 218 | * @param string $statement The SQL statement to parse  | 
            ||
| 219 | * @param string $tokenOffset The offset to start searching from  | 
            ||
| 220 | * @param string $currentLiteralDelimiter The delimiter of the current string literal  | 
            ||
| 221 | *  | 
            ||
| 222 | * @return bool Whether the token was found  | 
            ||
| 223 | */  | 
            ||
| 224 | private static function findClosingQuote(  | 
            ||
| 225 | $statement,  | 
            ||
| 226 | &$tokenOffset,  | 
            ||
| 227 | &$currentLiteralDelimiter  | 
            ||
| 228 |     ) { | 
            ||
| 229 | $token = self::findToken(  | 
            ||
| 230 | $statement,  | 
            ||
| 231 | $tokenOffset,  | 
            ||
| 232 | '/' . preg_quote($currentLiteralDelimiter, '/') . '/'  | 
            ||
| 233 | );  | 
            ||
| 234 | |||
| 235 |         if ($token === null) { | 
            ||
| 236 | return false;  | 
            ||
| 237 | }  | 
            ||
| 238 | |||
| 239 | $currentLiteralDelimiter = false;  | 
            ||
| 240 | ++$tokenOffset;  | 
            ||
| 241 | |||
| 242 | return true;  | 
            ||
| 243 | }  | 
            ||
| 244 | |||
| 245 | /**  | 
            ||
| 246 | * Finds the token described by regex starting from the given offset. Updates the offset with the position  | 
            ||
| 247 | * where the token was found.  | 
            ||
| 248 | *  | 
            ||
| 249 | * @param string $statement The SQL statement to parse  | 
            ||
| 250 | * @param int $offset The offset to start searching from  | 
            ||
| 251 | * @param string $regex The regex containing token pattern  | 
            ||
| 252 | *  | 
            ||
| 253 | * @return string|null Token or NULL if not found  | 
            ||
| 254 | */  | 
            ||
| 255 | private static function findToken($statement, &$offset, $regex)  | 
            ||
| 256 |     { | 
            ||
| 257 |         if (preg_match($regex, $statement, $matches, PREG_OFFSET_CAPTURE, $offset) === 1) { | 
            ||
| 258 | $offset = $matches[0][1];  | 
            ||
| 259 | |||
| 260 | return $matches[0][0];  | 
            ||
| 261 | }  | 
            ||
| 262 | |||
| 263 | return null;  | 
            ||
| 264 | }  | 
            ||
| 265 | |||
| 266 | /**  | 
            ||
| 267 |      * {@inheritdoc} | 
            ||
| 268 | */  | 
            ||
| 269 | public function bindValue($param, $value, $type = ParameterType::STRING)  | 
            ||
| 270 |     { | 
            ||
| 271 | return $this->bindParam($param, $value, $type, null);  | 
            ||
| 272 | }  | 
            ||
| 273 | |||
| 274 | /**  | 
            ||
| 275 |      * {@inheritdoc} | 
            ||
| 276 | */  | 
            ||
| 277 | public function bindParam($param, &$variable, $type = ParameterType::STRING, $length = null)  | 
            ||
| 278 |     { | 
            ||
| 279 |         if (is_int($param)) { | 
            ||
| 280 |             if (! isset($this->_paramMap[$param])) { | 
            ||
| 281 |                 throw new OCI8Exception(sprintf('Could not find variable mapping with index %d, in the SQL statement', $param)); | 
            ||
| 282 | }  | 
            ||
| 283 | |||
| 284 | $param = $this->_paramMap[$param];  | 
            ||
| 285 | }  | 
            ||
| 286 | |||
| 287 |         if ($type === ParameterType::LARGE_OBJECT) { | 
            ||
| 288 | $lob = oci_new_descriptor($this->_dbh, OCI_D_LOB);  | 
            ||
| 289 | |||
| 290 | $class = 'OCI-Lob';  | 
            ||
| 291 | assert($lob instanceof $class);  | 
            ||
| 292 | |||
| 293 | $lob->writetemporary($variable, OCI_TEMP_BLOB);  | 
            ||
| 294 | |||
| 295 | $variable =& $lob;  | 
            ||
| 296 | }  | 
            ||
| 297 | |||
| 298 | $this->boundValues[$param] =& $variable;  | 
            ||
| 299 | |||
| 300 | return oci_bind_by_name(  | 
            ||
| 301 | $this->_sth,  | 
            ||
| 302 | $param,  | 
            ||
| 303 | $variable,  | 
            ||
| 304 | $length ?? -1,  | 
            ||
| 305 | $this->convertParameterType($type)  | 
            ||
| 306 | );  | 
            ||
| 307 | }  | 
            ||
| 308 | |||
| 309 | /**  | 
            ||
| 310 | * Converts DBAL parameter type to oci8 parameter type  | 
            ||
| 311 | */  | 
            ||
| 312 | private function convertParameterType(int $type) : int  | 
            ||
| 323 | }  | 
            ||
| 324 | }  | 
            ||
| 325 | |||
| 326 | /**  | 
            ||
| 327 |      * {@inheritdoc} | 
            ||
| 328 | */  | 
            ||
| 329 | public function closeCursor()  | 
            ||
| 330 |     { | 
            ||
| 331 | // not having the result means there's nothing to close  | 
            ||
| 332 |         if (! $this->result) { | 
            ||
| 333 | return true;  | 
            ||
| 334 | }  | 
            ||
| 335 | |||
| 336 | oci_cancel($this->_sth);  | 
            ||
| 337 | |||
| 338 | $this->result = false;  | 
            ||
| 339 | |||
| 340 | return true;  | 
            ||
| 341 | }  | 
            ||
| 342 | |||
| 343 | /**  | 
            ||
| 344 |      * {@inheritdoc} | 
            ||
| 345 | */  | 
            ||
| 346 | public function columnCount()  | 
            ||
| 347 |     { | 
            ||
| 348 | $count = oci_num_fields($this->_sth);  | 
            ||
| 349 | |||
| 350 |         if ($count !== false) { | 
            ||
| 351 | return $count;  | 
            ||
| 352 | }  | 
            ||
| 353 | |||
| 354 | return 0;  | 
            ||
| 355 | }  | 
            ||
| 356 | |||
| 357 | /**  | 
            ||
| 358 |      * {@inheritdoc} | 
            ||
| 359 | */  | 
            ||
| 360 | public function errorCode()  | 
            ||
| 361 |     { | 
            ||
| 362 | $error = oci_error($this->_sth);  | 
            ||
| 363 |         if ($error !== false) { | 
            ||
| 364 | $error = $error['code'];  | 
            ||
| 365 | }  | 
            ||
| 366 | |||
| 367 | return $error;  | 
            ||
| 368 | }  | 
            ||
| 369 | |||
| 370 | /**  | 
            ||
| 371 |      * {@inheritdoc} | 
            ||
| 372 | */  | 
            ||
| 373 | public function errorInfo()  | 
            ||
| 374 |     { | 
            ||
| 375 | $error = oci_error($this->_sth);  | 
            ||
| 376 | |||
| 377 |         if ($error === false) { | 
            ||
| 378 | return [];  | 
            ||
| 379 | }  | 
            ||
| 380 | |||
| 381 | return $error;  | 
            ||
| 382 | }  | 
            ||
| 383 | |||
| 384 | /**  | 
            ||
| 385 |      * {@inheritdoc} | 
            ||
| 386 | */  | 
            ||
| 387 | public function execute($params = null)  | 
            ||
| 388 |     { | 
            ||
| 389 |         if ($params !== null) { | 
            ||
| 390 | $hasZeroIndex = array_key_exists(0, $params);  | 
            ||
| 391 |             foreach ($params as $key => $val) { | 
            ||
| 392 |                 if ($hasZeroIndex && is_int($key)) { | 
            ||
| 393 | $this->bindValue($key + 1, $val);  | 
            ||
| 394 |                 } else { | 
            ||
| 395 | $this->bindValue($key, $val);  | 
            ||
| 396 | }  | 
            ||
| 397 | }  | 
            ||
| 398 | }  | 
            ||
| 399 | |||
| 400 | $ret = @oci_execute($this->_sth, $this->_conn->getExecuteMode());  | 
            ||
| 401 |         if (! $ret) { | 
            ||
| 402 | throw OCI8Exception::fromErrorInfo($this->errorInfo());  | 
            ||
| 403 | }  | 
            ||
| 404 | |||
| 405 | $this->result = true;  | 
            ||
| 406 | |||
| 407 | return $ret;  | 
            ||
| 408 | }  | 
            ||
| 409 | |||
| 410 | /**  | 
            ||
| 411 |      * {@inheritdoc} | 
            ||
| 412 | */  | 
            ||
| 413 | public function setFetchMode($fetchMode)  | 
            ||
| 414 |     { | 
            ||
| 415 | $this->_defaultFetchMode = $fetchMode;  | 
            ||
| 416 | |||
| 417 | return true;  | 
            ||
| 418 | }  | 
            ||
| 419 | |||
| 420 | /**  | 
            ||
| 421 |      * {@inheritdoc} | 
            ||
| 422 | */  | 
            ||
| 423 | public function getIterator()  | 
            ||
| 424 |     { | 
            ||
| 425 | return new StatementIterator($this);  | 
            ||
| 426 | }  | 
            ||
| 427 | |||
| 428 | /**  | 
            ||
| 429 |      * {@inheritdoc} | 
            ||
| 430 | */  | 
            ||
| 431 | public function fetch($fetchMode = null)  | 
            ||
| 432 |     { | 
            ||
| 433 | // do not try fetching from the statement if it's not expected to contain result  | 
            ||
| 434 | // in order to prevent exceptional situation  | 
            ||
| 435 |         if (! $this->result) { | 
            ||
| 436 | return false;  | 
            ||
| 437 | }  | 
            ||
| 438 | |||
| 439 | $fetchMode = $fetchMode ?? $this->_defaultFetchMode;  | 
            ||
| 440 | |||
| 441 |         if ($fetchMode === FetchMode::COLUMN) { | 
            ||
| 442 | return $this->fetchColumn();  | 
            ||
| 443 | }  | 
            ||
| 444 | |||
| 445 |         if (! isset(self::$fetchModeMap[$fetchMode])) { | 
            ||
| 446 |             throw new InvalidArgumentException('Invalid fetch style: ' . $fetchMode); | 
            ||
| 447 | }  | 
            ||
| 448 | |||
| 449 | return oci_fetch_array(  | 
            ||
| 450 | $this->_sth,  | 
            ||
| 451 | self::$fetchModeMap[$fetchMode] | OCI_RETURN_NULLS | OCI_RETURN_LOBS  | 
            ||
| 452 | );  | 
            ||
| 453 | }  | 
            ||
| 454 | |||
| 455 | /**  | 
            ||
| 456 |      * {@inheritdoc} | 
            ||
| 457 | */  | 
            ||
| 458 | public function fetchAll($fetchMode = null)  | 
            ||
| 459 |     { | 
            ||
| 460 | $fetchMode = $fetchMode ?? $this->_defaultFetchMode;  | 
            ||
| 461 | |||
| 462 | $result = [];  | 
            ||
| 463 | |||
| 464 |         if (! isset(self::$fetchModeMap[$fetchMode])) { | 
            ||
| 465 |             throw new InvalidArgumentException('Invalid fetch style: ' . $fetchMode); | 
            ||
| 466 | }  | 
            ||
| 467 | |||
| 468 |         if (self::$fetchModeMap[$fetchMode] === OCI_BOTH) { | 
            ||
| 469 |             while ($row = $this->fetch($fetchMode)) { | 
            ||
| 470 | $result[] = $row;  | 
            ||
| 471 | }  | 
            ||
| 472 |         } else { | 
            ||
| 473 | $fetchStructure = OCI_FETCHSTATEMENT_BY_ROW;  | 
            ||
| 474 | |||
| 475 |             if ($fetchMode === FetchMode::COLUMN) { | 
            ||
| 476 | $fetchStructure = OCI_FETCHSTATEMENT_BY_COLUMN;  | 
            ||
| 477 | }  | 
            ||
| 478 | |||
| 479 | // do not try fetching from the statement if it's not expected to contain result  | 
            ||
| 480 | // in order to prevent exceptional situation  | 
            ||
| 481 |             if (! $this->result) { | 
            ||
| 482 | return [];  | 
            ||
| 483 | }  | 
            ||
| 484 | |||
| 485 | oci_fetch_all(  | 
            ||
| 486 | $this->_sth,  | 
            ||
| 487 | $result,  | 
            ||
| 488 | 0,  | 
            ||
| 489 | -1,  | 
            ||
| 490 | self::$fetchModeMap[$fetchMode] | OCI_RETURN_NULLS | $fetchStructure | OCI_RETURN_LOBS  | 
            ||
| 491 | );  | 
            ||
| 492 | |||
| 493 |             if ($fetchMode === FetchMode::COLUMN) { | 
            ||
| 494 | $result = $result[0];  | 
            ||
| 495 | }  | 
            ||
| 496 | }  | 
            ||
| 497 | |||
| 498 | return $result;  | 
            ||
| 499 | }  | 
            ||
| 500 | |||
| 501 | /**  | 
            ||
| 502 |      * {@inheritdoc} | 
            ||
| 503 | */  | 
            ||
| 504 | public function fetchColumn()  | 
            ||
| 505 |     { | 
            ||
| 506 | // do not try fetching from the statement if it's not expected to contain result  | 
            ||
| 507 | // in order to prevent exceptional situation  | 
            ||
| 508 |         if (! $this->result) { | 
            ||
| 509 | return false;  | 
            ||
| 510 | }  | 
            ||
| 511 | |||
| 512 | $row = oci_fetch_array($this->_sth, OCI_NUM | OCI_RETURN_NULLS | OCI_RETURN_LOBS);  | 
            ||
| 513 | |||
| 514 |         if ($row === false) { | 
            ||
| 515 | return false;  | 
            ||
| 516 | }  | 
            ||
| 517 | |||
| 518 | return $row[0] ?? null;  | 
            ||
| 519 | }  | 
            ||
| 520 | |||
| 521 | /**  | 
            ||
| 522 |      * {@inheritdoc} | 
            ||
| 523 | */  | 
            ||
| 524 | public function rowCount() : int  | 
            ||
| 533 | }  | 
            ||
| 534 | }  | 
            ||
| 535 |