Completed
Push — dev-0.1.x ( 89d2d8...828717 )
by Josué
11:01
created

PdoOci8Statement::bindValue()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
cc 1
eloc 2
nc 1
nop 3
crap 1
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 44
    public function __construct(Oci8ConnectionInterface $connection, $sqlText, $options = array())
35
    {
36 44
        if (!is_string($sqlText)) {
37
            throw new PdoOci8Exception('$sqlText is not a string');
38
        }
39
40 44
        $this->connection  = $connection;
41 44
        $this->sqlText     = $sqlText;
42
        //TODO assign value to queryString
43
        //$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...
44
45 44
        $this->options = array(
46 44
            \PDO::ATTR_AUTOCOMMIT          => true,
47 44
            \PDO::ATTR_CASE                => \PDO::CASE_NATURAL,
48 44
            \PDO::ATTR_ERRMODE             => \PDO::ERRMODE_SILENT,
49 44
            \PDO::ATTR_ORACLE_NULLS        => \PDO::NULL_NATURAL,
50 44
            \PDO::ATTR_PREFETCH            => 100,
51 44
            \PDO::ATTR_TIMEOUT             => 600,
52 44
            \PDO::ATTR_STRINGIFY_FETCHES   => false,
53 44
            \PDO::ATTR_STATEMENT_CLASS     => null,
54 44
            \PDO::ATTR_EMULATE_PREPARES    => false,
55 44
            \PDO::ATTR_DEFAULT_FETCH_MODE  => \PDO::FETCH_BOTH,
56 44
            \PDO::ATTR_FETCH_TABLE_NAMES   => false,
57 44
            \PDO::ATTR_FETCH_CATALOG_NAMES => false,
58 44
            \PDO::ATTR_MAX_COLUMN_LEN      => 0,
59 44
            PdoOci8::OCI_ATTR_RETURN_LOBS  => false,
60
        );
61
62 44 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...
63 44
            if (array_key_exists($option, $this->options)) {
64 44
                $this->options[$option] = $value;
65 44
            }
66 44
        }
67
68 44
        foreach ($options as $attribute => $value) {
69 44
            $this->setAttribute($attribute, $value);
70 44
        }
71
72
        try {
73 44
            $this->statement = $connection->parse($sqlText);
74 44
        } catch (\Exception $ex) {
75
            throw new PdoOci8Exception($ex->getMessage(), $ex->getCode(), $ex);
76
        }
77 44
    }
78
79
    /**
80
     * @param int|string $column
81
     * @param mixed $param
82
     * @param int $type
83
     * @param int $maxlen
84
     * @param mixed $driverdata
85
     *
86
     * @link http://php.net/manual/en/pdostatement.bindcolumn.php
87
     * @return bool
88
     */
89 2
    public function bindColumn($column, &$param, $type = null, $maxlen = null, $driverdata = null)
90
    {
91
        try {
92 2
            $type = $type === null ? \PDO::PARAM_STR : $type;
93 2
            $dataType = $this->getDriverDataType($type);
94 2
            return $this->statement->defineByName($column, $param, $dataType);
95
        } catch (\Exception $ex) {
96
            //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...
97
        }
98
99
        return false;
100
    }
101
102
    /**
103
     * @param string $parameter
104
     * @param mixed $variable
105
     * @param int $data_type
106
     * @param int $length
107
     * @param mixed $driver_options
108
     *
109
     * @link http://php.net/manual/en/pdostatement.bindparam.php
110
     * @return bool
111
     */
112 8
    public function bindParam(
113
        $parameter,
114
        &$variable,
115
        $data_type = \PDO::PARAM_STR,
116
        $length = null,
117
        $driver_options = null
118
    ) {
119 8
        $isBound = false;
120
        try {
121 8
            $data_type = $data_type === null ? \PDO::PARAM_STR : $data_type;
122 8
            $dataType = $this->getDriverDataType($data_type);
123 8
            $length = $length !== null ? $length : -1;
124 8
            $isBound = $this->statement->bindByName($parameter, $variable, $length, $dataType);
125 4
            if ($isBound) {
126 4
                $this->boundParameters[$parameter] = array(
127 4
                    'name'     => is_int($parameter) ? '' : $parameter,
128 4
                    'position' => strrpos($this->sqlText, $parameter),
129 4
                    'value'    => &$variable,
130 4
                    'type'     => $data_type,
131 1
                );
132 4
            }
133 8
        } catch (\Exception $ex) {
134
            //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...
135
        }
136
137 8
        return $isBound;
138
    }
139
140
    /**
141
     * @param string $parameter
142
     * @param $value
143
     * @param int $data_type
144
     *
145
     * @link http://php.net/manual/en/pdostatement.bindvalue.php
146
     * @return bool
147
     */
148 3
    public function bindValue($parameter, $value, $data_type = \PDO::PARAM_STR)
149
    {
150 3
        return $this->bindParam($parameter, $value, $data_type);
151
    }
152
153
    /**
154
     * @link http://php.net/manual/en/pdostatement.closecursor.php
155
     * @return bool
156
     */
157 1
    public function closeCursor()
158
    {
159 1
        return $this->statement->cancel();
160
    }
161
162
    /**
163
     * @link http://php.net/manual/en/pdostatement.columncount.php
164
     * @return int
165
     */
166 1
    public function columnCount()
167
    {
168 1
        return 0;
169
    }
170
171
    /**
172
     * @link http://php.net/manual/en/pdostatement.debugdumpparams.php
173
     */
174 2
    public function debugDumpParams()
175
    {
176 2
        $sqlText = $this->sqlText;
177 2
        $sqlTextLength = strlen($sqlText);
178 2
        $parameters = $this->boundParameters;
179 2
        usort($parameters, function ($a, $b) {
180 2
            if ($a['position'] === $b['position']) {
181
                return 0;
182
            }
183
184 2
            return $a['position'] < $b['position'] ? -1 : 1;
185 2
        });
186 2
        $parametersCount = count($parameters);
187
188 2
        echo "SQL: [{$sqlTextLength}] {$sqlText}". PHP_EOL .
189 2
            "Params: {$parametersCount}";
190 2
        foreach ($parameters as $key => $parameter) {
191
            //TODO Add parameter position and number
192 2
            $position = $parameter['position'];
193 2
            $nameLength = strlen($parameter['name']);
194 2
            $name = $parameter['name'];
195 2
            $index = $key + 1;
196 2
            $dataType = $parameter['type'];
197
198 2
            echo PHP_EOL;
199 2
            if ($name === '') {
200
                echo "Key: Position #{$position}:". PHP_EOL;
201
            } else {
202 2
                echo "Key: Name: [{$nameLength}] {$name}". PHP_EOL;
203
            }
204 2
            echo "paramno={$index}". PHP_EOL .
205 2
                "name=[{$nameLength}]{$name}". PHP_EOL .
206 2
                "is_param=1". PHP_EOL .
207 2
                "param_type={$dataType}";
208 2
        }
209 2
    }
210
211
    /**
212
     * @link http://php.net/manual/en/pdostatement.errorcode.php
213
     * @return string
214
     */
215 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...
216
    {
217 2
        $driverError = $this->statement->getError();
218 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...
219 1
            return null;
220
        }
221
222 1
        $error = $this->errorInfo();
223 1
        $sqlStateErrorCode = $error[0];
224
225 1
        return $sqlStateErrorCode;
226
    }
227
228
    /**
229
     * @link http://php.net/manual/en/pdo.errorinfo.php
230
     * @return array
231
     */
232 3
    public function errorInfo()
233
    {
234 3
        $driverError = $this->statement->getError();
235 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...
236 2
            $driverErrorMessage = $driverError['message'];
237 2
            $driverErrorCode = $driverError['code'];
238 2
        } else {
239 1
            $driverErrorMessage = null;
240 1
            $driverErrorCode = null;
241
        }
242
243 3
        $sqlStateErrorCode = OracleSqlStateCode::getSqlStateErrorCode((int)$driverErrorCode);
244
        $error = array(
245 3
            $sqlStateErrorCode,
246 3
            $driverErrorCode,
247
            $driverErrorMessage
248 3
        );
249
250 3
        return $error;
251
    }
252
253
    /**
254
     * @param array $input_parameters
255
     *
256
     * @link http://php.net/manual/en/pdostatement.execute.php
257
     * @return bool
258
     */
259 21
    public function execute($input_parameters = array())
260
    {
261
        try {
262 21
            foreach ($input_parameters as $key => $value) {
263
                if (is_int($key)) {
264
                    $parameterName = $key + 1;
265
                } else {
266
                    $parameterName = $key;
267
                }
268
                $this->bindValue($parameterName, $value);
269 21
            }
270
271 21
            if ($this->getAttribute(\PDO::ATTR_AUTOCOMMIT)) {
272 21
                $isCommitOnSuccess = OCI_NO_AUTO_COMMIT;
273 21
            } else {
274
                $isCommitOnSuccess = OCI_COMMIT_ON_SUCCESS;
275
            }
276
277 21
            $result = $this->statement->execute($isCommitOnSuccess);
278
279 18
            return $result;
280 3
        } catch (\Exception $ex) {
281
            //TODO Handle Exception
282 3
            new PdoOci8Exception($ex->getMessage(), $ex->getCode(), $ex);
283
        }
284
285 3
        return false;
286
    }
287
288
    /**
289
     * @param int $fetch_style
290
     * @param int $cursor_orientation
291
     * @param int $cursor_offset
292
     *
293
     * @link http://php.net/manual/en/pdostatement.fetch.php
294
     * @return mixed
295
     */
296 10
    public function fetch(
297
        $fetch_style = \PDO::ATTR_DEFAULT_FETCH_MODE,
298
        $cursor_orientation = \PDO::FETCH_ORI_NEXT,
299
        $cursor_offset = 0
300
    ) {
301 10
        if ($fetch_style === null) {
302
            $fetch_style === \PDO::ATTR_DEFAULT_FETCH_MODE;
303
        }
304
305
        try {
306
//            if ($fetch_style === \PDO::ATTR_DEFAULT_FETCH_MODE) {
0 ignored issues
show
Unused Code Comprehensibility introduced by
44% 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...
307
//                $fetch_style = $this->getAttribute(\PDO::ATTR_DEFAULT_FETCH_MODE);
308
//            }
309
310
            switch ($fetch_style) {
311 10
                case \PDO::FETCH_ASSOC:
312 3
                    $mode = OCI_ASSOC;
313 3
                    break;
314 7
                case \PDO::FETCH_BOUND:
315
                    // returns TRUE and assigns the values of the columns in your result set to the PHP
316
                    // variables to which they were bound with the PDOStatement::bindColumn() method
317 1
                    return $this->statement->fetch();
318 6
                case \PDO::FETCH_CLASS:
319
                    // returns a new instance of the requested class, mapping the columns of the result
320
                    // set to named properties in the class
321
                    break;
322 6
                case \PDO::FETCH_CLASS | \PDO::FETCH_CLASSTYPE:
323
                    // the name of the class is determined from a value of the first column.
324
                    break;
325 6
                case \PDO::FETCH_INTO:
326
                    // updates an existing instance of the requested class, mapping the columns of the
327
                    // result set to named properties in the class
328
                    break;
329 6
                case \PDO::FETCH_LAZY:
330 6
                case \PDO::FETCH_BOTH + \PDO::FETCH_OBJ:
331
                     // combines PDO::FETCH_BOTH and PDO::FETCH_OBJ, creating the object variable names
332
                    // as they are accessed
333
                    break;
334 6
                case \PDO::FETCH_NAMED:
335
                    // returns an array with the same form as PDO::FETCH_ASSOC, except that if there are
336
                    // multiple columns with the same name, the value referred to by that key will be an
337
                    // array of all the values in the row that had that column name
338
                    break;
339 6
                case \PDO::FETCH_NUM:
340 4
                    $mode = OCI_NUM;
341 4
                    break;
342 2
                case \PDO::FETCH_OBJ:
343
                    // returns an anonymous object with property names that correspond to the column names
344
                    // returned in your result set
345 1
                    return $this->statement->fetchObject();
346
                    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...
347 1
                case \PDO::FETCH_BOTH:
348 1
                default:
349 1
                    $mode = OCI_BOTH;
350 1
                    break;
351 1
            }
352
            // TODO Combine other flags: eg. OCI_NULLS and OCI_LOBS
353
            // TODO update $this->numRows on successfull fetch
354
            // TODO update $this->isIteratorValid on NOT successfull fetch
355 8
            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...
356
        } catch (\Exception $ex) {
357
            new PdoOci8Exception($ex->getMessage(), $ex->getCode(), $ex);
358
        }
359
360
        return false;
361
    }
362
363
    /**
364
     * @param int $fetch_style
365
     * @param mixed $fetch_argument
366
     * @param array $ctor_args
367
     *
368
     * @link http://php.net/manual/en/pdostatement.fetchall.php
369
     * @return array
370
     */
371 4
    public function fetchAll($fetch_style = null, $fetch_argument = null, $ctor_args = array())
372
    {
373
        // TODO Implement properly (use all other fetch modes)
374
        switch ($fetch_style) {
375 4
            case \PDO::FETCH_NUM:
376 1
                $mode = OCI_NUM;
377 1
                break;
378 3
            case \PDO::FETCH_OBJ:
379 3
            case \PDO::FETCH_INTO:
380 3
            case \PDO::FETCH_CLASS:
381 3
            case \PDO::FETCH_BOTH:
382 3
            case \PDO::FETCH_COLUMN:
383 3
            case \PDO::FETCH_BOUND:
384 3
            case \PDO::FETCH_CLASSTYPE:
385 3
            case \PDO::FETCH_FUNC:
386 3
            case \PDO::FETCH_GROUP:
387 3
            case \PDO::FETCH_KEY_PAIR:
388 3
            case \PDO::FETCH_LAZY:
389 3
            case \PDO::FETCH_NAMED:
390 3
            case \PDO::FETCH_UNIQUE:
391
                throw new PdoOci8Exception('Not implemented.');
392
                //break;
393 3
            case \PDO::FETCH_ASSOC:
394 3
            default:
395 3
                $mode = OCI_ASSOC;
396 3
        }
397
398 4
        $this->statement->fetchAll($rows, 0, -1, OCI_FETCHSTATEMENT_BY_ROW | $mode);
399
400 4
        return $rows;
401
    }
402
403
    /**
404
     * @param int $column_number
405
     *
406
     * @link http://php.net/manual/en/pdostatement.fetchcolumn.php
407
     * @return mixed
408
     */
409 3
    public function fetchColumn($column_number = 0)
410
    {
411 3
        $row = $this->fetch(\PDO::FETCH_NUM);
412 3
        if ($row === false) {
413 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...
414
        }
415
416 3
        if (array_key_exists($column_number, $row)) {
417 2
            return $row[$column_number];
418
        }
419
420
        // TODO Throw Exception
421 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...
422
    }
423
424
425
    /**
426
     * @param string $class_name
427
     * @param array $ctor_args
428
     *
429
     * @link http://php.net/manual/en/pdostatement.fetchobject.php
430
     * @return bool|object
431
     */
432 2
    public function fetchObject($class_name = "stdClass", $ctor_args = array())
433
    {
434 2
        $row = $this->fetch(\PDO::FETCH_ASSOC);
435 2
        if ($row === false) {
436
            return false;
437
        }
438
439 2
        $reflexionClass = new \ReflectionClass($class_name);
440 2
        $classInstance = $reflexionClass->newInstanceArgs($ctor_args);
441
442 2
        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...
443 2
            $classInstance->{$property} = $value;
444 2
        }
445
446 2
        return $classInstance;
447
    }
448
449
    /**
450
     * @param int $attribute
451
     *
452
     * @link http://php.net/manual/en/pdostatement.getattribute.php
453
     * @return mixed
454
     */
455 24
    public function getAttribute($attribute)
456
    {
457 24
        if (array_key_exists($attribute, $this->options)) {
458 24
            return $this->options[$attribute];
459
        }
460
461
        return null;
462
    }
463
464
    /**
465
     * @param int $column
466
     *
467
     * @throws PdoOci8Exception
468
     *
469
     * @link http://php.net/manual/en/pdostatement.getcolumnmeta.php
470
     * @return bool|array
471
     */
472 4
    public function getColumnMeta($column)
473
    {
474 4
        $statementType = $this->statement->getType();
475 4
        if ($statementType !== 'SELECT') {
476 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...
477
        }
478
479 3
        $table = $this->getTableName();
480
481 3
        $sqlText = $this->sqlText;
482 3
        $statement = $this->getConnection()->parse($sqlText);
483 3
        $statement->execute(OCI_DESCRIBE_ONLY);
484 3
        $field = $statement->getField($column + 1);
485
486 3
        if ($field instanceof Oci8FieldInterface) {
487
            // Oracle returns attributes in upper case by default
488 3
            $fieldName = $field->getName();
489 3
            if ($this->getAttribute(\PDO::ATTR_CASE) === \PDO::CASE_LOWER) {
490
                $fieldName = strtolower($fieldName);
491
            }
492
493
            return array(
494 3
                'native_type'      => $field->getRawType(),
495 3
                'driver:decl_type' => $field->getType(),
496 3
                'flags'            => array(),
497 3
                'name'             => $fieldName,
498 3
                'table'            => $table,
499 3
                'len'              => $field->getSize(),
500 3
                'precision'        => $field->getPrecision() - $field->getScale(),
501 3
                'pdo_type'         => $this->getPDODataType($field->getType()),
502 3
            );
503
        }
504
505
        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
    /**
509
     * @link http://php.net/manual/en/pdostatement.nextrowset.php
510
     * @return bool
511
     */
512
    public function nextRowset()
513
    {
514
        // TODO Implement
515
    }
516
517
    /**
518
     * @link http://php.net/manual/en/pdostatement.rowcount.php
519
     * @return int
520
     */
521 3
    public function rowCount()
522
    {
523 3
        return $this->statement->getNumRows();
524
    }
525
526
527
    /**
528
     * @param $attribute
529
     * @param $value
530
     *
531
     * @link http://php.net/manual/en/pdostatement.setattribute.php
532
     * @return bool
533
     */
534 44
    public function setAttribute($attribute, $value)
535
    {
536
        $readOnlyAttributes = array(
537 44
            \PDO::ATTR_AUTOCOMMIT,
538 44
        );
539
540 44 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...
541 44
            !array_key_exists($attribute, $this->options)) {
542 44
            return false;
543
        }
544
545 44
        $this->options[$attribute] = $value;
546
547 44
        return true;
548
    }
549
550
    /**
551
     * @param int $mode
552
     * @param string|int|object $target
553
     * @param array $ctor_args
554
     *
555
     * @link http://php.net/manual/en/pdostatement.setfetchmode.php
556
     * @return bool
557
     */
558 1
    public function setFetchMode($mode, $target = null, $ctor_args = array())
559
    {
560 1
        $isSuccess = $this->setAttribute(\PDO::ATTR_DEFAULT_FETCH_MODE, $mode);
561 1
        if ($isSuccess) {
562 1
            $this->fetchTarget = $target;
0 ignored issues
show
Bug introduced by
The property fetchTarget does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
563 1
            $this->fetchTargetConstructorArgs = $ctor_args;
0 ignored issues
show
Bug introduced by
The property fetchTargetConstructorArgs does not seem to exist. Did you mean fetchTarget?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
564 1
        }
565
566 1
        return $isSuccess;
567
    }
568
569
    /**
570
     * @return string
571
     */
572 3
    protected function getTableName()
573
    {
574 3
        $statementType = $this->statement->getType();
575 3
        if ($statementType !== 'SELECT') {
576
            return '';
577
        }
578
579 3
        $sqlText = strtoupper($this->sqlText);
580 3
        $idx     = strpos($sqlText, ' FROM ');
581 3
        $table   = substr($this->sqlText, $idx + 6);
582 3
        $table   = trim($table);
583
584 3
        if (strpos($table, '(') !== false) {
585 1
            return '';
586
        }
587
588 2
        $idxSpace = strpos($table, ' ');
589 2
        if ($idxSpace !== false) {
590 1
            $table = substr($table, 0, $idxSpace);
591 1
        }
592
593 2
        return $table;
594
    }
595
596
    /**
597
     * @param string $data_type The data type name
598
     *
599
     * @return int
600
     */
601 3
    protected function getPDODataType($data_type)
602
    {
603
        //TODO Add all oracle data types
604 3
        $pdoDataType = \PDO::PARAM_STR;
605
        switch ($data_type) {
606 3
            case 'NUMBER':
607 1
                $pdoDataType = \PDO::PARAM_INT;
608 1
                break;
609 2
            case 'CHAR':
610 2
            case 'VARCHAR2':
611 2
            case 'NVARCHAR2':
612 2
                $pdoDataType = \PDO::PARAM_STR;
613 2
                break;
614
            case 'LOB':
615
            case 'CLOB':
616
            case 'BLOB':
617
            case 'NCLOB':
618
                $pdoDataType = \PDO::PARAM_LOB;
619
                break;
620
            case 'BOOLEAN':
621
                $pdoDataType = \PDO::PARAM_BOOL;
622
        }
623
624 3
        return $pdoDataType;
625
    }
626
627
    /**
628
     * @param int $type
629
     * @throws PdoOci8Exception
630
     * @return int
631
     */
632 10
    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...
633
    {
634 10
        $dataType = null;
635
        switch ($dataType) {
636 10
            case \PDO::PARAM_BOOL:
637
                $dataType = SQLT_BOL;
638
                break;
639 10
            case \PDO::PARAM_INT:
640
                $dataType = SQLT_INT;
641
                break;
642 10
            case \PDO::PARAM_LOB:
643
                $dataType = SQLT_CLOB;
644
                break;
645 10
            case \PDO::PARAM_STMT:
646
                throw new PdoOci8Exception('Parameter type \PDO::PARAM_STMT is not currently supported.');
647 10
            case \PDO::PARAM_NULL:
648 10
            case \PDO::PARAM_STR:
649 10
                $dataType = SQLT_CHR;
650 10
                break;
651
            case \PDO::PARAM_INPUT_OUTPUT:
652
            case \PDO::PARAM_INPUT_OUTPUT | \PDO::PARAM_BOOL:
653
            case \PDO::PARAM_INPUT_OUTPUT | \PDO::PARAM_INT:
654
            case \PDO::PARAM_INPUT_OUTPUT | \PDO::PARAM_LOB:
655
            case \PDO::PARAM_INPUT_OUTPUT | \PDO::PARAM_STMT:
656
            case \PDO::PARAM_INPUT_OUTPUT | \PDO::PARAM_NULL:
657
            case \PDO::PARAM_INPUT_OUTPUT | \PDO::PARAM_STR:
658
                throw new PdoOci8Exception('Parameter type \PDO::PARAM_INPUT_OUTPUT is not currently supported.');
659
        }
660
661 10
        return $dataType;
662
    }
663
664
    /**
665
     * @return Oci8ConnectionInterface
666
     */
667 3
    protected function getConnection()
668
    {
669 3
        return $this->connection;
670
    }
671
672
    /**
673
     * @return \Traversable
674
     */
675 1
    protected function getInternalIterator()
676
    {
677 1
        if ($this->iterator instanceof \Traversable) {
678
            return $this->iterator;
679
        }
680
681 1
        $rows = $this->fetchAll();
682 1
        if ($rows === false) {
683
            //TODO Throw Exception?
684
            $rows = array();
685
        }
686
687 1
        $this->iterator = new \ArrayIterator($rows);
688
689 1
        return $this->iterator;
690
    }
691
692
    /**
693
     * @return array
694
     */
695
    public function current()
696
    {
697
        $iterator = $this->getInternalIterator();
698
699
        return $iterator->current();
700
    }
701
702
    public function next()
703
    {
704
        $iterator = $this->getInternalIterator();
705
706
        $iterator->next();
707
    }
708
709
    /**
710
     * @return int|null
711
     */
712
    public function key()
713
    {
714
        $iterator = $this->getInternalIterator();
715
716
        return $iterator->key();
717
    }
718
719
    /**
720
     * @return bool
721
     */
722 1
    public function valid()
723
    {
724 1
        $iterator = $this->getInternalIterator();
725
726 1
        return $iterator->valid();
727
    }
728
729
    public function rewind()
730
    {
731
        $iterator = $this->getInternalIterator();
732
733
        $iterator->rewind();
734
    }
735
}
736