Passed
Pull Request — master (#53)
by
unknown
02:22
created

SqliteAdapter   B

Complexity

Total Complexity 43

Size/Duplication

Total Lines 148
Duplicated Lines 12.84 %

Importance

Changes 0
Metric Value
dl 19
loc 148
rs 8.3157
c 0
b 0
f 0
wmc 43

11 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 2 1
A quote() 0 2 1
B alterDatabase() 0 24 4
B createTable() 5 20 8
A addIndex() 0 15 4
A getColumns() 0 7 2
F query() 9 34 12
B getType() 5 7 8
A optimiseColumns() 0 1 1
A lastInsertId() 0 2 1
A tableExists() 0 3 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like SqliteAdapter often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use SqliteAdapter, and based on these observations, apply Extract Interface, too.

1
<?php
2
namespace Maphper\DataSource;
3
4
class SqliteAdapter implements DatabaseAdapter {
5
	private $pdo;
6
	private $queryCache = [];
7
	
8
	public function __construct(\PDO $pdo) {
9
		$this->pdo = $pdo;	
10
	}
11
	
12
	public function quote($str) {
13
		return '`' . str_replace('.', '`.`', trim($str, '`')) . '`';
14
	}
15
		
16
	public function query(\Maphper\Lib\Query $query) {
17
		$queryId = md5($query->getSql());
18 View Code Duplication
		if (isset($this->queryCache[$queryId])) $stmt = $this->queryCache[$queryId];
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
19
		else {
20
			$stmt = $this->pdo->prepare($query->getSql(), [\PDO::ATTR_CURSOR => \PDO::CURSOR_FWDONLY]);
21
			if ($stmt) $this->queryCache[$queryId] = $stmt;
22
		}
23
		
24
		$args = $query->getArgs();
25 View Code Duplication
		foreach ($args as &$arg) if ($arg instanceof \DateTime) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
26
			if ($arg->format('H:i:s')  == '00:00:00') $arg = $arg->format('Y-m-d');
27
			else $arg = $arg->format('Y-m-d H:i:s');
28
		}
29
				
30
		if ($stmt !== false) {
31
			try {
32
				if (count($args) > 0) $res = $stmt->execute($args);
0 ignored issues
show
Unused Code introduced by
The assignment to $res is dead and can be removed.
Loading history...
33
				else $res = $stmt->execute();
34
				if ($stmt->errorCode() !== '00000') throw new \Exception('Invalid query');
35
				if (substr($query->getSql(), 0, 6) === 'SELECT') return $stmt->fetchAll(\PDO::FETCH_OBJ);
36
				else return $stmt;
37
			}
38
			catch (\Exception $e) {
39
				//SQLite causes an error if when the DB schema changes, rebuild $stmt and try again.
40
				if ($stmt->errorInfo()[2] == 'database schema has changed') {
41
					unset($this->queryCache[$queryId]);
42
					return $this->query($query);	
43
				}
44
				else return $stmt;				
45
			}
46
		}
47
		//Handle SQLite when PDO_ERRMODE is set to SILENT
48
		else {
49
			throw new \Exception('Invalid query');
50
		}
51
	}
52
	
53
	public function lastInsertId() {
54
		return $this->pdo->lastInsertId();
55
	}
56
	
57
	private function getType($val) {
58
		if ($val instanceof \DateTime) return 'DATETIME';
59 View Code Duplication
		else if (is_int($val)) return  'INTEGER';
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
60
		else if (is_double($val)) return 'DECIMAL(9,' . strlen($val) - strrpos($val, '.') - 1 . ')';
61
		else if (is_string($val) && strlen($val) < 256) return 'VARCHAR(255)';
62
		else if (is_string($val) && strlen($val) > 256) return 'LONGBLOG';
63
		else return 'VARCHAR(255)';		
64
	}
65
66
	private function tableExists($name) {
67
		$result = $this->pdo->query('SELECT name FROM sqlite_master WHERE type="table" and name="'. $name.'"');
68
		return count($result->fetchAll()) == 1;
69
	}
70
71
	private function getColumns($table) {
72
		$result = $this->pdo->query('PRAGMA table_info(' . $table . ');')->fetchAll(\PDO::FETCH_OBJ);
73
		$return = [];
74
		foreach ($result as $row) {
75
			$return[] = $row->name;
76
		}
77
		return $return;
78
	}
79
80
	//Alter the database so that it can store $data
81
	public function alterDatabase($table, array $primaryKey, $data) {
82
		//Unset query cache, otherwise it causes:
83
		// SQLSTATE[HY000]: General error: 17 database schema has changed
84
		$this->queryCache = [];
85
86
		$affix = '_'.substr(md5($table), 0, 6);
87
		$this->createTable($table . $affix, $primaryKey, $data);
88
		$fields = [];
89
		foreach ($data as $key => $value) { $fields[] = $key; }
90
		try {
91
			if ($this->tableExists($table)) {
92
				$columns = implode(', ', $this->getColumns($table));			
93
94
				$this->pdo->query('INSERT INTO ' . $this->quote($table . $affix) . '(' . $columns . ') SELECT ' . $columns . ' FROM ' . $this->quote($table));
95
				$this->pdo->query('DROP TABLE IF EXISTS ' . $table );
96
			}
97
		}
98
		catch (\PDOException $e) {
99
			// No data to copy
100
			echo $e->getMessage();
101
		}
102
103
		$this->pdo->query('DROP TABLE IF EXISTS ' . $table );
104
		$this->pdo->query('ALTER TABLE ' . $table . $affix. ' RENAME TO '. $table );
105
106
	}
107
108
	public function createTable($table, array $primaryKey, $data) {
109
		$parts = [];
110 View Code Duplication
		foreach ($primaryKey as $key) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
111
			$pk = $data->$key;
112
			if ($pk == null) $parts[] = $key . ' INTEGER'; 
113
			else $parts[] = $key . ' ' . $this->getType($pk) . ' NOT NULL';					
114
		}
115
		
116
		$pkField = implode(', ', $parts) . ', PRIMARY KEY(' . implode(', ', $primaryKey) . ')';
117
				
118
		$this->pdo->query('DROP TABLE IF EXISTS ' . $table );
119
		$this->pdo->query('CREATE TABLE ' . $table . ' (' . $pkField . ')');
120
					
121
		foreach ($data as $key => $value) {
122
			if (is_array($value) || (is_object($value) && !($value instanceof \DateTime))) continue;
123
			if (in_array($key, $primaryKey)) continue;
124
125
			$type = $this->getType($value);
126
		
127
			$this->pdo->query('ALTER TABLE ' . $table . ' ADD ' . $this->quote($key) . ' ' . $type);
128
		}
129
	}
130
	
131
132
	public function addIndex($table, array $fields) {
133
		if (empty($fields)) return false;
134
		
135
		//SQLite doesn't support ASC/DESC indexes, remove the keywords
136
		foreach ($fields as &$field) $field = str_ireplace([' desc', ' asc'], '', $field);
137
		sort($fields);
138
		$fields = array_map('strtolower', $fields);
139
		$fields = array_map('trim', $fields);
140
		$keyName = implode('_', $fields);
141
	
142
		
143
		try {
144
			$this->pdo->query('CREATE INDEX IF NOT EXISTS  ' . $keyName . ' ON ' . $table . ' (' . implode(', ', $fields) . ')');
145
		}
146
		catch (\Exception $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
147
			
148
		}
149
	}
150
	
151
	public function optimiseColumns($table) {
152
		//TODO
153
	}
154
}