Completed
Push — master ( bc5307...59c2a6 )
by Arjay
10:42
created

Oci8Connection::causedByLostConnection()   B

Complexity

Conditions 5
Paths 9

Size

Total Lines 31
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 30

Importance

Changes 0
Metric Value
cc 5
eloc 20
nc 9
nop 1
dl 0
loc 31
ccs 0
cts 0
cp 0
crap 30
rs 8.439
c 0
b 0
f 0
1
<?php
2
3
namespace Yajra\Oci8;
4
5
use PDO;
6
use Exception;
7
use PDOStatement;
8
use Illuminate\Support\Str;
9
use Yajra\Pdo\Oci8\Statement;
10
use Yajra\Oci8\Schema\Trigger;
11
use Yajra\Oci8\Schema\Sequence;
12
use Illuminate\Database\Grammar;
13
use Illuminate\Database\Connection;
14
use Doctrine\DBAL\Connection as DoctrineConnection;
15
use Yajra\Oci8\Query\OracleBuilder as QueryBuilder;
16
use Yajra\Oci8\Schema\OracleBuilder as SchemaBuilder;
17
use Doctrine\DBAL\Driver\OCI8\Driver as DoctrineDriver;
18
use Yajra\Oci8\Query\Grammars\OracleGrammar as QueryGrammar;
19
use Yajra\Oci8\Query\Processors\OracleProcessor as Processor;
20
use Yajra\Oci8\Schema\Grammars\OracleGrammar as SchemaGrammar;
21
22
class Oci8Connection extends Connection
23
{
24
    const RECONNECT_ERRORS = 'reconnect_errors';
25
26
    /**
27
     * @var string
28
     */
29
    protected $schema;
30
31
    /**
32
     * @var \Yajra\Oci8\Schema\Sequence
33
     */
34
    protected $sequence;
35
36
    /**
37
     * @var \Yajra\Oci8\Schema\Trigger
38
     */
39
    protected $trigger;
40
41
    /**
42
     * @param PDO|\Closure $pdo
43
     * @param string       $database
44
     * @param string       $tablePrefix
45
     * @param array        $config
46
     */
47
    public function __construct($pdo, $database = '', $tablePrefix = '', array $config = [])
48
    {
49
        parent::__construct($pdo, $database, $tablePrefix, $config);
50
        $this->sequence = new Sequence($this);
51
        $this->trigger  = new Trigger($this);
52
    }
53
54
    /**
55
     * Get current schema.
56
     *
57
     * @return string
58
     */
59
    public function getSchema()
60
    {
61
        return $this->schema;
62
    }
63
64
    /**
65
     * Set current schema.
66
     *
67
     * @param string $schema
68
     * @return $this
69
     */
70
    public function setSchema($schema)
71
    {
72
        $this->schema = $schema;
73
        $sessionVars  = [
74
            'CURRENT_SCHEMA' => $schema,
75
        ];
76
77
        return $this->setSessionVars($sessionVars);
78
    }
79
80
    /**
81
     * Update oracle session variables.
82
     *
83
     * @param array $sessionVars
84
     * @return $this
85
     */
86
    public function setSessionVars(array $sessionVars)
87
    {
88
        $vars = [];
89
        foreach ($sessionVars as $option => $value) {
90
            $vars[] = (strtoupper($option) == 'CURRENT_SCHEMA') ?
91
                "$option  = $value" :
92
                "$option  = '$value'";
93
        }
94
95
        if ($vars) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $vars 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...
96
            $sql = 'ALTER SESSION SET ' . implode(' ', $vars);
97
            $this->statement($sql);
98
        }
99
100
        return $this;
101
    }
102
103
    /**
104
     * Get sequence class.
105
     *
106
     * @return \Yajra\Oci8\Schema\Sequence
107
     */
108
    public function getSequence()
109
    {
110
        return $this->sequence;
111
    }
112
113
    /**
114
     * Set sequence class.
115
     *
116
     * @param \Yajra\Oci8\Schema\Sequence $sequence
117
     * @return \Yajra\Oci8\Schema\Sequence
118
     */
119
    public function setSequence(Sequence $sequence)
120
    {
121
        return $this->sequence = $sequence;
122
    }
123
124
    /**
125
     * Get oracle trigger class.
126
     *
127
     * @return \Yajra\Oci8\Schema\Trigger
128
     */
129
    public function getTrigger()
130
    {
131
        return $this->trigger;
132
    }
133
134
    /**
135
     * Set oracle trigger class.
136
     *
137
     * @param \Yajra\Oci8\Schema\Trigger $trigger
138
     * @return \Yajra\Oci8\Schema\Trigger
139
     */
140
    public function setTrigger(Trigger $trigger)
141
    {
142
        return $this->trigger = $trigger;
143
    }
144
145
    /**
146
     * Get a schema builder instance for the connection.
147
     *
148
     * @return \Yajra\Oci8\Schema\OracleBuilder
149
     */
150
    public function getSchemaBuilder()
151
    {
152
        if (is_null($this->schemaGrammar)) {
153
            $this->useDefaultSchemaGrammar();
154
        }
155
156
        return new SchemaBuilder($this);
157
    }
158
159
    /**
160
     * Begin a fluent query against a database table.
161
     *
162
     * @param string $table
163
     * @return \Yajra\Oci8\Query\OracleBuilder
164
     */
165
    public function table($table)
166
    {
167
        $processor = $this->getPostProcessor();
168
169
        $query = new QueryBuilder($this, $this->getQueryGrammar(), $processor);
170
171
        return $query->from($table);
172
    }
173
174
    /**
175
     * Set oracle session date format.
176
     *
177
     * @param string $format
178
     * @return $this
179
     */
180
    public function setDateFormat($format = 'YYYY-MM-DD HH24:MI:SS')
181
    {
182
        $sessionVars = [
183
            'NLS_DATE_FORMAT'      => $format,
184
            'NLS_TIMESTAMP_FORMAT' => $format,
185
        ];
186
187
        return $this->setSessionVars($sessionVars);
188
    }
189
190
    /**
191
     * Get doctrine connection.
192
     *
193
     * @return \Doctrine\DBAL\Connection
194
     */
195
    public function getDoctrineConnection()
196
    {
197
        if (is_null($this->doctrineConnection)) {
198
            $data                     = ['pdo' => $this->getPdo(), 'user' => $this->getConfig('username')];
199
            $this->doctrineConnection = new DoctrineConnection(
200
                $data,
201
                $this->getDoctrineDriver()
202
            );
203
        }
204
205
        return $this->doctrineConnection;
206
    }
207
208
    /**
209
     * Get doctrine driver.
210
     *
211
     * @return \Doctrine\DBAL\Driver\OCI8\Driver
212
     */
213
    protected function getDoctrineDriver()
214
    {
215
        return new DoctrineDriver();
216
    }
217
218
    /**
219
     * Execute a PL/SQL Function and return its value.
220
     * Usage: DB::executeFunction('function_name(:binding_1,:binding_n)', [':binding_1' => 'hi', ':binding_n' =>
221
     * 'bye'], PDO::PARAM_LOB).
222
     *
223
     * @param string $functionName
224
     * @param array  $bindings (kvp array)
225
     * @param int    $returnType (PDO::PARAM_*)
226
     * @param int    $length
227
     * @return mixed $returnType
228
     */
229
    public function executeFunction($functionName, array $bindings = [], $returnType = PDO::PARAM_STR, $length = null)
230
    {
231
        $stmt = $this->createStatementFromFunction($functionName, $bindings);
232
233
        $stmt = $this->addBindingsToStatement($stmt, $bindings);
234
235
        $stmt->bindParam(':result', $result, $returnType, $length);
236
        $stmt->execute();
237
238
        return $result;
239
    }
240
241
    /**
242
     * Execute a PL/SQL Procedure and return its results.
243
     *
244
     * Usage: DB::executeProcedure($procedureName, $bindings).
245
     * $bindings looks like:
246
     *         $bindings = [
247
     *                  'p_userid'  => $id
248
     *         ];
249
     *
250
     * @param  string $procedureName
251
     * @param  array  $bindings
252
     * @return bool
253
     */
254
    public function executeProcedure($procedureName, array $bindings = [])
255
    {
256
        $stmt = $this->createStatementFromProcedure($procedureName, $bindings);
257
258
        $stmt = $this->addBindingsToStatement($stmt, $bindings);
259
260
        return $stmt->execute();
261
    }
262
263
    /**
264
     * Execute a PL/SQL Procedure and return its cursor result.
265
     * Usage: DB::executeProcedureWithCursor($procedureName, $bindings).
266
     *
267
     * https://docs.oracle.com/cd/E17781_01/appdev.112/e18555/ch_six_ref_cur.htm#TDPPH218
268
     *
269
     * @param  string $procedureName
270
     * @param  array  $bindings
271
     * @param  string $cursorName
272
     * @return array
273
     */
274
    public function executeProcedureWithCursor($procedureName, array $bindings = [], $cursorName = ':cursor')
275
    {
276
        $stmt = $this->createStatementFromProcedure($procedureName, $bindings, $cursorName);
277
278
        $stmt = $this->addBindingsToStatement($stmt, $bindings);
279
280
        $cursor = null;
281
        $stmt->bindParam($cursorName, $cursor, PDO::PARAM_STMT);
282
        $stmt->execute();
283
284
        $statement = new Statement($cursor, $this->getPdo(), $this->getPdo()->getOptions());
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class PDO as the method getOptions() does only exist in the following sub-classes of PDO: Yajra\Pdo\Oci8. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
Compatibility introduced by
$this->getPdo() of type object<PDO> is not a sub-type of object<Yajra\Pdo\Oci8>. It seems like you assume a child class of the class PDO to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
285
        $statement->execute();
286
        $results = $statement->fetchAll(PDO::FETCH_OBJ);
287
        $statement->closeCursor();
288
289
        return $results;
290
    }
291
292
    /**
293
     * Creates sql command to run a procedure with bindings.
294
     *
295
     * @param  string      $procedureName
296
     * @param  array       $bindings
297
     * @param  string|bool $cursor
298
     * @return string
299
     */
300
    public function createSqlFromProcedure($procedureName, array $bindings, $cursor = false)
301
    {
302
        $paramsString = implode(',', array_map(function ($param) {
303
            return ':' . $param;
304
        }, array_keys($bindings)));
305
306
        $prefix = count($bindings) ? ',' : '';
307
        $cursor = $cursor ? $prefix . $cursor : null;
308
309
        return sprintf('begin %s(%s%s); end;', $procedureName, $paramsString, $cursor);
310
    }
311
312
    /**
313
     * Creates statement from procedure.
314
     *
315
     * @param  string      $procedureName
316
     * @param  array       $bindings
317
     * @param  string|bool $cursorName
318
     * @return PDOStatement
319
     */
320
    public function createStatementFromProcedure($procedureName, array $bindings, $cursorName = false)
321
    {
322
        $sql = $this->createSqlFromProcedure($procedureName, $bindings, $cursorName);
323
324
        return $this->getPdo()->prepare($sql);
325
    }
326
327
    /**
328
     * Create statement from function.
329
     *
330
     * @param string $functionName
331
     * @param array  $bindings
332
     * @return PDOStatement
333
     */
334
    public function createStatementFromFunction($functionName, array $bindings)
335
    {
336
        $bindings = $bindings ? ':' . implode(', :', array_keys($bindings)) : '';
337
338
        $sql = sprintf('begin :result := %s(%s); end;', $functionName, $bindings);
339
340
        return $this->getPdo()->prepare($sql);
341
    }
342
343
    /**
344
     * Bind values to their parameters in the given statement.
345
     *
346
     * @param PDOStatement $statement
347
     * @param array        $bindings
348
     */
349
    public function bindValues($statement, $bindings)
350
    {
351
        foreach ($bindings as $key => $value) {
352
            $statement->bindParam($key, $bindings[$key]);
353
        }
354
    }
355
356
    /**
357
     * Get the default query grammar instance.
358
     *
359
     * @return \Illuminate\Database\Grammar|\Yajra\Oci8\Query\Grammars\OracleGrammar
360
     */
361
    protected function getDefaultQueryGrammar()
362
    {
363
        return $this->withTablePrefix(new QueryGrammar());
364
    }
365
366
    /**
367
     * Set the table prefix and return the grammar.
368
     *
369
     * @param \Illuminate\Database\Grammar|\Yajra\Oci8\Query\Grammars\OracleGrammar|\Yajra\Oci8\Schema\Grammars\OracleGrammar $grammar
370
     * @return \Illuminate\Database\Grammar
371
     */
372
    public function withTablePrefix(Grammar $grammar)
373
    {
374
        return $this->withSchemaPrefix(parent::withTablePrefix($grammar));
375
    }
376
377
    /**
378
     * Set the schema prefix and return the grammar.
379
     *
380
     * @param \Illuminate\Database\Grammar|\Yajra\Oci8\Query\Grammars\OracleGrammar|\Yajra\Oci8\Schema\Grammars\OracleGrammar $grammar
381
     * @return \Illuminate\Database\Grammar
382
     */
383
    public function withSchemaPrefix(Grammar $grammar)
384
    {
385
        $grammar->setSchemaPrefix($this->getConfigSchemaPrefix());
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Illuminate\Database\Grammar as the method setSchemaPrefix() does only exist in the following sub-classes of Illuminate\Database\Grammar: Yajra\Oci8\Query\Grammars\OracleGrammar, Yajra\Oci8\Schema\Grammars\OracleGrammar. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
386
387
        return $grammar;
388
    }
389
390
    /**
391
     * Get config schema prefix.
392
     *
393
     * @return string
394
     */
395
    protected function getConfigSchemaPrefix()
396
    {
397
        return isset($this->config['prefix_schema']) ? $this->config['prefix_schema'] : '';
398
    }
399
400
    /**
401
     * Get the default schema grammar instance.
402
     *
403
     * @return \Illuminate\Database\Grammar|\Yajra\Oci8\Schema\Grammars\OracleGrammar
404
     */
405
    protected function getDefaultSchemaGrammar()
406
    {
407
        return $this->withTablePrefix(new SchemaGrammar());
408
    }
409
410
    /**
411
     * Get the default post processor instance.
412
     *
413
     * @return \Yajra\Oci8\Query\Processors\OracleProcessor
414
     */
415
    protected function getDefaultPostProcessor()
416
    {
417
        return new Processor();
418
    }
419
420
    /**
421
     * Add bindings to statement.
422
     *
423
     * @param  array        $bindings
424
     * @param  PDOStatement $stmt
425
     * @return PDOStatement
426
     */
427
    public function addBindingsToStatement(PDOStatement $stmt, array $bindings)
428
    {
429
        foreach ($bindings as $key => &$binding) {
430
431
            //defaults
432
            $value  = &$binding;
433
            $type   = PDO::PARAM_STR;
434
            $length = null;
435
436
            // extended array syntax for bindings
437
            if (is_array($binding)) {
438
                $value  = &$binding['value'];
439
                $type   = array_key_exists('type', $binding) ? $binding['type'] : PDO::PARAM_STR;
440
                $length = array_key_exists('length', $binding) ? $binding['length'] : null;
441
            }
442
443
            $stmt->bindParam(':' . $key, $value, $type, $length);
444
        }
445
446
        return $stmt;
447
    }
448
449
    /**
450
     * Determine if the given exception was caused by a lost connection.
451
     *
452
     * @param  \Exception  $e
453
     * @return bool
454
     */
455
    public function causedByLostConnection(Exception $e)
456
    {
457
        if (parent::causedByLostConnection($e)) {
458
            return true;
459
        }
460
461
        $lostConnectionErrors = [
462
            'ORA-03113',    //End-of-file on communication channel
463
            'ORA-03114',    //Not Connected to Oracle
464
            'ORA-03135',    //Connection lost contact
465
            'ORA-12170',    //Connect timeout occurred
466
            'ORA-12537',    //Connection closed
467
            'ORA-27146',    //Post/wait initialization failed
468
            'ORA-25408',    //Can not safely replay call
469
            'ORA-56600',    //Illegal Call
470
        ];
471
472
        $additionalErrors = null;
473
474
        $options = isset($this->config['options']) ? $this->config['options'] : [];
475
        if (array_key_exists(static::RECONNECT_ERRORS, $options)) {
476
            $additionalErrors = $this->config['options'][static::RECONNECT_ERRORS];
477
        }
478
479
        if (is_array($additionalErrors)) {
480
            $lostConnectionErrors = array_merge($lostConnectionErrors,
481
                $this->config['options'][static::RECONNECT_ERRORS]);
482
        }
483
484
        return Str::contains($e->getMessage(), $lostConnectionErrors);
485
    }
486
}
487