Completed
Push — master ( 04d19a...743d3f )
by Simon
02:19
created

PdoDatabase::rollBack()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 3
nc 1
nop 0
dl 0
loc 5
rs 9.4285
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
	return PdoDatabase::getDatabaseConnection($db);
11
}
12
13
class PdoDatabase extends PDO
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

You can fix this by adding a namespace to your class:

namespace YourVendor;

class YourClass { }

When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.

Loading history...
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
	public static function getDatabaseConnection($connectionName)
37
	{
38
		if (!isset(self::$connections[$connectionName])) {
39
			global $cDatabaseConfig;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
40
41
			if (!array_key_exists($connectionName, $cDatabaseConfig)) {
42
				throw new Exception("Database configuration not found for alias $connectionName");
43
			}
44
45
			try {
46
				$databaseObject = new PdoDatabase(
47
					$cDatabaseConfig[$connectionName]["dsrcname"],
48
					$cDatabaseConfig[$connectionName]["username"],
49
					$cDatabaseConfig[$connectionName]["password"],
50
					$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
			$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
			$databaseObject->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
66
67
			self::$connections[$connectionName] = $databaseObject;
68
		}
69
70
		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);
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 132 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
132
			BootstrapSkin::displayInternalFooter();
133
			die();
0 ignored issues
show
Coding Style Compatibility introduced by
The method transactionally() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

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.
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
147
			if (defined("PUBLICMODE")) {
148
				BootstrapSkin::displayPublicFooter();
149
			}
150
			else {
151
				BootstrapSkin::displayInternalFooter();
152
			}
153
154
			die();
0 ignored issues
show
Coding Style Compatibility introduced by
The method transactionally() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

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
	public function prepare($statement, $driver_options = array())
0 ignored issues
show
Coding Style introduced by
prepare uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

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

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style Naming introduced by
The parameter $driver_options is not named in camelCase.

This check marks parameter names that have not been written in camelCase.

In camelCase names are written without any punctuation, the start of each new word being marked by a capital letter. Thus the name database connection string becomes databaseConnectionString.

Loading history...
165
	{
166
		global $enableQueryLog;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
167
		if ($enableQueryLog) {
168
			try {
169
				if ($this->queryLogStatement === null) {
170
					$this->queryLogStatement = 
171
						parent::prepare(<<<SQL
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
		return parent::prepare($statement, $driver_options);   
195
	}
196
}
197