Completed
Pull Request — master (#526)
by Michael
02:11
created

PdoDatabase::prepare()   A

Complexity

Conditions 4
Paths 6

Size

Total Lines 31
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 11.1547

Importance

Changes 0
Metric Value
cc 4
eloc 20
nc 6
nop 2
dl 0
loc 31
ccs 4
cts 17
cp 0.2353
crap 11.1547
rs 9.6
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * @param string $db
5
 * @return PdoDatabase
6
 * @throws Exception
7
 */
8
function gGetDb($db = "acc")
9
{
10 1
	return PdoDatabase::getDatabaseConnection($db);
11
}
12
13
class PdoDatabase extends PDO
14
{
15
	/**
16
	 * @var PdoDatabase[]
17
	 */
18
	private static $connections = array();
19
20
	/**
21
	 * @var bool True if a transaction is active
22
	 */
23
	protected $hasActiveTransaction = false;
24
    
25
	/**
26
	 * Summary of $queryLogStatement
27
	 * @var PDOStatement
28
	 */
29
	private $queryLogStatement;
30
31
	/**
32
	 * @param string $connectionName
33
	 * @return PdoDatabase
34
	 * @throws Exception
35
	 */
36 1
	public static function getDatabaseConnection($connectionName)
37
	{
38 1
		if (!isset(self::$connections[$connectionName])) {
39 1
			global $cDatabaseConfig;
40
41 1
			if (!array_key_exists($connectionName, $cDatabaseConfig)) {
42
				throw new Exception("Database configuration not found for alias $connectionName");
43
			}
44
45
			try {
46 1
				$databaseObject = new PdoDatabase(
47 1
					$cDatabaseConfig[$connectionName]["dsrcname"],
48 1
					$cDatabaseConfig[$connectionName]["username"],
49 1
					$cDatabaseConfig[$connectionName]["password"],
50 1
					$cDatabaseConfig[$connectionName]["options"]
51
				);
52
			}
53
			catch (PDOException $ex) {
54
				// wrap around any potential stack traces which may include passwords
55
				throw new Exception("Error connecting to database '$connectionName': " . $ex->getMessage());
56
			}
57
58 1
			$databaseObject->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
59
60
			// emulating prepared statements gives a performance boost on MySQL.
61
			//
62
			// however, our version of PDO doesn't seem to understand parameter types when emulating
63
			// the prepared statements, so we're forced to turn this off for now.
64
			// -- stw 2014-02-11
65 1
			$databaseObject->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
66
67 1
			self::$connections[$connectionName] = $databaseObject;
68
		}
69
70 1
		return self::$connections[$connectionName];
71
	}
72
73
	/**
74
	 * Determines if this connection has a transaction in progress or not
75
	 * @return boolean true if there is a transaction in progress.
76
	 */
77
	public function hasActiveTransaction()
78
	{
79
		return $this->hasActiveTransaction;
80
	}
81
82
	/**
83
	 * Summary of beginTransaction
84
	 * @return bool
85
	 */
86
	public function beginTransaction()
87
	{
88
		// Override the pre-existing method, which doesn't stop you from
89
		// starting transactions within transactions - which doesn't work and
90
		// will throw an exception. This eliminates the need to catch exceptions
91
		// all over the rest of the code
92
		if ($this->hasActiveTransaction) {
93
			return false;
94
		}
95
		else {
96
			// set the transaction isolation level for every transaction.
97
			$this->exec("SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;");
98
99
			// start a new transaction, and return whether or not the start was
100
			// successful
101
			$this->hasActiveTransaction = parent::beginTransaction();
102
			return $this->hasActiveTransaction;
103
		}
104
	}
105
106
	/**
107
	 * Commits the active transaction
108
	 */
109
	public function commit()
110
	{
111
		parent::commit();
112
		$this->hasActiveTransaction = false;
113
	}
114
115
	/**
116
	 * Rolls back a transaction
117
	 */
118
	public function rollBack()
119
	{
120
		parent::rollback();
121
		$this->hasActiveTransaction = false;
122
	}
123
124
	/**
125
	 * Summary of transactionally
126
	 * @param Closure $method 
127
	 */
128
	public function transactionally($method)
129
	{
130
		if (!$this->beginTransaction()) {
131
			BootstrapSkin::displayAlertBox("Error starting database transaction.", "alert-error", "Database transaction error", true, false);
132
			BootstrapSkin::displayInternalFooter();
133
			die();
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
134
		}
135
136
		try {
137
			$method();
138
139
			$this->commit();
140
		}
141
		catch (TransactionException $ex) {
142
			$this->rollBack();
143
144
			BootstrapSkin::displayAlertBox($ex->getMessage(), $ex->getAlertType(), $ex->getTitle(), true, false);
145
146
			// TODO: yuk.
147
			if (defined("PUBLICMODE")) {
148
				BootstrapSkin::displayPublicFooter();
149
			}
150
			else {
151
				BootstrapSkin::displayInternalFooter();
152
			}
153
154
			die();
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
155
		}
156
	}
157
158
	/**
159
	 * Prepares a statement for execution.
160
	 * @param string $statement 
161
	 * @param array $driver_options 
162
	 * @return PDOStatement
163
	 */
164 1
	public function prepare($statement, $driver_options = array())
165
	{
166 1
		global $enableQueryLog;
167 1
		if ($enableQueryLog) {
168
			try {
169
				if ($this->queryLogStatement === null) {
170
					$this->queryLogStatement = 
171
						parent::prepare(<<<SQL
0 ignored issues
show
Documentation Bug introduced by
It seems like parent::prepare(' ...ck, :request, :rqts);') can also be of type boolean. However, the property $queryLogStatement is declared as type PDOStatement. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
172
							INSERT INTO applicationlog (source, message, stack, request, request_ts) 
173
							VALUES (:source, :message, :stack, :request, :rqts);
174
SQL
175
						);
176
				}
177
178
				$this->queryLogStatement->execute(
179
					array(
180
						":source" => "QueryLog",
181
						":message" => $statement,
182
						":stack" => DebugHelper::getBacktrace(),
183
						":request" => $_SERVER["REQUEST_URI"],
184
						":rqts" => $_SERVER["REQUEST_TIME_FLOAT"],
185
					)
186
				);
187
			}
188
			catch (Exception $ex) {
189
				trigger_error("Error logging query. Disabling for this request. " . $ex->getMessage(), E_USER_NOTICE);
190
				$enableQueryLog = false;
191
			}
192
		}
193
        
194 1
		return parent::prepare($statement, $driver_options);   
0 ignored issues
show
Bug Best Practice introduced by
The expression return parent::prepare($...ement, $driver_options) also could return the type boolean which is incompatible with the documented return type PDOStatement.
Loading history...
195
	}
196
}
197