Completed
Pull Request — master (#505)
by Arjay
11:40
created

Oci8Connection   B

Complexity

Total Complexity 45

Size/Duplication

Total Lines 448
Duplicated Lines 0 %

Coupling/Cohesion

Components 6
Dependencies 10

Test Coverage

Coverage 0%

Importance

Changes 0
Metric Value
dl 0
loc 448
ccs 0
cts 102
cp 0
rs 8.8
c 0
b 0
f 0
wmc 45
lcom 6
cbo 10

27 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 6 1
A getSchema() 0 4 1
A setSchema() 0 9 1
A setSessionVars() 0 18 5
A getSequence() 0 4 1
A setSequence() 0 4 1
A getTrigger() 0 4 1
A setTrigger() 0 4 1
A getSchemaBuilder() 0 8 2
A query() 0 6 1
A setDateFormat() 0 9 1
A getDoctrineConnection() 0 12 2
A getDoctrineDriver() 0 4 1
A executeFunction() 0 11 1
A executeProcedure() 0 8 1
A executeProcedureWithCursor() 0 17 1
A createSqlFromProcedure() 0 11 3
A createStatementFromProcedure() 0 6 1
A createStatementFromFunction() 0 8 2
A getDefaultQueryGrammar() 0 4 1
A withTablePrefix() 0 4 1
A withSchemaPrefix() 0 6 1
A getConfigSchemaPrefix() 0 4 2
A getDefaultSchemaGrammar() 0 4 1
A getDefaultPostProcessor() 0 4 1
A addBindingsToStatement() 0 18 5
A causedByLostConnection() 0 31 5

How to fix   Complexity   

Complex Class

Complex classes like Oci8Connection often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Oci8Connection, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Yajra\Oci8;
4
5
use PDO;
6
use Exception;
7
use Throwable;
8
use PDOStatement;
9
use Illuminate\Support\Str;
10
use Yajra\Pdo\Oci8\Statement;
11
use Yajra\Oci8\Schema\Trigger;
12
use Yajra\Oci8\Schema\Sequence;
13
use Illuminate\Database\Grammar;
14
use Illuminate\Database\Connection;
15
use Doctrine\DBAL\Connection as DoctrineConnection;
16
use Yajra\Oci8\Query\OracleBuilder as QueryBuilder;
17
use Yajra\Oci8\Schema\OracleBuilder as SchemaBuilder;
18
use Doctrine\DBAL\Driver\OCI8\Driver as DoctrineDriver;
19
use Yajra\Oci8\Query\Grammars\OracleGrammar as QueryGrammar;
20
use Yajra\Oci8\Query\Processors\OracleProcessor as Processor;
21
use Yajra\Oci8\Schema\Grammars\OracleGrammar as SchemaGrammar;
22
23
class Oci8Connection extends Connection
24
{
25
    const RECONNECT_ERRORS = 'reconnect_errors';
26
27
    /**
28
     * @var string
29
     */
30
    protected $schema;
31
32
    /**
33
     * @var \Yajra\Oci8\Schema\Sequence
34
     */
35
    protected $sequence;
36
37
    /**
38
     * @var \Yajra\Oci8\Schema\Trigger
39
     */
40
    protected $trigger;
41
42
    /**
43
     * @param PDO|\Closure $pdo
44
     * @param string       $database
45
     * @param string       $tablePrefix
46
     * @param array        $config
47
     */
48
    public function __construct($pdo, $database = '', $tablePrefix = '', array $config = [])
49
    {
50
        parent::__construct($pdo, $database, $tablePrefix, $config);
51
        $this->sequence = new Sequence($this);
52
        $this->trigger  = new Trigger($this);
53
    }
54
55
    /**
56
     * Get current schema.
57
     *
58
     * @return string
59
     */
60
    public function getSchema()
61
    {
62
        return $this->schema;
63
    }
64
65
    /**
66
     * Set current schema.
67
     *
68
     * @param string $schema
69
     * @return $this
70
     */
71
    public function setSchema($schema)
72
    {
73
        $this->schema = $schema;
74
        $sessionVars  = [
75
            'CURRENT_SCHEMA' => $schema,
76
        ];
77
78
        return $this->setSessionVars($sessionVars);
79
    }
80
81
    /**
82
     * Update oracle session variables.
83
     *
84
     * @param array $sessionVars
85
     * @return $this
86
     */
87
    public function setSessionVars(array $sessionVars)
88
    {
89
        $vars = [];
90
        foreach ($sessionVars as $option => $value) {
91
            if (strtoupper($option) == 'CURRENT_SCHEMA' || strtoupper($option) == 'EDITION') {
92
                $vars[] = "$option  = $value";
93
            } else {
94
                $vars[] = "$option  = '$value'";
95
            }
96
        }
97
98
        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...
99
            $sql = 'ALTER SESSION SET ' . implode(' ', $vars);
100
            $this->statement($sql);
101
        }
102
103
        return $this;
104
    }
105
106
    /**
107
     * Get sequence class.
108
     *
109
     * @return \Yajra\Oci8\Schema\Sequence
110
     */
111
    public function getSequence()
112
    {
113
        return $this->sequence;
114
    }
115
116
    /**
117
     * Set sequence class.
118
     *
119
     * @param \Yajra\Oci8\Schema\Sequence $sequence
120
     * @return \Yajra\Oci8\Schema\Sequence
121
     */
122
    public function setSequence(Sequence $sequence)
123
    {
124
        return $this->sequence = $sequence;
125
    }
126
127
    /**
128
     * Get oracle trigger class.
129
     *
130
     * @return \Yajra\Oci8\Schema\Trigger
131
     */
132
    public function getTrigger()
133
    {
134
        return $this->trigger;
135
    }
136
137
    /**
138
     * Set oracle trigger class.
139
     *
140
     * @param \Yajra\Oci8\Schema\Trigger $trigger
141
     * @return \Yajra\Oci8\Schema\Trigger
142
     */
143
    public function setTrigger(Trigger $trigger)
144
    {
145
        return $this->trigger = $trigger;
146
    }
147
148
    /**
149
     * Get a schema builder instance for the connection.
150
     *
151
     * @return \Yajra\Oci8\Schema\OracleBuilder
152
     */
153
    public function getSchemaBuilder()
154
    {
155
        if (is_null($this->schemaGrammar)) {
156
            $this->useDefaultSchemaGrammar();
157
        }
158
159
        return new SchemaBuilder($this);
160
    }
161
162
    /**
163
     * Get a new query builder instance.
164
     *
165
     * @return \Illuminate\Database\Query\Builder
166
     */
167
    public function query()
168
    {
169
        return new QueryBuilder(
170
            $this, $this->getQueryGrammar(), $this->getPostProcessor()
171
        );
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
     * Get the default query grammar instance.
345
     *
346
     * @return \Illuminate\Database\Grammar|\Yajra\Oci8\Query\Grammars\OracleGrammar
347
     */
348
    protected function getDefaultQueryGrammar()
349
    {
350
        return $this->withTablePrefix(new QueryGrammar());
351
    }
352
353
    /**
354
     * Set the table prefix and return the grammar.
355
     *
356
     * @param \Illuminate\Database\Grammar|\Yajra\Oci8\Query\Grammars\OracleGrammar|\Yajra\Oci8\Schema\Grammars\OracleGrammar $grammar
357
     * @return \Illuminate\Database\Grammar
358
     */
359
    public function withTablePrefix(Grammar $grammar)
360
    {
361
        return $this->withSchemaPrefix(parent::withTablePrefix($grammar));
362
    }
363
364
    /**
365
     * Set the schema prefix and return the grammar.
366
     *
367
     * @param \Illuminate\Database\Grammar|\Yajra\Oci8\Query\Grammars\OracleGrammar|\Yajra\Oci8\Schema\Grammars\OracleGrammar $grammar
368
     * @return \Illuminate\Database\Grammar
369
     */
370
    public function withSchemaPrefix(Grammar $grammar)
371
    {
372
        $grammar->setSchemaPrefix($this->getConfigSchemaPrefix());
373
374
        return $grammar;
375
    }
376
377
    /**
378
     * Get config schema prefix.
379
     *
380
     * @return string
381
     */
382
    protected function getConfigSchemaPrefix()
383
    {
384
        return isset($this->config['prefix_schema']) ? $this->config['prefix_schema'] : '';
385
    }
386
387
    /**
388
     * Get the default schema grammar instance.
389
     *
390
     * @return \Illuminate\Database\Grammar|\Yajra\Oci8\Schema\Grammars\OracleGrammar
391
     */
392
    protected function getDefaultSchemaGrammar()
393
    {
394
        return $this->withTablePrefix(new SchemaGrammar());
395
    }
396
397
    /**
398
     * Get the default post processor instance.
399
     *
400
     * @return \Yajra\Oci8\Query\Processors\OracleProcessor
401
     */
402
    protected function getDefaultPostProcessor()
403
    {
404
        return new Processor();
405
    }
406
407
    /**
408
     * Add bindings to statement.
409
     *
410
     * @param  array        $bindings
411
     * @param  PDOStatement $stmt
412
     * @return PDOStatement
413
     */
414
    public function addBindingsToStatement(PDOStatement $stmt, array $bindings)
415
    {
416
        foreach ($bindings as $key => &$binding) {
417
            $value  = &$binding;
418
            $type   = PDO::PARAM_STR;
419
            $length = -1;
420
421
            if (is_array($binding)) {
422
                $value  = &$binding['value'];
423
                $type   = array_key_exists('type', $binding) ? $binding['type'] : PDO::PARAM_STR;
424
                $length = array_key_exists('length', $binding) ? $binding['length'] : -1;
425
            }
426
427
            $stmt->bindParam(':' . $key, $value, $type, $length);
428
        }
429
430
        return $stmt;
431
    }
432
433
    /**
434
     * Determine if the given exception was caused by a lost connection.
435
     *
436
     * @param  \Exception  $e
437
     * @return bool
438
     */
439
    protected function causedByLostConnection(Throwable $e)
440
    {
441
        if (parent::causedByLostConnection($e)) {
442
            return true;
443
        }
444
445
        $lostConnectionErrors = [
446
            'ORA-03113',    //End-of-file on communication channel
447
            'ORA-03114',    //Not Connected to Oracle
448
            'ORA-03135',    //Connection lost contact
449
            'ORA-12170',    //Connect timeout occurred
450
            'ORA-12537',    //Connection closed
451
            'ORA-27146',    //Post/wait initialization failed
452
            'ORA-25408',    //Can not safely replay call
453
            'ORA-56600',    //Illegal Call
454
        ];
455
456
        $additionalErrors = null;
457
458
        $options = isset($this->config['options']) ? $this->config['options'] : [];
459
        if (array_key_exists(static::RECONNECT_ERRORS, $options)) {
460
            $additionalErrors = $this->config['options'][static::RECONNECT_ERRORS];
461
        }
462
463
        if (is_array($additionalErrors)) {
464
            $lostConnectionErrors = array_merge($lostConnectionErrors,
465
                $this->config['options'][static::RECONNECT_ERRORS]);
466
        }
467
468
        return Str::contains($e->getMessage(), $lostConnectionErrors);
469
    }
470
}
471