Completed
Push — dev-0.1.x ( 28fa1c...bdc014 )
by Josué
21:52 queued 19:49
created

PdoOci8Statement::getColumnMeta()   B

Complexity

Conditions 4
Paths 4

Size

Total Lines 35
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 21
CRAP Score 4.0312

Importance

Changes 4
Bugs 0 Features 3
Metric Value
c 4
b 0
f 3
dl 0
loc 35
ccs 21
cts 24
cp 0.875
rs 8.5806
cc 4
eloc 23
nc 4
nop 1
crap 4.0312
1
<?php
2
3
namespace Jpina\PdoOci8;
4
5
use Jpina\Oci8\Oci8ConnectionInterface;
6
use Jpina\Oci8\Oci8FieldInterface;
7
use Jpina\Oci8\Oci8StatementInterface;
8
9
/**
10
 * Custom PDO_OCI implementation via OCI8 driver
11
 *
12
 * @see http://php.net/manual/en/class.pdostatement.php
13
 */
14
class PdoOci8Statement extends \PDOStatement
15
{
16
    /** @var  Oci8ConnectionInterface */
17
    private $connection;
18
19
    /** @var  Oci8StatementInterface */
20
    private $statement;
21
22
    /** @var string */
23
    private $sqlText = '';
24
25
    /** @var array */
26
    private $boundParameters = array();
27
28
    /** @var array */
29
    private $options;
30
31
    /** @var \ArrayIterator */
32
    private $iterator;
33
34
    /** @var string|object */
35
    private $fetchTarget = 'stdClass';
36
37
    /** @var array */
38
    private $fetchTargetConstructorArgs = array();
39
40 47
    public function __construct(Oci8ConnectionInterface $connection, $sqlText, $options = array())
41
    {
42 47
        if (!is_string($sqlText)) {
43
            throw new PdoOci8Exception('$sqlText is not a string');
44
        }
45
46 47
        $this->connection  = $connection;
47 47
        $this->sqlText     = $sqlText;
48
        //TODO assign value to queryString
49
        //$this->queryString = $sqlText;
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
50
51 47
        $this->options = array(
52 47
            \PDO::ATTR_AUTOCOMMIT          => true,
53 47
            \PDO::ATTR_CASE                => \PDO::CASE_NATURAL,
54 47
            \PDO::ATTR_ERRMODE             => \PDO::ERRMODE_SILENT,
55 47
            \PDO::ATTR_ORACLE_NULLS        => \PDO::NULL_NATURAL,
56 47
            \PDO::ATTR_PREFETCH            => 100,
57 47
            \PDO::ATTR_TIMEOUT             => 600,
58 47
            \PDO::ATTR_STRINGIFY_FETCHES   => false,
59 47
            \PDO::ATTR_STATEMENT_CLASS     => null,
60 47
            \PDO::ATTR_EMULATE_PREPARES    => false,
61 47
            \PDO::ATTR_DEFAULT_FETCH_MODE  => \PDO::FETCH_BOTH,
62 47
            \PDO::ATTR_FETCH_TABLE_NAMES   => false,
63 47
            \PDO::ATTR_FETCH_CATALOG_NAMES => false,
64 47
            \PDO::ATTR_MAX_COLUMN_LEN      => 0,
65 47
            PdoOci8::OCI_ATTR_RETURN_LOBS  => false,
66
        );
67
68 47 View Code Duplication
        foreach ($options as $option => $value) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
69 47
            if (array_key_exists($option, $this->options)) {
70 47
                $this->options[$option] = $value;
71 47
            }
72 47
        }
73
74 47
        foreach ($options as $attribute => $value) {
75 47
            $this->setAttribute($attribute, $value);
76 47
        }
77
78
        try {
79 47
            $this->statement = $connection->parse($sqlText);
80 47
        } catch (\Exception $ex) {
81
            throw new PdoOci8Exception($ex->getMessage(), $ex->getCode(), $ex);
82
        }
83 47
    }
84
85
    /**
86
     * @param int|string $column
87
     * @param mixed $param
88
     * @param int $type
89
     * @param int $maxlen
90
     * @param mixed $driverdata
91
     *
92
     * @link http://php.net/manual/en/pdostatement.bindcolumn.php
93
     * @return bool
94
     */
95 2
    public function bindColumn($column, &$param, $type = null, $maxlen = null, $driverdata = null)
96
    {
97
        try {
98 2
            $type = $type === null ? \PDO::PARAM_STR : $type;
99 2
            $dataType = $this->getDriverDataType($type);
100 2
            return $this->statement->defineByName($column, $param, $dataType);
101
        } catch (\Exception $ex) {
102
            //throw new PdoOci8Exception($ex->getMessage(), $ex->getCode(), $ex);
0 ignored issues
show
Unused Code Comprehensibility introduced by
70% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
103
        }
104
105
        return false;
106
    }
107
108
    /**
109
     * @param string $parameter
110
     * @param mixed $variable
111
     * @param int $data_type
112
     * @param int $length
113
     * @param mixed $driver_options
114
     *
115
     * @link http://php.net/manual/en/pdostatement.bindparam.php
116
     * @return bool
117
     */
118 9
    public function bindParam(
119
        $parameter,
120
        &$variable,
121
        $data_type = \PDO::PARAM_STR,
122
        $length = null,
123
        $driver_options = null
124
    ) {
125 9
        $isBound = false;
126
        try {
127 9
            $data_type = $data_type === null ? \PDO::PARAM_STR : $data_type;
128 9
            $dataType = $this->getDriverDataType($data_type);
129 9
            $length = $length !== null ? $length : -1;
130 9
            $isBound = $this->statement->bindByName($parameter, $variable, $length, $dataType);
131 6
            if ($isBound) {
132 5
                $this->boundParameters[$parameter] = array(
133 5
                    'name'     => is_int($parameter) ? '' : $parameter,
134 5
                    'position' => strrpos($this->sqlText, $parameter),
135 5
                    'value'    => &$variable,
136 5
                    'type'     => $data_type,
137
                );
138 5
            }
139 9
        } catch (\Exception $ex) {
140
            //throw new PdoOci8Exception($ex->getMessage(), $ex->getCode(), $ex);
0 ignored issues
show
Unused Code Comprehensibility introduced by
70% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
141
        }
142
143 9
        return $isBound;
144
    }
145
146
    /**
147
     * @param string $parameter
148
     * @param $value
149
     * @param int $data_type
150
     *
151
     * @link http://php.net/manual/en/pdostatement.bindvalue.php
152
     * @return bool
153
     */
154 4
    public function bindValue($parameter, $value, $data_type = \PDO::PARAM_STR)
155
    {
156 4
        return $this->bindParam($parameter, $value, $data_type);
157
    }
158
159
    /**
160
     * @link http://php.net/manual/en/pdostatement.closecursor.php
161
     * @return bool
162
     */
163 1
    public function closeCursor()
164
    {
165 1
        return $this->statement->cancel();
166
    }
167
168
    /**
169
     * @link http://php.net/manual/en/pdostatement.columncount.php
170
     * @return int
171
     */
172 1
    public function columnCount()
173
    {
174 1
        return 0;
175
    }
176
177
    /**
178
     * @link http://php.net/manual/en/pdostatement.debugdumpparams.php
179
     */
180 2
    public function debugDumpParams()
181
    {
182 2
        $sqlText = $this->sqlText;
183 2
        $sqlTextLength = strlen($sqlText);
184 2
        $parameters = $this->boundParameters;
185 2
        usort($parameters, function ($a, $b) {
186 2
            if ($a['position'] === $b['position']) {
187
                return 0;
188
            }
189
190 2
            return $a['position'] < $b['position'] ? -1 : 1;
191 2
        });
192 2
        $parametersCount = count($parameters);
193
194 2
        echo "SQL: [{$sqlTextLength}] {$sqlText}". PHP_EOL .
195 2
            "Params: {$parametersCount}";
196 2
        foreach ($parameters as $key => $parameter) {
197
            //TODO Add parameter position and number
198 2
            $position = $parameter['position'];
199 2
            $nameLength = strlen($parameter['name']);
200 2
            $name = $parameter['name'];
201 2
            $index = $key + 1;
202 2
            $dataType = $parameter['type'];
203
204 2
            echo PHP_EOL;
205 2
            if ($name === '') {
206
                echo "Key: Position #{$position}:". PHP_EOL;
207
            } else {
208 2
                echo "Key: Name: [{$nameLength}] {$name}". PHP_EOL;
209
            }
210 2
            echo "paramno={$index}". PHP_EOL .
211 2
                "name=[{$nameLength}]{$name}". PHP_EOL .
212 2
                "is_param=1". PHP_EOL .
213 2
                "param_type={$dataType}";
214 2
        }
215 2
    }
216
217
    /**
218
     * @link http://php.net/manual/en/pdostatement.errorcode.php
219
     * @return string
220
     */
221 2 View Code Duplication
    public function errorCode()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
222
    {
223 2
        $driverError = $this->statement->getError();
224 2
        if (!$driverError) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $driverError of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
225 1
            return null;
226
        }
227
228 1
        $error = $this->errorInfo();
229 1
        $sqlStateErrorCode = $error[0];
230
231 1
        return $sqlStateErrorCode;
232
    }
233
234
    /**
235
     * @link http://php.net/manual/en/pdo.errorinfo.php
236
     * @return array
237
     */
238 3
    public function errorInfo()
239
    {
240 3
        $driverError = $this->statement->getError();
241 3
        if ($driverError) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $driverError of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
242 2
            $driverErrorMessage = $driverError['message'];
243 2
            $driverErrorCode = $driverError['code'];
244 2
        } else {
245 1
            $driverErrorMessage = null;
246 1
            $driverErrorCode = null;
247
        }
248
249 3
        $sqlStateErrorCode = OracleSqlStateCode::getSqlStateErrorCode((int)$driverErrorCode);
250
        $error = array(
251 3
            $sqlStateErrorCode,
252 3
            $driverErrorCode,
253
            $driverErrorMessage
254 3
        );
255
256 3
        return $error;
257
    }
258
259
    /**
260
     * @param array $input_parameters
261
     *
262
     * @link http://php.net/manual/en/pdostatement.execute.php
263
     * @return bool
264
     */
265 24
    public function execute($input_parameters = array())
266
    {
267
        try {
268 24
            foreach ($input_parameters as $key => $value) {
269 1
                if (is_int($key)) {
270
                    $parameterName = $key + 1;
271
                } else {
272 1
                    $parameterName = $key;
273
                }
274 1
                $this->bindValue($parameterName, $value);
275 24
            }
276
277 24
            if ($this->getAttribute(\PDO::ATTR_AUTOCOMMIT)) {
278 24
                $isCommitOnSuccess = OCI_NO_AUTO_COMMIT;
279 24
            } else {
280
                $isCommitOnSuccess = OCI_COMMIT_ON_SUCCESS;
281
            }
282
283 24
            $result = $this->statement->execute($isCommitOnSuccess);
284
285 21
            return $result;
286 3
        } catch (\Exception $ex) {
287
            //TODO Handle Exception
288 3
            new PdoOci8Exception($ex->getMessage(), $ex->getCode(), $ex);
289
        }
290
291 3
        return false;
292
    }
293
294
    /**
295
     * @param int $fetch_style
296
     * @param int $cursor_orientation
297
     * @param int $cursor_offset
298
     *
299
     * @link http://php.net/manual/en/pdostatement.fetch.php
300
     * @return mixed
301
     */
302 11
    public function fetch(
303
        $fetch_style = \PDO::ATTR_DEFAULT_FETCH_MODE,
304
        $cursor_orientation = \PDO::FETCH_ORI_NEXT,
305
        $cursor_offset = 0
306
    ) {
307 11
        if ($fetch_style === null) {
308
            $fetch_style = \PDO::ATTR_DEFAULT_FETCH_MODE;
309
        }
310
311
        try {
312 11
            if ($fetch_style === \PDO::ATTR_DEFAULT_FETCH_MODE) {
313 4
                $fetch_style = $this->getAttribute(\PDO::ATTR_DEFAULT_FETCH_MODE);
314 4
            }
315
316
            switch ($fetch_style) {
317 11
                case \PDO::FETCH_ASSOC:
318 1
                    $mode = OCI_ASSOC;
319 1
                    break;
320 10
                case \PDO::FETCH_BOUND:
321
                    // returns TRUE and assigns the values of the columns in your result set to the PHP
322
                    // variables to which they were bound with the PDOStatement::bindColumn() method
323 1
                    return $this->statement->fetch();
324 9
                case \PDO::FETCH_CLASS:
325
                    // returns a new instance of the requested class, mapping the columns of the result
326
                    // set to named properties in the class
327
                    $className = $this->getFetchTarget();
328
                    $args      = $this->getFetchTargetConstructorArgs();
329
                    return $this->fetchObject($className, $args);
0 ignored issues
show
Bug introduced by
It seems like $className defined by $this->getFetchTarget() on line 327 can also be of type object; however, Jpina\PdoOci8\PdoOci8Statement::fetchObject() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
330
                    break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
331 9
                case \PDO::FETCH_CLASS | \PDO::FETCH_CLASSTYPE:
332
                    // the name of the class is determined from a value of the first column.
333
                    return $this->fetchInto();
334
                    break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
335 9
                case \PDO::FETCH_INTO:
336
                    // updates an existing instance of the requested class, mapping the columns of the
337
                    // result set to named properties in the class
338
                    $instance = $this->getFetchTarget();
339
                    return $this->fetchInto($instance);
0 ignored issues
show
Bug introduced by
It seems like $instance defined by $this->getFetchTarget() on line 338 can also be of type string; however, Jpina\PdoOci8\PdoOci8Statement::fetchInto() does only seem to accept object|null, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
340
                    break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
341 9
                case \PDO::FETCH_LAZY:
342 9
                case \PDO::FETCH_BOTH + \PDO::FETCH_OBJ:
343
                     // combines PDO::FETCH_BOTH and PDO::FETCH_OBJ, creating the object variable names
344
                    // as they are accessed
345
                    break;
346 9
                case \PDO::FETCH_NAMED:
347
                    // returns an array with the same form as PDO::FETCH_ASSOC, except that if there are
348
                    // multiple columns with the same name, the value referred to by that key will be an
349
                    // array of all the values in the row that had that column name
350
                    break;
351 9
                case \PDO::FETCH_NUM:
352 4
                    $mode = OCI_NUM;
353 4
                    break;
354 5
                case \PDO::FETCH_OBJ:
355
                    // returns an anonymous object with property names that correspond to the column names
356
                    // returned in your result set
357
                    return $this->fetchObject();
358
                    break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
359 5
                case \PDO::FETCH_BOTH:
360 5
                default:
361 5
                    $mode = OCI_BOTH;
362 5
                    break;
363
            }
364
            // TODO Combine other flags: eg. OCI_NULLS and OCI_LOBS
365
            // TODO update $this->numRows on successfull fetch
366
            // TODO update $this->isIteratorValid on NOT successfull fetch
367 10
            return $this->statement->fetchArray($mode);
0 ignored issues
show
Bug introduced by
The variable $mode does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
368
        } catch (\Exception $ex) {
369
            new PdoOci8Exception($ex->getMessage(), $ex->getCode(), $ex);
370
        }
371
372
        return false;
373
    }
374
375
    /**
376
     * @param int $fetch_style
377
     * @param mixed $fetch_argument
378
     * @param array $ctor_args
379
     *
380
     * @link http://php.net/manual/en/pdostatement.fetchall.php
381
     * @return array
382
     */
383 6
    public function fetchAll($fetch_style = null, $fetch_argument = null, $ctor_args = array())
384
    {
385
        // TODO Implement properly (use all other fetch modes)
386
        switch ($fetch_style) {
387 6
            case \PDO::FETCH_NUM:
388 1
                $mode = OCI_NUM;
389 1
                break;
390 5
            case \PDO::FETCH_OBJ:
391 5
            case \PDO::FETCH_INTO:
392 5
            case \PDO::FETCH_CLASS:
393 5
            case \PDO::FETCH_BOTH:
394 5
            case \PDO::FETCH_COLUMN:
395 5
            case \PDO::FETCH_BOUND:
396 5
            case \PDO::FETCH_CLASSTYPE:
397 5
            case \PDO::FETCH_FUNC:
398 5
            case \PDO::FETCH_GROUP:
399 5
            case \PDO::FETCH_KEY_PAIR:
400 5
            case \PDO::FETCH_LAZY:
401 5
            case \PDO::FETCH_NAMED:
402 5
            case \PDO::FETCH_UNIQUE:
403
                throw new PdoOci8Exception('Not implemented.');
404
                //break;
405 5
            case \PDO::FETCH_ASSOC:
406 5
            default:
407 5
                $mode = OCI_ASSOC;
408 5
        }
409
410 6
        $this->statement->fetchAll($rows, 0, -1, OCI_FETCHSTATEMENT_BY_ROW | $mode);
411
412 6
        return $rows;
413
    }
414
415
    /**
416
     * @param int $column_number
417
     *
418
     * @link http://php.net/manual/en/pdostatement.fetchcolumn.php
419
     * @return mixed
420
     */
421 3
    public function fetchColumn($column_number = 0)
422
    {
423 3
        $row = $this->fetch(\PDO::FETCH_NUM);
424 3
        if ($row === false) {
425 1
            return false;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return false; (false) is incompatible with the return type of the parent method PDOStatement::fetchColumn of type string.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
426
        }
427
428 3
        if (array_key_exists($column_number, $row)) {
429 2
            return $row[$column_number];
430
        }
431
432
        // TODO Throw Exception
433 1
        return false;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return false; (false) is incompatible with the return type of the parent method PDOStatement::fetchColumn of type string.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
434
    }
435
436
437
    /**
438
     * @param string $class_name
439
     * @param array $ctor_args
440
     *
441
     * @link http://php.net/manual/en/pdostatement.fetchobject.php
442
     * @return bool|object
443
     */
444
    public function fetchObject($class_name = 'stdClass', $ctor_args = array())
445
    {
446
        $instance = $this->getFetchClassInstance($class_name, $ctor_args);
447
448
        return $this->fetchInto($instance);
449
    }
450
451
    /**
452
     * @param object|null $instance
453
     * @return bool|object
454
     */
455
    protected function fetchInto($instance = null){
456
        $row = $this->fetch(\PDO::FETCH_ASSOC);
457
        if ($row === false) {
458
            return false;
459
        }
460
461
        if ($instance === null) {
462
            $className = reset($row);
463
            $args = $this->getFetchTargetConstructorArgs();
464
            $instance = $this->getFetchClassInstance($className, $args);
465
        }
466
467
        foreach ($row as $property => $value) {
0 ignored issues
show
Bug introduced by
The expression $row of type boolean|object|array is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
468
            if ($instance instanceof \stdClass) {
469
                $instance->{$property} = $value;
470
            } elseif (property_exists($instance, $property)) {
471
                $instance->{$property} = $value;
472
            }
473
        }
474
475
        return $instance;
476
    }
477
478
    /**
479
     * @param int $attribute
480
     *
481
     * @link http://php.net/manual/en/pdostatement.getattribute.php
482
     * @return mixed
483
     */
484 27
    public function getAttribute($attribute)
485
    {
486 27
        if (array_key_exists($attribute, $this->options)) {
487 27
            return $this->options[$attribute];
488
        }
489
490
        return null;
491
    }
492
493
    /**
494
     * @param int $column
495
     *
496
     * @throws PdoOci8Exception
497
     *
498
     * @link http://php.net/manual/en/pdostatement.getcolumnmeta.php
499
     * @return bool|array
500
     */
501 4
    public function getColumnMeta($column)
502
    {
503 4
        $statementType = $this->statement->getType();
504 4
        if ($statementType !== 'SELECT') {
505 1
            return false;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return false; (false) is incompatible with the return type of the parent method PDOStatement::getColumnMeta of type array.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
506
        }
507
508 3
        $table = $this->getTableName();
509
510 3
        $sqlText = $this->sqlText;
511 3
        $statement = $this->getConnection()->parse($sqlText);
512 3
        $statement->execute(OCI_DESCRIBE_ONLY);
513 3
        $field = $statement->getField($column + 1);
514
515 3
        if ($field instanceof Oci8FieldInterface) {
516
            // Oracle returns attributes in upper case by default
517 3
            $fieldName = $field->getName();
518 3
            if ($this->getAttribute(\PDO::ATTR_CASE) === \PDO::CASE_LOWER) {
519
                $fieldName = strtolower($fieldName);
520
            }
521
522
            return array(
523 3
                'native_type'      => $field->getRawType(),
524 3
                'driver:decl_type' => $field->getType(),
525 3
                'flags'            => array(),
526 3
                'name'             => $fieldName,
527 3
                'table'            => $table,
528 3
                'len'              => $field->getSize(),
529 3
                'precision'        => $field->getPrecision() - $field->getScale(),
530 3
                'pdo_type'         => $this->getPDODataType($field->getType()),
531 3
            );
532
        }
533
534
        return false;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return false; (false) is incompatible with the return type of the parent method PDOStatement::getColumnMeta of type array.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
535
    }
536
537
    /**
538
     * @return Oci8ConnectionInterface
539
     */
540 3
    protected function getConnection()
541
    {
542 3
        return $this->connection;
543
    }
544
545
    /**
546
     * @param int $type
547
     * @throws PdoOci8Exception
548
     * @return int
549
     */
550 11
    protected function getDriverDataType($type)
0 ignored issues
show
Unused Code introduced by
The parameter $type is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
551
    {
552 11
        $dataType = null;
553
        switch ($dataType) {
554 11
            case \PDO::PARAM_BOOL:
555
                $dataType = SQLT_BOL;
556
                break;
557 11
            case \PDO::PARAM_INT:
558
                $dataType = SQLT_INT;
559
                break;
560 11
            case \PDO::PARAM_LOB:
561
                $dataType = SQLT_CLOB;
562
                break;
563 11
            case \PDO::PARAM_STMT:
564
                throw new PdoOci8Exception('Parameter type \PDO::PARAM_STMT is not currently supported.');
565 11
            case \PDO::PARAM_NULL:
566 11
            case \PDO::PARAM_STR:
567 11
                $dataType = SQLT_CHR;
568 11
                break;
569
            case \PDO::PARAM_INPUT_OUTPUT:
570
            case \PDO::PARAM_INPUT_OUTPUT | \PDO::PARAM_BOOL:
571
            case \PDO::PARAM_INPUT_OUTPUT | \PDO::PARAM_INT:
572
            case \PDO::PARAM_INPUT_OUTPUT | \PDO::PARAM_LOB:
573
            case \PDO::PARAM_INPUT_OUTPUT | \PDO::PARAM_STMT:
574
            case \PDO::PARAM_INPUT_OUTPUT | \PDO::PARAM_NULL:
575
            case \PDO::PARAM_INPUT_OUTPUT | \PDO::PARAM_STR:
576
                throw new PdoOci8Exception('Parameter type \PDO::PARAM_INPUT_OUTPUT is not currently supported.');
577
        }
578
579 11
        return $dataType;
580
    }
581
582
    /**
583
     * @param string $className
584
     * @param array $args
585
     * @return object
586
     */
587
    protected function getFetchClassInstance($className = 'stdClass', $args = array())
588
    {
589
        $reflexionClass = new \ReflectionClass($className);
590
        $instance       = $reflexionClass->newInstanceArgs($args);
591
592
        return $instance;
593
    }
594
595
    /**
596
     * @return string|object
597
     */
598
    protected function getFetchTarget()
599
    {
600
        return $this->fetchTarget;
601
    }
602
603
    /**
604
     * @return array
605
     */
606
    protected function getFetchTargetConstructorArgs()
607
    {
608
        return $this->fetchTargetConstructorArgs;
609
    }
610
611
    /**
612
     * @return \Traversable
613
     */
614 1
    protected function getInternalIterator()
615
    {
616 1
        if ($this->iterator instanceof \Traversable) {
617
            return $this->iterator;
618
        }
619
620 1
        $rows = $this->fetchAll();
621 1
        if ($rows === false) {
622
            //TODO Throw Exception?
623
            $rows = array();
624
        }
625
626 1
        $this->iterator = new \ArrayIterator($rows);
627
628 1
        return $this->iterator;
629
    }
630
631
    /**
632
     * @param string $data_type The data type name
633
     *
634
     * @return int
635
     */
636 3
    protected function getPDODataType($data_type)
637
    {
638
        //TODO Add all oracle data types
639 3
        $pdoDataType = \PDO::PARAM_STR;
640
        switch ($data_type) {
641 3
            case 'NUMBER':
642 1
                $pdoDataType = \PDO::PARAM_INT;
643 1
                break;
644 2
            case 'CHAR':
645 2
            case 'VARCHAR2':
646 2
            case 'NVARCHAR2':
647 2
                $pdoDataType = \PDO::PARAM_STR;
648 2
                break;
649
            case 'LOB':
650
            case 'CLOB':
651
            case 'BLOB':
652
            case 'NCLOB':
653
                $pdoDataType = \PDO::PARAM_LOB;
654
                break;
655
            case 'BOOLEAN':
656
                $pdoDataType = \PDO::PARAM_BOOL;
657
        }
658
659 3
        return $pdoDataType;
660
    }
661
662
    /**
663
     * @return string
664
     */
665 3
    protected function getTableName()
666
    {
667 3
        $statementType = $this->statement->getType();
668 3
        if ($statementType !== 'SELECT') {
669
            return '';
670
        }
671
672 3
        $sqlText = strtoupper($this->sqlText);
673 3
        $idx     = strpos($sqlText, ' FROM ');
674 3
        $table   = substr($this->sqlText, $idx + 6);
675 3
        $table   = trim($table);
676
677 3
        if (strpos($table, '(') !== false) {
678 1
            return '';
679
        }
680
681 2
        $idxSpace = strpos($table, ' ');
682 2
        if ($idxSpace !== false) {
683 1
            $table = substr($table, 0, $idxSpace);
684 1
        }
685
686 2
        return $table;
687
    }
688
689
    /**
690
     * @link http://php.net/manual/en/pdostatement.nextrowset.php
691
     * @return bool
692
     */
693
    public function nextRowset()
694
    {
695
        throw new \Exception('Not implemented');
696
    }
697
698
    /**
699
     * @link http://php.net/manual/en/pdostatement.rowcount.php
700
     * @return int
701
     */
702 8
    public function rowCount()
703
    {
704 8
        return $this->statement->getNumRows();
705
    }
706
707
    /**
708
     * @param $attribute
709
     * @param $value
710
     *
711
     * @link http://php.net/manual/en/pdostatement.setattribute.php
712
     * @return bool
713
     */
714 47
    public function setAttribute($attribute, $value)
715
    {
716
        $readOnlyAttributes = array(
717 47
            \PDO::ATTR_AUTOCOMMIT,
718 47
        );
719
720 47 View Code Duplication
        if (array_search($attribute, $readOnlyAttributes) !== false ||
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
721 47
            !array_key_exists($attribute, $this->options)) {
722 47
            return false;
723
        }
724
725 47
        $this->options[$attribute] = $value;
726
727 47
        return true;
728
    }
729
730
    /**
731
     * @param int $mode
732
     * @param string|int|object $target
733
     * @param array $ctor_args
734
     *
735
     * @link http://php.net/manual/en/pdostatement.setfetchmode.php
736
     * @return bool
737
     */
738 1
    public function setFetchMode($mode, $target = null, $ctor_args = array())
739
    {
740 1
        $isSuccess = $this->setAttribute(\PDO::ATTR_DEFAULT_FETCH_MODE, $mode);
741 1
        if ($isSuccess) {
742 1
            $this->setFetchTarget($target);
0 ignored issues
show
Bug introduced by
It seems like $target defined by parameter $target on line 738 can also be of type integer or null; however, Jpina\PdoOci8\PdoOci8Statement::setFetchTarget() does only seem to accept string|object, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
743 1
            $this->setFetchTargetConstructorArgs($ctor_args);
744 1
        }
745
746 1
        return $isSuccess;
747
    }
748
749
    /**
750
     * @param string|object $target
751
     */
752 1
    protected function setFetchTarget($target)
753
    {
754 1
        $this->fetchTarget = $target;
755 1
    }
756
757
    /**
758
     * @param array $args
759
     */
760 1
    protected function setFetchTargetConstructorArgs($args)
761
    {
762 1
        $this->fetchTargetConstructorArgs = $args;
763 1
    }
764
765
    /**
766
     * @return array
767
     */
768
    public function current()
769
    {
770
        $iterator = $this->getInternalIterator();
771
772
        return $iterator->current();
773
    }
774
775
    public function next()
776
    {
777
        $iterator = $this->getInternalIterator();
778
779
        $iterator->next();
780
    }
781
782
    /**
783
     * @return int|null
784
     */
785
    public function key()
786
    {
787
        $iterator = $this->getInternalIterator();
788
789
        return $iterator->key();
790
    }
791
792
    /**
793
     * @return bool
794
     */
795 1
    public function valid()
796
    {
797 1
        $iterator = $this->getInternalIterator();
798
799 1
        return $iterator->valid();
800
    }
801
802
    public function rewind()
803
    {
804
        $iterator = $this->getInternalIterator();
805
806
        $iterator->rewind();
807
    }
808
}
809