Completed
Push — master ( 69dbe8...baa4bd )
by Arjay
11:50
created

Oci8Connection::createStatementFromFunction()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
cc 2
eloc 4
nc 2
nop 2
dl 0
loc 8
ccs 0
cts 1
cp 0
crap 6
rs 9.4285
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 Illuminate\Database\Connection;
8
use Illuminate\Database\Grammar;
9
use PDO;
10
use PDOStatement;
11
use Yajra\Oci8\Query\Grammars\OracleGrammar as QueryGrammar;
12
use Yajra\Oci8\Query\OracleBuilder as QueryBuilder;
13
use Yajra\Oci8\Query\Processors\OracleProcessor as Processor;
14
use Yajra\Oci8\Schema\Grammars\OracleGrammar as SchemaGrammar;
15
use Yajra\Oci8\Schema\OracleBuilder as SchemaBuilder;
16
use Yajra\Oci8\Schema\Sequence;
17
use Yajra\Oci8\Schema\Trigger;
18
use Yajra\Pdo\Oci8\Statement;
19
20
class Oci8Connection extends Connection
21
{
22
    /**
23
     * @var string
24
     */
25
    protected $schema;
26
27
    /**
28
     * @var \Yajra\Oci8\Schema\Sequence
29
     */
30
    protected $sequence;
31
32
    /**
33
     * @var \Yajra\Oci8\Schema\Trigger
34
     */
35
    protected $trigger;
36
37
    /**
38
     * @param PDO|\Closure $pdo
39
     * @param string $database
40
     * @param string $tablePrefix
41
     * @param array $config
42
     */
43
    public function __construct($pdo, $database = '', $tablePrefix = '', array $config = [])
44
    {
45
        parent::__construct($pdo, $database, $tablePrefix, $config);
46
        $this->sequence = new Sequence($this);
47
        $this->trigger  = new Trigger($this);
48
    }
49
50
    /**
51
     * Get current schema.
52
     *
53
     * @return string
54
     */
55
    public function getSchema()
56
    {
57
        return $this->schema;
58
    }
59
60
    /**
61
     * Set current schema.
62
     *
63
     * @param string $schema
64
     * @return $this
65
     */
66
    public function setSchema($schema)
67
    {
68
        $this->schema = $schema;
69
        $sessionVars  = [
70
            'CURRENT_SCHEMA' => $schema,
71
        ];
72
73
        return $this->setSessionVars($sessionVars);
74
    }
75
76
    /**
77
     * Update oracle session variables.
78
     *
79
     * @param array $sessionVars
80
     * @return $this
81
     */
82
    public function setSessionVars(array $sessionVars)
83
    {
84
        $vars = [];
85
        foreach ($sessionVars as $option => $value) {
86
            if (strtoupper($option) == 'CURRENT_SCHEMA') {
87
                $vars[] = "$option  = $value";
88
            } else {
89
                $vars[] = "$option  = '$value'";
90
            }
91
        }
92
        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...
93
            $sql = 'ALTER SESSION SET ' . implode(' ', $vars);
94
            $this->statement($sql);
95
        }
96
97
        return $this;
98
    }
99
100
    /**
101
     * Get sequence class.
102
     *
103
     * @return \Yajra\Oci8\Schema\Sequence
104
     */
105
    public function getSequence()
106
    {
107
        return $this->sequence;
108
    }
109
110
    /**
111
     * Set sequence class.
112
     *
113
     * @param \Yajra\Oci8\Schema\Sequence $sequence
114
     * @return \Yajra\Oci8\Schema\Sequence
115
     */
116
    public function setSequence(Sequence $sequence)
117
    {
118
        return $this->sequence = $sequence;
119
    }
120
121
    /**
122
     * Get oracle trigger class.
123
     *
124
     * @return \Yajra\Oci8\Schema\Trigger
125
     */
126
    public function getTrigger()
127
    {
128
        return $this->trigger;
129
    }
130
131
    /**
132
     * Set oracle trigger class.
133
     *
134
     * @param \Yajra\Oci8\Schema\Trigger $trigger
135
     * @return \Yajra\Oci8\Schema\Trigger
136
     */
137
    public function setTrigger(Trigger $trigger)
138
    {
139
        return $this->trigger = $trigger;
140
    }
141
142
    /**
143
     * Get a schema builder instance for the connection.
144
     *
145
     * @return \Yajra\Oci8\Schema\OracleBuilder
146
     */
147
    public function getSchemaBuilder()
148
    {
149
        if (is_null($this->schemaGrammar)) {
150
            $this->useDefaultSchemaGrammar();
151
        }
152
153
        return new SchemaBuilder($this);
154
    }
155
156
    /**
157
     * Begin a fluent query against a database table.
158
     *
159
     * @param string $table
160
     * @return \Yajra\Oci8\Query\OracleBuilder
161
     */
162
    public function table($table)
163
    {
164
        $processor = $this->getPostProcessor();
165
166
        $query = new QueryBuilder($this, $this->getQueryGrammar(), $processor);
167
168
        return $query->from($table);
169
    }
170
171
    /**
172
     * Set oracle session date format.
173
     *
174
     * @param string $format
175
     * @return $this
176
     */
177
    public function setDateFormat($format = 'YYYY-MM-DD HH24:MI:SS')
178
    {
179
        $sessionVars = [
180
            'NLS_DATE_FORMAT'      => $format,
181
            'NLS_TIMESTAMP_FORMAT' => $format,
182
        ];
183
184
        return $this->setSessionVars($sessionVars);
185
    }
186
187
    /**
188
     * Get doctrine connection.
189
     *
190
     * @return \Doctrine\DBAL\Connection
191
     */
192
    public function getDoctrineConnection()
193
    {
194
        if (is_null($this->doctrineConnection)) {
195
            $data = ['pdo' => $this->getPdo(), 'user' => $this->getConfig('username')];
196
            $this->doctrineConnection = new DoctrineConnection(
197
                $data,
198
                $this->getDoctrineDriver()
199
            );
200
        }
201
202
        return $this->doctrineConnection;
203
    }
204
205
    /**
206
     * Get doctrine driver.
207
     *
208
     * @return \Doctrine\DBAL\Driver\OCI8\Driver
209
     */
210
    protected function getDoctrineDriver()
211
    {
212
        return new DoctrineDriver();
213
    }
214
215
    /**
216
     * Execute a PL/SQL Function and return its value.
217
     * Usage: DB::executeFunction('function_name(:binding_1,:binding_n)', [':binding_1' => 'hi', ':binding_n' =>
218
     * 'bye'], PDO::PARAM_LOB).
219
     *
220
     * @param string $functionName
221
     * @param array  $bindings   (kvp array)
222
     * @param int    $returnType (PDO::PARAM_*)
223
     * @param int    $length
224
     * @return mixed $returnType
225
     */
226
    public function executeFunction($functionName, array $bindings = [], $returnType = PDO::PARAM_STR, $length = null)
227
    {
228
        $stmt = $this->createStatementFromFunction($functionName, $bindings);
229
230
        foreach ($bindings as $key => &$value) {
231
            $stmt->bindParam($key, $value);
232
        }
233
234
        $stmt->bindParam(':result', $result, $returnType, $length);
235
        $stmt->execute();
236
237
        return $result;
238
    }
239
240
    /**
241
     * Execute a PL/SQL Procedure and return its results.
242
     *
243
     * Usage: DB::executeProcedure($procedureName, $bindings).
244
     * $bindings looks like:
245
     *         $bindings = [
246
     *                  'p_userid'  => $id
247
     *         ];
248
     *
249
     * @param  string $procedureName
250
     * @param  array  $bindings
251
     * @return bool
252
     */
253
    public function executeProcedure($procedureName, array $bindings = [])
254
    {
255
        $stmt = $this->createStatementFromProcedure($procedureName, $bindings);
256
257
        foreach ($bindings as $key => &$value) {
258
            $stmt->bindParam(':' . $key, $value);
259
        }
260
261
        return $stmt->execute();
262
    }
263
264
    /**
265
     * Execute a PL/SQL Procedure and return its cursor result.
266
     * Usage: DB::executeProcedureWithCursor($procedureName, $bindings).
267
     *
268
     * https://docs.oracle.com/cd/E17781_01/appdev.112/e18555/ch_six_ref_cur.htm#TDPPH218
269
     *
270
     * @param  string $procedureName
271
     * @param  array  $bindings
272
     * @param  string $cursorName
273
     * @return array
274
     */
275
    public function executeProcedureWithCursor($procedureName, array $bindings = [], $cursorName = ':cursor')
276
    {
277
        $stmt = $this->createStatementFromProcedure($procedureName, $bindings, $cursorName);
278
279
        foreach ($bindings as $key => &$value) {
280
            $stmt->bindParam(':' . $key, $value);
281
        }
282
283
        $cursor = null;
284
        $stmt->bindParam($cursorName, $cursor, PDO::PARAM_STMT);
285
        $stmt->execute();
286
287
        $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...
288
        $statement->execute();
289
        $results = $statement->fetchAll(PDO::FETCH_OBJ);
290
        $statement->closeCursor();
291
292
        return $results;
293
    }
294
295
    /**
296
     * Creates sql command to run a procedure with bindings.
297
     *
298
     * @param  string      $procedureName
299
     * @param  array       $bindings
300
     * @param  string|bool $cursor
301
     * @return string
302
     */
303
    public function createSqlFromProcedure($procedureName, array $bindings, $cursor = false)
304
    {
305
        $paramsString = implode(',', array_map(function ($param) {
306
            return ':' . $param;
307
        }, array_keys($bindings)));
308
309
        $prefix = count($bindings) ? ',' : '';
310
        $cursor = $cursor ? $prefix . $cursor : null;
311
312
        return sprintf('begin %s(%s%s); end;', $procedureName, $paramsString, $cursor);
313
    }
314
315
    /**
316
     * Creates statement from procedure.
317
     *
318
     * @param  string      $procedureName
319
     * @param  array        $bindings
320
     * @param  string|bool $cursorName
321
     * @return PDOStatement
322
     */
323
    public function createStatementFromProcedure($procedureName, array $bindings, $cursorName = false)
324
    {
325
        $sql = $this->createSqlFromProcedure($procedureName, $bindings, $cursorName);
326
327
        return $this->getPdo()->prepare($sql);
328
    }
329
330
    /**
331
     * Create statement from function.
332
     *
333
     * @param string $functionName
334
     * @param array  $bindings
335
     *
336
     * @return PDOStatement
337
     */
338
    public function createStatementFromFunction($functionName, array $bindings)
339
    {
340
        $bindings = $bindings ? ':' . implode(', :', array_keys($bindings)) : '';
341
342
        $sql = sprintf('begin :result := %s(%s); end;', $functionName, $bindings);
343
344
        return $this->getPdo()->prepare($sql);
345
    }
346
347
    /**
348
     * Bind values to their parameters in the given statement.
349
     *
350
     * @param PDOStatement $statement
351
     * @param array $bindings
352
     */
353
    public function bindValues($statement, $bindings)
354
    {
355
        foreach ($bindings as $key => $value) {
356
            $statement->bindParam($key, $bindings[$key]);
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());
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...
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