Completed
Push — master ( 5c9c14...d8f82a )
by James Ekow Abaka
05:37
created

Driver::getDefaultSchema()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
cc 1
eloc 2
nc 1
nop 0
crap 1
1
<?php
2
3
namespace ntentan\atiaa;
4
5
use ntentan\config\Config;
6
use ntentan\atiaa\exceptions\DatabaseDriverException;
7
8
/**
9
 * A driver class for connecting to a specific database platform.
10
 * The Driver class is the main wrapper for atiaa. The driver class contains
11
 * an instance of PDO with which it performs its operations. Aside from wrapping
12
 * around PDO it also provides methods which makes it possible to quote strings
13
 * and identifiers in a platform independent fashion. The driver class is
14
 * responsible for loading the descriptors which are used for describing the
15
 * database schemas.
16
 */
17
abstract class Driver
18
{
19
20
    /**
21
     * The internal PDO connection that is wrapped by this driver.
22
     * @var \PDO
23
     */
24
    private $pdo;
25
26
    /**
27
     * The default schema used in the connection.
28
     * @var string
29
     */
30
    protected $defaultSchema;
31
32
    /**
33
     * The connection parameters with which this connection was established.
34
     * @var array
35
     */
36
    protected $config;
37
38
    /**
39
     * An instance of the descriptor used internally.
40
     * @var \ntentan\atiaa\Descriptor
41
     */
42
    private $descriptor;
43
    
44
    private static $transactionCount = 0;
45
46
    /**
47
     * Creates a new instance of the Atiaa driver. This class is usually initiated
48
     * through the \ntentan\atiaa\Atiaa::getConnection() method. For example
49
     * to create a new instance of a connection to a mysql database.
50
     * 
51
     * ````php
52
     * use ntentan\atiaa\Driver;
53
     * 
54
     * \\ This automatically insitatiates the driver class
55
     * $driver = Driver::getConnection(
56
     *     array(
57
     *         'driver' => 'mysql',
58
     *         'user' => 'root',
59
     *         'password' => 'rootpassy',
60
     *         'host' => 'localhost',
61
     *         'dbname' => 'somedb'
62
     *     )
63
     * );
64
     * 
65
     * var_dump($driver->query("SELECT * FROM some_table");
66
     * var_dump($driver->describe());
67
     * ````
68
     * 
69
     * @param array<string> $config The configuration with which to connect to the database.
70
     */
71 33
    public function __construct($config = null)
72
    {
73 33
        $this->config = $config ? $config : Config::get('ntentan:db');
74 33
        $username = isset($this->config['user']) ? $this->config['user'] : null;
75 33
        $password = isset($this->config['password']) ? $this->config['password'] : null;
76
77
        try{
78 33
            $this->pdo = new \PDO(
79 33
                $this->getDriverName() . ":" . $this->expand($this->config), $username, $password
80
            );
81 31
            $this->pdo->setAttribute(\PDO::ATTR_STRINGIFY_FETCHES, false);
82 31
            $this->pdo->setAttribute(\PDO::ATTR_EMULATE_PREPARES, false);
83 31
            $this->pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
84
        }
85 2
        catch (\PDOException $e)
86
        {
87 2
            throw new DatabaseDriverException("PDO failed to connect: {$e->getMessage()}", $e);
88
        }
89 31
    }
90
91 20
    public function __destruct()
92
    {
93 20
        $this->disconnect();
94 20
    }
95
96
    /**
97
     * Close a connection to the database server.
98
     */
99 30
    public function disconnect()
100
    {
101 30
        $this->pdo = null;
102 30
        $this->pdo = new NullConnection();
0 ignored issues
show
Documentation Bug introduced by
It seems like new \ntentan\atiaa\NullConnection() of type object<ntentan\atiaa\NullConnection> is incompatible with the declared type object<PDO> of property $pdo.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
103 30
    }
104
105
    /**
106
     * Get the default schema of the current connection.
107
     * @return string
108
     */
109 11
    public function getDefaultSchema()
110
    {
111 11
        return $this->defaultSchema;
112
    }
113
114
    /**
115
     * Use the PDO driver to quote a string.
116
     * @param type $string
117
     * @return string
118
     */
119 3
    public function quote($string)
120
    {
121 3
        return $this->pdo->quote($string);
122
    }
123
124
    /**
125
     * 
126
     * @param boolean $status
0 ignored issues
show
Bug introduced by
There is no parameter named $status. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
127
     * @param \PDOStatement  $result 
0 ignored issues
show
Bug introduced by
There is no parameter named $result. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
128
     */
129 25
    private function fetchRows($statement)
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
130
    {
131
        try {
132 25
            $rows = $statement->fetchAll(\PDO::FETCH_ASSOC);
133 25
            return $rows;
134
        } catch (\PDOException $e) {
135
            // Skip any exceptions from fetching rows
136
        }
137
    }
138
139
    /**
140
     * Pepare and execute a query, while binding data at the same time. Prevents
141
     * the writing of repetitive prepare and execute statements. This method
142
     * returns an array which contains the results of the query that was
143
     * executed. For queries which do not return any results a null is returned.
144
     * 
145
     * @todo Add a parameter to cache prepared statements so they can be reused easily.
146
     * 
147
     * @param string $query The query to be executed quoted in PDO style
148
     * @param false|array<mixed> $bindData The data to be bound to the query object.
149
     * @return array<mixed>
150
     */
151 29
    public function query($query, $bindData = false)
152
    {
153
        try {
154 29
            if (is_array($bindData)) {
155 22
                $statement = $this->pdo->prepare($query);
156 22
                $statement->execute($bindData);
157
            } else {
158 29
                $statement = $this->pdo->query($query);
159
            }
160 6
        } catch (\PDOException $e) {
161 3
            $boundData = json_encode($bindData);
162 3
            throw new DatabaseDriverException("{$e->getMessage()} [$query] [BOUND DATA:$boundData]");
163
        }
164 25
        $rows = $this->fetchRows($statement);
165 25
        $statement->closeCursor();
166 25
        return $rows;
167
    }
168
169
    /**
170
     * Runs a query but ensures that all identifiers are properly quoted by calling
171
     * the Driver::quoteQueryIdentifiers method on the query before executing it.
172
     * 
173
     * @param string $query
174
     * @param false|array<mixed> $bindData
175
     * @return array<mixed>
176
     */
177 16
    public function quotedQuery($query, $bindData = false)
178
    {
179 16
        return $this->query($this->quoteQueryIdentifiers($query), $bindData);
180
    }
181
182
    /**
183
     * Expands the configuration array into a format that can easily be passed
184
     * to PDO.
185
     * 
186
     * @param array $params The query parameters
187
     * @return string
188
     */
189 33
    private function expand($params)
190
    {
191 33
        unset($params['driver']);
192 33
        if (isset($params['file'])) {
193 33
            if($params['file'] != '') {
194 10
                return $params['file'];
195
            }
196
        } 
197
        
198 23
        $equated = array();
199 23
        foreach ($params as $key => $value) {
200 23
            if ($value == '') {
201 23
                continue;
202
            } else {
203 23
                $equated[] = "$key=$value";
204
            }
205
        }
206 23
        return implode(';', $equated);
207
    }
208
209
    /**
210
     * This method provides a system independent way of quoting identifiers in
211
     * queries. By default all identifiers can be quoted with double quotes (").
212
     * When a query quoted with double quotes is passed through this method the
213
     * output generated has the double quotes replaced with the quoting character
214
     * of the target database platform.
215
     * 
216
     * @param string $query
217
     * @return string
218
     */
219 19
    public function quoteQueryIdentifiers($query)
220
    {
221 19
        return preg_replace_callback(
222 19
            '/\"([a-zA-Z\_ ]*)\"/', 
223 19
            function($matches) {
224 19
                return $this->quoteIdentifier($matches[1]);
225 19
            }, $query
226
        );
227
    }
228
229
    /**
230
     * Returns an array description of the schema represented by the connection.
231
     * The description returns contains information about `tables`, `columns`, `keys`,
232
     * `constraints`, `views` and `indices`.
233
     * 
234
     * @return array<mixed>
235
     */
236 6
    public function describe()
237
    {
238 6
        return $this->getDescriptor()->describe();
239
    }
240
241
    /**
242
     * Returns the description of a database table as an associative array.
243
     * 
244
     * @param type $table
245
     * @return array<mixed>
246
     */
247 7
    public function describeTable($table)
248
    {
249 7
        $table = explode('.', $table);
250 7
        if (count($table) > 1) {
251 1
            $schema = $table[0];
252 1
            $table = $table[1];
253
        } else {
254 6
            $schema = $this->getDefaultSchema();
255 6
            $table = $table[0];
256
        }
257 7
        return $this->getDescriptor()->describeTables($schema, array($table), true);
258
    }
259
260
    /**
261
     * A wrapper arround PDO's beginTransaction method which uses a static reference
262
     * counter to implement nested transactions.
263
     */
264 6
    public function beginTransaction()
265
    {
266 6
        if(self::$transactionCount++ === 0) {
267 6
            $this->pdo->beginTransaction();
268
        }
269 6
    }
270
271
    /**
272
     * A wrapper around PDO's commit transaction method which uses a static reference
273
     * counter to implement nested transactions.
274
     */
275 3
    public function commit()
276
    {
277 3
        if(--self::$transactionCount === 0) {
278 3
            $this->pdo->commit();
279
        }
280 3
    }
281
282
    /**
283
     * A wrapper around PDO's rollback transaction methd which rolls back all
284
     * activities performed since the first call to begin transaction. 
285
     * Unfortunately, transactions cannot be rolled back in a nested fashion.
286
     */
287 3
    public function rollback()
288
    {
289 3
        $this->pdo->rollBack();
290 3
        self::$transactionCount = 0;
291 3
    }
292
293
    /**
294
     * Return the underlying PDO object.
295
     * @return \PDO
296
     */
297 3
    public function getPDO()
298
    {
299 3
        return $this->pdo;
300
    }
301
302
    /**
303
     * Returns an instance of a descriptor for a given driver.
304
     * @return \atiaa\Descriptor
305
     */
306 13
    private function getDescriptor()
307
    {
308 13
        if (!is_object($this->descriptor)) {
309 13
            $descriptorClass = "\\ntentan\\atiaa\\descriptors\\" . ucfirst($this->config['driver']) . "Descriptor";
310 13
            $this->descriptor = new $descriptorClass($this);
311
        }
312 13
        return $this->descriptor;
313
    }
314
315
    /**
316
     * A wrapper around PDO's lastInsertId() method.
317
     * @return mixed
318
     */
319
    public function getLastInsertId()
320
    {
321
        return $this->pdo->lastInsertId();
322
    }
323
324
    /**
325
     * Specify the default schema to use in cases where a schema is not provided
326
     * as part of the table reference.
327
     * @param string $defaultSchema
328
     */
329
    public function setDefaultSchema($defaultSchema)
330
    {
331
        $this->defaultSchema = $defaultSchema;
332
    }
333
334
    abstract protected function getDriverName();
0 ignored issues
show
Documentation introduced by
For interfaces and abstract methods it is generally a good practice to add a @return annotation even if it is just @return void or @return null, so that implementors know what to do in the overridden method.

For interface and abstract methods, it is impossible to infer the return type from the immediate code. In these cases, it is generally advisible to explicitly annotate these methods with a @return doc comment to communicate to implementors of these methods what they are expected to return.

Loading history...
335
336
    abstract public function quoteIdentifier($identifier);
0 ignored issues
show
Documentation introduced by
For interfaces and abstract methods it is generally a good practice to add a @return annotation even if it is just @return void or @return null, so that implementors know what to do in the overridden method.

For interface and abstract methods, it is impossible to infer the return type from the immediate code. In these cases, it is generally advisible to explicitly annotate these methods with a @return doc comment to communicate to implementors of these methods what they are expected to return.

Loading history...
337
338
    /**
339
     * Returns a new instance of a driver based on the connection parameters 
340
     * passed to the method. The connection parameters are passed through an
341
     * associative array with the following keys.
342
     * 
343
     * driver 
344
     * : The name of the driver to use for the database connection. Supported
345
     *   drivers are `mysql` and `postgresql`. This parameter is required for
346
     *   all connections.
347
     * 
348
     * user
349
     * : The username to use for the database connection on platforms that 
350
     *   support it.
351
     * 
352
     * password
353
     * : The password associated to the user specified in the connection.
354
     * 
355
     * host
356
     * : The host name of the database server.
357
     * 
358
     * dbname
359
     * : The name of the default database to use after the connection to the 
360
     *   database is established.
361
     * 
362
     * @param array $config 
363
     * @return \ntentan\atiaa\Driver
364
     */
365
    /*public static function getConnection($config)
0 ignored issues
show
Unused Code Comprehensibility introduced by
57% 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...
366
    {
367
        if (is_string($config) && file_exists($config)) {
368
            require $config;
369
        } else if ($config['driver'] == '') {
370
            throw new DatabaseDriverException("Please specify a name for your database driver.");
371
        }
372
        try {
373
            $class = "\\ntentan\\atiaa\\drivers\\" . ucfirst($config['driver']) . "Driver";
374
            return new $class($config);
375
        } catch (\PDOException $e) {
376
            throw new DatabaseDriverException("PDO failed to connect: {$e->getMessage()}", $e);
377
        }
378
    }*/
379
    
380 3
    public function setCleanDefaults($cleanDefaults)
381
    {
382 3
        $this->getDescriptor()->setCleanDefaults($cleanDefaults);
383 3
    }
384
}
385