Completed
Push — dev-0.1.x ( c6e53a...28fa1c )
by Josué
20:56
created

PdoOci8Statement::getFetchTarget()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 4
ccs 1
cts 1
cp 1
rs 10
cc 1
eloc 2
nc 1
nop 0
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 49
    /** @var string|object */
35
    private $fetchTarget = 'stdClass';
36 49
37
    /** @var array */
38
    private $fetchTargetConstructorArgs = array();
39
40 49
    public function __construct(Oci8ConnectionInterface $connection, $sqlText, $options = array())
41 49
    {
42
        if (!is_string($sqlText)) {
43
            throw new PdoOci8Exception('$sqlText is not a string');
44
        }
45 49
46 49
        $this->connection  = $connection;
47 49
        $this->sqlText     = $sqlText;
48 49
        //TODO assign value to queryString
49 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 49
51 49
        $this->options = array(
52 49
            \PDO::ATTR_AUTOCOMMIT          => true,
53 49
            \PDO::ATTR_CASE                => \PDO::CASE_NATURAL,
54 49
            \PDO::ATTR_ERRMODE             => \PDO::ERRMODE_SILENT,
55 49
            \PDO::ATTR_ORACLE_NULLS        => \PDO::NULL_NATURAL,
56 49
            \PDO::ATTR_PREFETCH            => 100,
57 49
            \PDO::ATTR_TIMEOUT             => 600,
58 49
            \PDO::ATTR_STRINGIFY_FETCHES   => false,
59 49
            \PDO::ATTR_STATEMENT_CLASS     => null,
60
            \PDO::ATTR_EMULATE_PREPARES    => false,
61
            \PDO::ATTR_DEFAULT_FETCH_MODE  => \PDO::FETCH_BOTH,
62 49
            \PDO::ATTR_FETCH_TABLE_NAMES   => false,
63 49
            \PDO::ATTR_FETCH_CATALOG_NAMES => false,
64 49
            \PDO::ATTR_MAX_COLUMN_LEN      => 0,
65 49
            PdoOci8::OCI_ATTR_RETURN_LOBS  => false,
66 49
        );
67
68 49 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 49
            if (array_key_exists($option, $this->options)) {
70 49
                $this->options[$option] = $value;
71
            }
72
        }
73 49
74 49
        foreach ($options as $attribute => $value) {
75
            $this->setAttribute($attribute, $value);
76
        }
77 49
78
        try {
79
            $this->statement = $connection->parse($sqlText);
80
        } catch (\Exception $ex) {
81
            throw new PdoOci8Exception($ex->getMessage(), $ex->getCode(), $ex);
82
        }
83
    }
84
85
    /**
86
     * @param int|string $column
87
     * @param mixed $param
88
     * @param int $type
89 2
     * @param int $maxlen
90
     * @param mixed $driverdata
91
     *
92 2
     * @link http://php.net/manual/en/pdostatement.bindcolumn.php
93 2
     * @return bool
94 2
     */
95
    public function bindColumn($column, &$param, $type = null, $maxlen = null, $driverdata = null)
96
    {
97
        try {
98
            $type = $type === null ? \PDO::PARAM_STR : $type;
99
            $dataType = $this->getDriverDataType($type);
100
            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 9
     * @param int $length
113
     * @param mixed $driver_options
114
     *
115
     * @link http://php.net/manual/en/pdostatement.bindparam.php
116
     * @return bool
117
     */
118
    public function bindParam(
119 9
        $parameter,
120
        &$variable,
121 9
        $data_type = \PDO::PARAM_STR,
122 9
        $length = null,
123 9
        $driver_options = null
124 9
    ) {
125 5
        $isBound = false;
126 5
        try {
127 5
            $data_type = $data_type === null ? \PDO::PARAM_STR : $data_type;
128 5
            $dataType = $this->getDriverDataType($data_type);
129 5
            $length = $length !== null ? $length : -1;
130 5
            $isBound = $this->statement->bindByName($parameter, $variable, $length, $dataType);
131 1
            if ($isBound) {
132 5
                $this->boundParameters[$parameter] = array(
133 9
                    'name'     => is_int($parameter) ? '' : $parameter,
134
                    'position' => strrpos($this->sqlText, $parameter),
135
                    'value'    => &$variable,
136
                    'type'     => $data_type,
137 9
                );
138
            }
139
        } 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
        return $isBound;
144
    }
145
146
    /**
147
     * @param string $parameter
148 4
     * @param $value
149
     * @param int $data_type
150 4
     *
151
     * @link http://php.net/manual/en/pdostatement.bindvalue.php
152
     * @return bool
153
     */
154
    public function bindValue($parameter, $value, $data_type = \PDO::PARAM_STR)
155
    {
156
        return $this->bindParam($parameter, $value, $data_type);
157 1
    }
158
159 1
    /**
160
     * @link http://php.net/manual/en/pdostatement.closecursor.php
161
     * @return bool
162
     */
163
    public function closeCursor()
164
    {
165
        return $this->statement->cancel();
166 1
    }
167
168 1
    /**
169
     * @link http://php.net/manual/en/pdostatement.columncount.php
170
     * @return int
171
     */
172
    public function columnCount()
173
    {
174 2
        return 0;
175
    }
176 2
177 2
    /**
178 2
     * @link http://php.net/manual/en/pdostatement.debugdumpparams.php
179 2
     */
180 2
    public function debugDumpParams()
181
    {
182
        $sqlText = $this->sqlText;
183
        $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 2
            }
189 2
190 2
            return $a['position'] < $b['position'] ? -1 : 1;
191
        });
192 2
        $parametersCount = count($parameters);
193 2
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
            $name = $parameter['name'];
201
            $index = $key + 1;
202 2
            $dataType = $parameter['type'];
203
204 2
            echo PHP_EOL;
205 2
            if ($name === '') {
206 2
                echo "Key: Position #{$position}:". PHP_EOL;
207 2
            } else {
208 2
                echo "Key: Name: [{$nameLength}] {$name}". PHP_EOL;
209 2
            }
210
            echo "paramno={$index}". PHP_EOL .
211
                "name=[{$nameLength}]{$name}". PHP_EOL .
212
                "is_param=1". PHP_EOL .
213
                "param_type={$dataType}";
214
        }
215 2
    }
216
217 2
    /**
218 2
     * @link http://php.net/manual/en/pdostatement.errorcode.php
219 1
     * @return string
220
     */
221 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 1
    {
223 1
        $driverError = $this->statement->getError();
224
        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
        $error = $this->errorInfo();
229
        $sqlStateErrorCode = $error[0];
230
231
        return $sqlStateErrorCode;
232 3
    }
233
234 3
    /**
235 3
     * @link http://php.net/manual/en/pdo.errorinfo.php
236 2
     * @return array
237 2
     */
238 2
    public function errorInfo()
239 1
    {
240 1
        $driverError = $this->statement->getError();
241
        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
            $driverErrorMessage = $driverError['message'];
243 3
            $driverErrorCode = $driverError['code'];
244
        } else {
245 3
            $driverErrorMessage = null;
246 3
            $driverErrorCode = null;
247
        }
248 3
249
        $sqlStateErrorCode = OracleSqlStateCode::getSqlStateErrorCode((int)$driverErrorCode);
250 3
        $error = array(
251
            $sqlStateErrorCode,
252
            $driverErrorCode,
253
            $driverErrorMessage
254
        );
255
256
        return $error;
257
    }
258
259 26
    /**
260
     * @param array $input_parameters
261
     *
262 26
     * @link http://php.net/manual/en/pdostatement.execute.php
263 1
     * @return bool
264
     */
265
    public function execute($input_parameters = array())
266 1
    {
267
        try {
268 1
            foreach ($input_parameters as $key => $value) {
269 26
                if (is_int($key)) {
270
                    $parameterName = $key + 1;
271 26
                } else {
272 26
                    $parameterName = $key;
273 26
                }
274
                $this->bindValue($parameterName, $value);
275
            }
276
277 26
            if ($this->getAttribute(\PDO::ATTR_AUTOCOMMIT)) {
278
                $isCommitOnSuccess = OCI_NO_AUTO_COMMIT;
279 23
            } else {
280 3
                $isCommitOnSuccess = OCI_COMMIT_ON_SUCCESS;
281
            }
282 3
283
            $result = $this->statement->execute($isCommitOnSuccess);
284
285 3
            return $result;
286
        } catch (\Exception $ex) {
287
            //TODO Handle Exception
288
            new PdoOci8Exception($ex->getMessage(), $ex->getCode(), $ex);
289
        }
290
291
        return false;
292
    }
293
294
    /**
295
     * @param int $fetch_style
296 13
     * @param int $cursor_orientation
297
     * @param int $cursor_offset
298
     *
299
     * @link http://php.net/manual/en/pdostatement.fetch.php
300
     * @return mixed
301 13
     */
302
    public function fetch(
303
        $fetch_style = \PDO::ATTR_DEFAULT_FETCH_MODE,
304
        $cursor_orientation = \PDO::FETCH_ORI_NEXT,
305
        $cursor_offset = 0
306
    ) {
307
        if ($fetch_style === null) {
308
            $fetch_style = \PDO::ATTR_DEFAULT_FETCH_MODE;
309
        }
310
311 13
        try {
312 3
            if ($fetch_style === \PDO::ATTR_DEFAULT_FETCH_MODE) {
313 3
                $fetch_style = $this->getAttribute(\PDO::ATTR_DEFAULT_FETCH_MODE);
314 10
            }
315
316
            switch ($fetch_style) {
317 1
                case \PDO::FETCH_ASSOC:
318 9
                    $mode = OCI_ASSOC;
319
                    break;
320
                case \PDO::FETCH_BOUND:
321
                    // returns TRUE and assigns the values of the columns in your result set to the PHP
322 9
                    // variables to which they were bound with the PDOStatement::bindColumn() method
323
                    return $this->statement->fetch();
324
                case \PDO::FETCH_CLASS:
325 9
                    // 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 9
                    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 9
                    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
                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 9
                    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
                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 9
                    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 4
                    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 4
                case \PDO::FETCH_LAZY:
342 5
                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 1
                    break;
346
                case \PDO::FETCH_NAMED:
347 4
                    // returns an array with the same form as PDO::FETCH_ASSOC, except that if there are
348 4
                    // multiple columns with the same name, the value referred to by that key will be an
349 4
                    // array of all the values in the row that had that column name
350 4
                    break;
351 4
                case \PDO::FETCH_NUM:
352
                    $mode = OCI_NUM;
353
                    break;
354
                case \PDO::FETCH_OBJ:
355 11
                    // 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
                case \PDO::FETCH_BOTH:
360
                default:
361
                    $mode = OCI_BOTH;
362
                    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
            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 6
372
        return false;
373
    }
374
375 6
    /**
376 1
     * @param int $fetch_style
377 1
     * @param mixed $fetch_argument
378 5
     * @param array $ctor_args
379 5
     *
380 5
     * @link http://php.net/manual/en/pdostatement.fetchall.php
381 5
     * @return array
382 5
     */
383 5
    public function fetchAll($fetch_style = null, $fetch_argument = null, $ctor_args = array())
384 5
    {
385 5
        // TODO Implement properly (use all other fetch modes)
386 5
        switch ($fetch_style) {
387 5
            case \PDO::FETCH_NUM:
388 5
                $mode = OCI_NUM;
389 5
                break;
390 5
            case \PDO::FETCH_OBJ:
391
            case \PDO::FETCH_INTO:
392
            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
            case \PDO::FETCH_FUNC:
398 6
            case \PDO::FETCH_GROUP:
399
            case \PDO::FETCH_KEY_PAIR:
400 6
            case \PDO::FETCH_LAZY:
401
            case \PDO::FETCH_NAMED:
402
            case \PDO::FETCH_UNIQUE:
403
                throw new PdoOci8Exception('Not implemented.');
404
                //break;
405
            case \PDO::FETCH_ASSOC:
406
            default:
407
                $mode = OCI_ASSOC;
408
        }
409 3
410
        $this->statement->fetchAll($rows, 0, -1, OCI_FETCHSTATEMENT_BY_ROW | $mode);
411 3
412 3
        return $rows;
413 1
    }
414
415
    /**
416 3
     * @param int $column_number
417 2
     *
418
     * @link http://php.net/manual/en/pdostatement.fetchcolumn.php
419
     * @return mixed
420
     */
421 1
    public function fetchColumn($column_number = 0)
422
    {
423
        $row = $this->fetch(\PDO::FETCH_NUM);
424
        if ($row === false) {
425
            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
        if (array_key_exists($column_number, $row)) {
429
            return $row[$column_number];
430
        }
431
432 2
        // TODO Throw Exception
433
        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 2
    }
435 2
436
437
    /**
438
     * @param string $class_name
439 2
     * @param array $ctor_args
440 2
     *
441
     * @link http://php.net/manual/en/pdostatement.fetchobject.php
442 2
     * @return bool|object
443 2
     */
444 2
    public function fetchObject($class_name = 'stdClass', $ctor_args = array())
445
    {
446 2
        $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 29
    protected function fetchInto($instance = null){
456
        $row = $this->fetch(\PDO::FETCH_ASSOC);
457 29
        if ($row === false) {
458 29
            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 4
            }
473
        }
474 4
475 4
        return $instance;
476 1
    }
477
478
    /**
479 3
     * @param int $attribute
480
     *
481 3
     * @link http://php.net/manual/en/pdostatement.getattribute.php
482 3
     * @return mixed
483 3
     */
484 3
    public function getAttribute($attribute)
485
    {
486 3
        if (array_key_exists($attribute, $this->options)) {
487
            return $this->options[$attribute];
488 3
        }
489 3
490
        return null;
491
    }
492
493
    /**
494 3
     * @param int $column
495 3
     *
496 3
     * @throws PdoOci8Exception
497 3
     *
498 3
     * @link http://php.net/manual/en/pdostatement.getcolumnmeta.php
499 3
     * @return bool|array
500 3
     */
501 3
    public function getColumnMeta($column)
502 3
    {
503
        $statementType = $this->statement->getType();
504
        if ($statementType !== 'SELECT') {
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
        $table = $this->getTableName();
509
510
        $sqlText = $this->sqlText;
511
        $statement = $this->getConnection()->parse($sqlText);
512
        $statement->execute(OCI_DESCRIBE_ONLY);
513
        $field = $statement->getField($column + 1);
514
515
        if ($field instanceof Oci8FieldInterface) {
516
            // Oracle returns attributes in upper case by default
517
            $fieldName = $field->getName();
518
            if ($this->getAttribute(\PDO::ATTR_CASE) === \PDO::CASE_LOWER) {
519
                $fieldName = strtolower($fieldName);
520
            }
521 8
522
            return array(
523 8
                'native_type'      => $field->getRawType(),
524
                'driver:decl_type' => $field->getType(),
525
                'flags'            => array(),
526
                'name'             => $fieldName,
527
                'table'            => $table,
528
                'len'              => $field->getSize(),
529
                'precision'        => $field->getPrecision() - $field->getScale(),
530
                'pdo_type'         => $this->getPDODataType($field->getType()),
531
            );
532
        }
533
534 49
        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 49
    /**
538 49
     * @return Oci8ConnectionInterface
539
     */
540 49
    protected function getConnection()
541 49
    {
542 49
        return $this->connection;
543
    }
544
545 49
    /**
546
     * @param int $type
547 49
     * @throws PdoOci8Exception
548
     * @return int
549
     */
550
    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
        $dataType = null;
553
        switch ($dataType) {
554
            case \PDO::PARAM_BOOL:
555
                $dataType = SQLT_BOL;
556
                break;
557
            case \PDO::PARAM_INT:
558 1
                $dataType = SQLT_INT;
559
                break;
560 1
            case \PDO::PARAM_LOB:
561 1
                $dataType = SQLT_CLOB;
562 1
                break;
563 1
            case \PDO::PARAM_STMT:
564 1
                throw new PdoOci8Exception('Parameter type \PDO::PARAM_STMT is not currently supported.');
565
            case \PDO::PARAM_NULL:
566 1
            case \PDO::PARAM_STR:
567
                $dataType = SQLT_CHR;
568
                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 3
            case \PDO::PARAM_INPUT_OUTPUT | \PDO::PARAM_LOB:
573
            case \PDO::PARAM_INPUT_OUTPUT | \PDO::PARAM_STMT:
574 3
            case \PDO::PARAM_INPUT_OUTPUT | \PDO::PARAM_NULL:
575 3
            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 3
        return $dataType;
580 3
    }
581 3
582 3
    /**
583
     * @param string $className
584 3
     * @param array $args
585 1
     * @return object
586
     */
587
    protected function getFetchClassInstance($className = 'stdClass', $args = array())
588 2
    {
589 2
        $reflexionClass = new \ReflectionClass($className);
590 1
        $instance       = $reflexionClass->newInstanceArgs($args);
591 1
592
        return $instance;
593 2
    }
594
595
    /**
596
     * @return string|object
597
     */
598
    protected function getFetchTarget()
599
    {
600
        return $this->fetchTarget;
601 3
    }
602
603
    /**
604 3
     * @return array
605
     */
606 3
    protected function getFetchTargetConstructorArgs()
607 1
    {
608 1
        return $this->fetchTargetConstructorArgs;
609 2
    }
610 2
611 2
    /**
612 2
     * @return \Traversable
613 2
     */
614
    protected function getInternalIterator()
615
    {
616
        if ($this->iterator instanceof \Traversable) {
617
            return $this->iterator;
618
        }
619
620
        $rows = $this->fetchAll();
621
        if ($rows === false) {
622
            //TODO Throw Exception?
623
            $rows = array();
624 3
        }
625
626
        $this->iterator = new \ArrayIterator($rows);
627
628
        return $this->iterator;
629
    }
630
631
    /**
632 11
     * @param string $data_type The data type name
633
     *
634 11
     * @return int
635
     */
636 11
    protected function getPDODataType($data_type)
637
    {
638
        //TODO Add all oracle data types
639 11
        $pdoDataType = \PDO::PARAM_STR;
640
        switch ($data_type) {
641
            case 'NUMBER':
642 11
                $pdoDataType = \PDO::PARAM_INT;
643
                break;
644
            case 'CHAR':
645 11
            case 'VARCHAR2':
646
            case 'NVARCHAR2':
647 11
                $pdoDataType = \PDO::PARAM_STR;
648 11
                break;
649 11
            case 'LOB':
650 11
            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
        return $pdoDataType;
660
    }
661 11
662
    /**
663
     * @return string
664
     */
665
    protected function getTableName()
666
    {
667 3
        $statementType = $this->statement->getType();
668
        if ($statementType !== 'SELECT') {
669 3
            return '';
670
        }
671
672
        $sqlText = strtoupper($this->sqlText);
673
        $idx     = strpos($sqlText, ' FROM ');
674
        $table   = substr($this->sqlText, $idx + 6);
675 1
        $table   = trim($table);
676
677 1
        if (strpos($table, '(') !== false) {
678
            return '';
679
        }
680
681 1
        $idxSpace = strpos($table, ' ');
682 1
        if ($idxSpace !== false) {
683
            $table = substr($table, 0, $idxSpace);
684
        }
685
686
        return $table;
687 1
    }
688
689 1
    /**
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
    public function rowCount()
703
    {
704
        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
    public function setAttribute($attribute, $value)
715
    {
716
        $readOnlyAttributes = array(
717
            \PDO::ATTR_AUTOCOMMIT,
718
        );
719
720 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
            !array_key_exists($attribute, $this->options)) {
722 1
            return false;
723
        }
724 1
725
        $this->options[$attribute] = $value;
726 1
727
        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
    public function setFetchMode($mode, $target = null, $ctor_args = array())
739
    {
740
        $isSuccess = $this->setAttribute(\PDO::ATTR_DEFAULT_FETCH_MODE, $mode);
741
        if ($isSuccess) {
742
            $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
            $this->setFetchTargetConstructorArgs($ctor_args);
744
        }
745
746
        return $isSuccess;
747
    }
748
749
    /**
750
     * @param string|object $target
751
     */
752
    protected function setFetchTarget($target)
753
    {
754
        $this->fetchTarget = $target;
755
    }
756
757
    /**
758
     * @param array $args
759
     */
760
    protected function setFetchTargetConstructorArgs($args)
761
    {
762
        $this->fetchTargetConstructorArgs = $args;
763
    }
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
    public function valid()
796
    {
797
        $iterator = $this->getInternalIterator();
798
799
        return $iterator->valid();
800
    }
801
802
    public function rewind()
803
    {
804
        $iterator = $this->getInternalIterator();
805
806
        $iterator->rewind();
807
    }
808
}
809