PDODatabase::query()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
c 1
b 0
f 0
nc 1
nop 1
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
1
<?php
2
3
declare(strict_types=1);
4
/**
5
 * Database Access Layer.
6
 * @author Doug Wright
7
 */
8
9
namespace DVDoug\DB;
10
11
/**
12
 * PDO-backed database connection (common parts).
13
 * @author Doug Wright
14
 */
15
abstract class PDODatabase extends \PDO implements DatabaseInterface
16
{
17
    /**
18
     * Character to use when quoting identifiers.
19
     */
20
    public const IDENTIFIER_OPENQUOTE = '"';
21
22
    /**
23
     * Character to use when quoting identifiers.
24
     */
25
    public const IDENTIFIER_CLOSEQUOTE = '"';
26
27
    /**
28
     * Constructor.
29
     * @param string $aDSN
30
     * @param string $aUsername
31
     * @param string $aPassword
32
     */
33 40
    public function __construct($aDSN, $aUsername, $aPassword, array $aDriverOptions = null)
34
    {
35 40
        parent::__construct($aDSN, $aUsername, $aPassword, $aDriverOptions);
36 40
        $this->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
37 40
        $this->setAttribute(\PDO::ATTR_STATEMENT_CLASS, ['\DVDoug\DB\PDOStatement']);
38 40
    }
39
40
    /**
41
     * Executes an SQL statement, returning the result set if any as a StatementInterface object.
42
     * @param  string                  $aSQL the SQL statement to execute
43
     * @return StatementInterface|bool
44
     */
45 20
    public function query($aSQL)
46
    {
47 20
        return parent::query($aSQL);
48
    }
49
50
    /**
51
     * Returns the ID of the last inserted row or sequence value.
52
     * @param string $aName name of the sequence object (if any) from which the ID should be returned
53
     */
54
    public function getLastInsertId($aName = null): string
55
    {
56
        return parent::lastInsertID($aName);
57
    }
58
59
    /**
60
     * Escapes/quotes a parameter for use in a query.
61
     *
62
     * @param mixed $aParam     the parameter to be quoted
63
     * @param int   $aParamType data type hint for drivers
64
     *
65
     * @throws \RuntimeException
66
     * @return string            a quoted string that is theoretically safe to pass into an SQL statement
67
     */
68 12
    public function escape($aParam, $aParamType = DatabaseInterface::PARAM_IS_STR)
69
    {
70
        switch ($aParamType) {
71 12
            case self::PARAM_IS_INT:
72 8
                if (is_int($aParam) || ctype_digit($aParam)) {
73 4
                    return (int) $aParam;
74
                } else {
75 4
                    throw new \RuntimeException("Parameter {$aParam} is not an integer");
76
                }
77
                break;
78
79
            default:
80 8
                return parent::quote($aParam, $aParamType);
81
        }
82
    }
83
84
    /**
85
     * Adds appropriate quotes to an identifier so it can be safely used in an SQL statement.
86
     * @param mixed $aIdentifier the parameter to be quoted
87
     */
88 20
    public function quoteIdentifier($aIdentifier): string
89
    {
90 20
        return static::IDENTIFIER_OPENQUOTE . $aIdentifier . static::IDENTIFIER_CLOSEQUOTE;
91
    }
92
93
    /**
94
     * Get MySQL table definition.
95
     * @param string $aDatabase       database/schema name
96
     * @param string $aTable          table name
97
     * @param bool   $aSkipUnusedCols whether to skip unused columns
98
     */
99 8
    public function getMySQLTableDef($aDatabase, $aTable, $aSkipUnusedCols = true): string
100
    {
101 8
        $table = strtolower($aTable);
102
103 8
        $columns = $this->getTableColumns($aDatabase, $aTable);
104
105 8
        $colDefs = [];
106
107 8
        foreach ($columns as $columnName => $column) {
108 8
            if ($aSkipUnusedCols && $column->getDistinctValueCount() <= 1) {
109
                unset($columns[$columnName]);
110
                continue;
111
            }
112
113 8
            $colDefs[] = $column->getMySQLColumnDef();
114
        }
115
116 8
        $primaryKey = $this->getPrimaryKey($aDatabase, $aTable);
117 8
        $indexes = $this->getIndexes($aDatabase, $aTable);
118
119 8
        $tableDef = "CREATE TABLE `{$table}` (" . "\n";
120 8
        $tableDef .= implode(',' . "\n", $colDefs);
121
122 8
        if ($primaryKey) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $primaryKey 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...
123
            $length = 0;
124
            foreach ($primaryKey as $primaryCol) {
125
                $length += $columns[$primaryCol]->getLength();
126
            }
127
            if ($length <= 191) { //skip index if too long for MySQL
128
                $tableDef .= ',' . "\n" . "\n";
129
                $tableDef .= 'PRIMARY KEY (';
130
                $tableDef .= implode(', ' . "\n", array_map(function ($c) {return '`' . strtolower($c) . '`'; }, $primaryKey));
131
                $tableDef .= ')';
132
            }
133
        }
134
135 8
        if ($indexes) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $indexes 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...
136
            foreach ($indexes as $indexName => $indexColumns) {
137
                $length = 0;
138
                foreach ($indexColumns as &$col) {
139
                    if (!in_array($col, array_keys($columns))) { //skip index if it includes a skipped column
140
                        continue 2;
141
                    }
142
143
                    $length += $columns[$col]->getLength();
144
                    if ($length > 191) { //skip index if too long for MySQL
145
                        continue 2;
146
                    }
147
148
                    if (preg_match('/(BLOB|TEXT)$/', $columns[$col]->getMySQLType())) {
149
                        continue 2;
150
                    }
151
                }
152
                $tableDef .= ',' . "\n";
153
                $tableDef .= 'KEY `' . strtolower($indexName) . '` (';
154
                $tableDef .= implode(', ', array_map(function ($c) {return '`' . strtolower($c) . '`'; }, $indexColumns));
155
                $tableDef .= ')';
156
            }
157
        }
158 8
        $tableDef .= ') ENGINE=InnoDB ROW_FORMAT=COMPRESSED';
159
160 8
        return $tableDef;
161
    }
162
163
    /**
164
     * Get Oracle table definition.
165
     * @param string $aDatabase       database/schema name
166
     * @param string $aTable          table name
167
     * @param bool   $aSkipUnusedCols whether to skip unused columns
168
     */
169 8
    public function getOracleTableDef($aDatabase, $aTable, $aSkipUnusedCols = true): string
170
    {
171 8
        $table = strtolower($aTable);
172
173 8
        $columns = $this->getTableColumns($aDatabase, $aTable);
174
175 8
        $colDefs = [];
176
177 8
        foreach ($columns as $columnName => $column) {
178 8
            if ($aSkipUnusedCols && $column->getDistinctValueCount() <= 1) {
179
                unset($columns[$columnName]);
180
                continue;
181
            }
182
183 8
            $colDefs[] = $column->getOracleColumnDef();
184
        }
185
186 8
        $primaryKey = $this->getPrimaryKey($aDatabase, $aTable);
187 8
        $indexes = $this->getIndexes($aDatabase, $aTable);
188
189 8
        $tableDef = "CREATE TABLE `{$table}` (" . "\n";
190 8
        $tableDef .= implode(',' . "\n", $colDefs);
191
192 8
        if ($primaryKey) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $primaryKey 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...
193
            $tableDef .= ',' . "\n" . "\n";
194
            $tableDef .= 'PRIMARY KEY (';
195
            $tableDef .= implode(', ' . "\n", array_map(function ($c) {return '"' . strtolower($c) . '"'; }, $primaryKey));
196
            $tableDef .= ')';
197
        }
198
199 8
        if ($indexes) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $indexes 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...
200
            foreach ($indexes as $indexName => $indexColumns) {
201
                foreach ($indexColumns as &$col) {
202
                    if (!in_array($col, array_keys($columns))) { //skip index if it includes a skipped column
203
                        continue 2;
204
                    }
205
                }
206
                $tableDef .= ',' . "\n";
207
                $tableDef .= 'KEY `' . strtolower($indexName) . '` (';
208
                $tableDef .= implode(', ', array_map(function ($c) {return '"' . strtolower($c) . '"'; }, $indexColumns));
209
                $tableDef .= ')';
210
            }
211
        }
212 8
        $tableDef .= ')';
213
214 8
        return $tableDef;
215
    }
216
}
217