Passed
Push — master ( 2d27c8...269005 )
by Richard
01:38
created

Database::deleteByField()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 5
c 0
b 0
f 0
nc 1
nop 2
dl 0
loc 8
rs 9.4285
1
<?php
2
namespace Maphper\DataSource;
3
class Database implements \Maphper\DataSource {
4
	const EDIT_STRUCTURE = 1;
5
	const EDIT_INDEX = 2;
6
	const EDIT_OPTIMISE = 4;
7
8
	private $table;
9
    private $options;
10
	private $cache = [];
11
	private $primaryKey;
12
	private $fields = '*';
13
	private $defaultSort;
14
	private $adapter;
15
	private $crudBuilder;
16
    private $selectBuilder;
17
    private $whereBuilder;
18
    private $databaseModify;
19
    private $databaseSelect;
20
21
	public function __construct($db, $table, $primaryKey = 'id', array $options = []) {
22
		$this->options = new DatabaseOptions($db, $options);
23
		$this->adapter = $this->options->getAdapter();
24
25
		$this->table = $table;
26
		$this->primaryKey = is_array($primaryKey) ? $primaryKey : [$primaryKey];
27
28
		$this->crudBuilder = new \Maphper\Lib\CrudBuilder();
29
		$this->selectBuilder = new \Maphper\Lib\SelectBuilder();
30
        $this->whereBuilder = new \Maphper\Lib\Sql\WhereBuilder();
31
32
		$this->fields = implode(',', array_map([$this->adapter, 'quote'], (array) $this->options->read('fields')));
33
34
		$defaultSort = $this->options->read('defaultSort') !== false ? $this->options->read('defaultSort')  : implode(', ', $this->primaryKey);
35
        $this->databaseModify = new DatabaseModify($this->adapter, $this->options->getEditMode(), $this->table);
36
        $this->databaseSelect = new DatabaseSelect($this->adapter, $this->databaseModify, $defaultSort, $this->table);
37
38
		$this->databaseModify->optimizeColumns();
39
	}
40
41
	public function getPrimaryKey() {
42
		return $this->primaryKey;
43
	}
44
45
	public function deleteById($id) {
46
		$this->adapter->query($this->crudBuilder->delete($this->table, [$this->primaryKey[0] . ' = :id'], [':id' => $id], 1));
47
		unset($this->cache[$id]);
48
	}
49
50
	public function findById($id) {
51
		if (!isset($this->cache[$id])) {
52
			try {
53
				$result = $this->selectQuery($this->selectBuilder->select($this->table, $this->getPrimaryKey()[0] . ' = :id', [':id' => $id], ['limit' => 1]));
54
			}
55
			catch (\Exception $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
56
			}
57
58
			if (isset($result[0])) 	$this->cache[$id] = $result[0];
59
			else return null;
60
		}
61
		return $this->cache[$id];
62
	}
63
64
	public function findAggregate($function, $field, $group = null, array $criteria = [], array $options = []) {
65
		//Cannot count/sum/max multiple fields, pick the first one. This should only come into play when trying to count() a mapper with multiple primary keys
66
		if (is_array($field)) $field = $field[0];
67
		$query = $this->whereBuilder->createSql($criteria);
68
69
		try {
70
			$this->databaseModify->addIndex(array_keys($query['args']));
71
			$this->databaseModify->addIndex(explode(',', $group));
72
			$result = $this->selectQuery($this->selectBuilder->aggregate($this->table, $function, $field, $query['sql'], $query['args'], $group));
73
74
			return $this->determineAggregateResult($result, $group, $field);
75
		}
76
		catch (\Exception $e) {
77
			return $group ? [] : 0;
78
		}
79
	}
80
81
    private function determineAggregateResult($result, $group, $field) {
82
        if ($group != null) {
83
            $ret = [];
84
            foreach ($result as $res) $ret[$res->$field] = $res->val;
85
            return $ret;
86
        }
87
        else if (isset($result[0])) return $result[0]->val;
88
        else return 0;
89
    }
90
91
	public function findByField(array $fields, $options = []) {
92
        return $this->databaseSelect->findByField($fields, $options);
93
	}
94
95
	public function deleteByField(array $fields, array $options = []) {
96
		$query = $this->whereBuilder->createSql($fields);
97
		$this->adapter->query($this->crudBuilder->delete($this->table, $query['sql'], $query['args'], $options['limit'], null, $options['order']));
98
		$this->databaseModify->addIndex(array_keys($query['args']));
99
100
		//Clear the cache
101
		$this->cache = [];
102
		$this->databaseSelect->clearResultCache();
103
	}
104
105
    private function getIfNew($data) {
106
        $new = false;
107
        foreach ($this->primaryKey as $k) {
108
            if (empty($data->$k)) {
109
                $data->$k = null;
110
                $new = true;
111
            }
112
        }
113
        return $new;
114
    }
115
116
	public function save($data, $tryagain = true) {
117
        $new = $this->getIfNew($data);
118
119
		try {
120
            $result = $this->insert($this->table, $this->primaryKey, $data);
121
122
			//If there was an error but PDO is silent, trigger the catch block anyway
123
			if ($result->errorCode() !== '00000') throw new \Exception('Could not insert into ' . $this->table);
124
		}
125
		catch (\Exception $e) {
126
			if (!$this->getTryAgain($tryagain)) throw $e;
127
128
			$this->adapter->alterDatabase($this->table, $this->primaryKey, $data);
129
			$this->save($data, false);
130
		}
131
132
		$this->updatePK($data, $new);
133
		//Something has changed, clear any cached results as they may now be incorrect
134
		$this->databaseSelect->clearResultCache();
135
		$this->updateCache($data);
136
	}
137
138
    private function getTryAgain($tryagain) {
139
        return $tryagain && self::EDIT_STRUCTURE & $this->alterDb;
0 ignored issues
show
Bug Best Practice introduced by
The property alterDb does not exist on Maphper\DataSource\Database. Did you maybe forget to declare it?
Loading history...
140
    }
141
142
    private function updatePK($data, $new) {
143
        if ($new && count($this->primaryKey) == 1) $data->{$this->primaryKey[0]} = $this->adapter->lastInsertId();
144
    }
145
146
    private function checkIfUpdateWorked($data) {
147
        $updateWhere = $this->whereBuilder->createSql($data);
148
        $matched = $this->findByField($updateWhere['args']);
149
        if (count($matched) == 0) throw new \InvalidArgumentException('Record inserted into table ' . $this->table . ' fails table constraints');
150
    }
151
152
    private function updateCache($data) {
153
        $pkValue = $data->{$this->primaryKey[0]};
154
		if (isset($this->cache[$pkValue])) $this->cache[$pkValue] = (object) array_merge((array)$this->cache[$pkValue], (array)$data);
155
		else $this->cache[$pkValue] = $data;
156
    }
157
158
	private function insert($table, array $primaryKey, $data) {
159
		$error = 0;
160
		try {
161
			$result = $this->adapter->query($this->crudBuilder->insert($table, $data));
162
		}
163
		catch (\Exception $e) {
164
			$error = 1;
165
		}
166
167
 		if ($error || $result->errorCode() !== '00000') {
168
            $result = $this->tryUpdate($table, $primaryKey, $data);
169
        }
170
171
		return $result;
172
	}
173
174
    private function tryUpdate($table, array $primaryKey, $data) {
175
        $result = $this->adapter->query($this->crudBuilder->update($table, $primaryKey, $data));
176
        if ($result->rowCount() === 0) $this->checkIfUpdateWorked($data);
177
178
        return $result;
179
    }
180
181
    private function selectQuery(\Maphper\Lib\Query $query) {
182
        return $this->adapter->query($query)->fetchAll(\PDO::FETCH_OBJ);
183
    }
184
}
185