Completed
Pull Request — master (#562)
by Arjay
15:38
created

Oci8Connection::getDefaultSchemaGrammar()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 0
dl 0
loc 4
ccs 0
cts 0
cp 0
crap 2
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace Yajra\Oci8;
4
5
use Doctrine\DBAL\Connection as DoctrineConnection;
6
use Doctrine\DBAL\Driver\OCI8\Driver as DoctrineDriver;
7
use Exception;
8
use Illuminate\Database\Connection;
9
use Illuminate\Database\Grammar;
10
use Illuminate\Support\Str;
11
use PDO;
12
use PDOStatement;
13
use Throwable;
14
use Yajra\Oci8\Query\Grammars\OracleGrammar as QueryGrammar;
15
use Yajra\Oci8\Query\OracleBuilder as QueryBuilder;
16
use Yajra\Oci8\Query\Processors\OracleProcessor as Processor;
17
use Yajra\Oci8\Schema\Grammars\OracleGrammar as SchemaGrammar;
18
use Yajra\Oci8\Schema\OracleBuilder as SchemaBuilder;
19
use Yajra\Oci8\Schema\Sequence;
20
use Yajra\Oci8\Schema\Trigger;
21
use Yajra\Pdo\Oci8\Statement;
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
     * Bind values to their parameters in the given statement.
345
     *
346
     * @param  \PDOStatement  $statement
347
     * @param  array  $bindings
348
     * @return void
349
     */
350
    public function bindValues($statement, $bindings)
351
    {
352
        foreach ($bindings as $key => $value) {
353
            $statement->bindValue(
354
                is_string($key) ? $key : $key + 1, $value,
355
                is_int($value) || is_null($value) ? PDO::PARAM_INT : PDO::PARAM_STR
356
            );
357
        }
358
    }
359
360
    /**
361
     * Get the default query grammar instance.
362
     *
363
     * @return \Illuminate\Database\Grammar|\Yajra\Oci8\Query\Grammars\OracleGrammar
364
     */
365
    protected function getDefaultQueryGrammar()
366
    {
367
        return $this->withTablePrefix(new QueryGrammar());
368
    }
369
370
    /**
371
     * Set the table prefix and return the grammar.
372
     *
373
     * @param \Illuminate\Database\Grammar|\Yajra\Oci8\Query\Grammars\OracleGrammar|\Yajra\Oci8\Schema\Grammars\OracleGrammar $grammar
374
     * @return \Illuminate\Database\Grammar
375
     */
376
    public function withTablePrefix(Grammar $grammar)
377
    {
378
        return $this->withSchemaPrefix(parent::withTablePrefix($grammar));
379
    }
380
381
    /**
382
     * Set the schema prefix and return the grammar.
383
     *
384
     * @param \Illuminate\Database\Grammar|\Yajra\Oci8\Query\Grammars\OracleGrammar|\Yajra\Oci8\Schema\Grammars\OracleGrammar $grammar
385
     * @return \Illuminate\Database\Grammar
386
     */
387
    public function withSchemaPrefix(Grammar $grammar)
388
    {
389
        $grammar->setSchemaPrefix($this->getConfigSchemaPrefix());
390
391
        return $grammar;
392
    }
393
394
    /**
395
     * Get config schema prefix.
396
     *
397
     * @return string
398
     */
399
    protected function getConfigSchemaPrefix()
400
    {
401
        return isset($this->config['prefix_schema']) ? $this->config['prefix_schema'] : '';
402
    }
403
404
    /**
405
     * Get the default schema grammar instance.
406
     *
407
     * @return \Illuminate\Database\Grammar|\Yajra\Oci8\Schema\Grammars\OracleGrammar
408
     */
409
    protected function getDefaultSchemaGrammar()
410
    {
411
        return $this->withTablePrefix(new SchemaGrammar());
412
    }
413
414
    /**
415
     * Get the default post processor instance.
416
     *
417
     * @return \Yajra\Oci8\Query\Processors\OracleProcessor
418
     */
419
    protected function getDefaultPostProcessor()
420
    {
421
        return new Processor();
422
    }
423
424
    /**
425
     * Add bindings to statement.
426
     *
427
     * @param  array        $bindings
428
     * @param  PDOStatement $stmt
429
     * @return PDOStatement
430
     */
431
    public function addBindingsToStatement(PDOStatement $stmt, array $bindings)
432
    {
433
        foreach ($bindings as $key => &$binding) {
434
            $value  = &$binding;
435
            $type   = PDO::PARAM_STR;
436
            $length = -1;
437
438
            if (is_array($binding)) {
439
                $value  = &$binding['value'];
440
                $type   = array_key_exists('type', $binding) ? $binding['type'] : PDO::PARAM_STR;
441
                $length = array_key_exists('length', $binding) ? $binding['length'] : -1;
442
            }
443
444
            $stmt->bindParam(':' . $key, $value, $type, $length);
445
        }
446
447
        return $stmt;
448
    }
449
450
    /**
451
     * Determine if the given exception was caused by a lost connection.
452
     *
453
     * @param  \Exception  $e
454
     * @return bool
455
     */
456
    protected function causedByLostConnection(Throwable $e)
457
    {
458
        if (parent::causedByLostConnection($e)) {
459
            return true;
460
        }
461
462
        $lostConnectionErrors = [
463
            'ORA-03113',    //End-of-file on communication channel
464
            'ORA-03114',    //Not Connected to Oracle
465
            'ORA-03135',    //Connection lost contact
466
            'ORA-12170',    //Connect timeout occurred
467
            'ORA-12537',    //Connection closed
468
            'ORA-27146',    //Post/wait initialization failed
469
            'ORA-25408',    //Can not safely replay call
470
            'ORA-56600',    //Illegal Call
471
        ];
472
473
        $additionalErrors = null;
474
475
        $options = isset($this->config['options']) ? $this->config['options'] : [];
476
        if (array_key_exists(static::RECONNECT_ERRORS, $options)) {
477
            $additionalErrors = $this->config['options'][static::RECONNECT_ERRORS];
478
        }
479
480
        if (is_array($additionalErrors)) {
481
            $lostConnectionErrors = array_merge($lostConnectionErrors,
482
                $this->config['options'][static::RECONNECT_ERRORS]);
483
        }
484
485
        return Str::contains($e->getMessage(), $lostConnectionErrors);
486
    }
487
}
488