1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* Created by Gorlum 08.01.2018 14:46 |
4
|
|
|
*/ |
5
|
|
|
|
6
|
|
|
namespace Core; |
7
|
|
|
|
8
|
|
|
|
9
|
|
|
use Common\Interfaces\IContainer; |
10
|
|
|
use \DBAL\DbQuery; |
11
|
|
|
use \DBAL\ActiveRecord; |
12
|
|
|
use Common\Traits\TContainer; |
13
|
|
|
use Exception; |
14
|
|
|
use SN; |
15
|
|
|
|
16
|
|
|
/** |
17
|
|
|
* Basic persistent entity class (lives in DB) |
18
|
|
|
* |
19
|
|
|
* Support direct ActiveRecord access |
20
|
|
|
* Support locked reads on transactions |
21
|
|
|
* |
22
|
|
|
* Represents in-game entity which have representation in DB (aka one or more connected ActiveRecords) |
23
|
|
|
* |
24
|
|
|
* @package Core |
25
|
|
|
* |
26
|
|
|
* @property int|string $id - bigint - |
27
|
|
|
* |
28
|
|
|
* @method array asArray() Extracts values as array [$propertyName => $propertyValue] (from ActiveRecord) |
29
|
|
|
* @method bool update() Updates DB record(s) in DB (from ActiveRecord) |
30
|
|
|
*/ |
31
|
|
|
class EntityDb extends Entity implements IContainer { |
32
|
|
|
use TContainer; |
33
|
|
|
|
34
|
|
|
/** |
35
|
|
|
* @var string $_activeClass |
36
|
|
|
*/ |
37
|
|
|
protected $_activeClass = ''; // \\DBAL\\ActiveRecord |
38
|
|
|
|
39
|
|
|
/** |
40
|
|
|
* Name translation table |
41
|
|
|
* |
42
|
|
|
* [entityPropertyName => containerPropertyName] |
43
|
|
|
* |
44
|
|
|
* @var string[] $_containerTranslateNames |
45
|
|
|
*/ |
46
|
|
|
protected $_containerTranslateNames = []; |
47
|
|
|
|
48
|
|
|
|
49
|
|
|
/** |
50
|
|
|
* @var ActiveRecord $_container |
51
|
|
|
*/ |
52
|
|
|
protected $_container; |
53
|
|
|
|
54
|
|
|
protected $_isNew = true; |
55
|
|
|
protected $_forUpdate = DbQuery::DB_SHARED; |
56
|
|
|
|
57
|
|
|
/** |
58
|
|
|
* EntityDb constructor. |
59
|
|
|
*/ |
60
|
|
|
public function __construct() { |
61
|
|
|
if (empty($this->_activeClass)) { |
62
|
|
|
/** @noinspection PhpUnhandledExceptionInspection */ |
63
|
|
|
throw new Exception("Class " . get_called_class() . " have no _activeClass property declared!"); |
64
|
|
|
} |
65
|
|
|
|
66
|
|
|
$this->reset(); |
67
|
|
|
} |
68
|
|
|
|
69
|
|
|
/** |
70
|
|
|
* @return $this |
71
|
|
|
*/ |
72
|
|
|
public function reset() { |
73
|
|
|
// unset($this->_container); // Strange thing - after this string it is not possible to set _container property again! |
|
|
|
|
74
|
|
|
$this->_container = new $this->_activeClass(); |
75
|
|
|
|
76
|
|
|
$this->_isNew = true; |
77
|
|
|
$this->_forUpdate = DbQuery::DB_SHARED; |
78
|
|
|
|
79
|
|
|
return $this; |
80
|
|
|
} |
81
|
|
|
|
82
|
|
|
/** |
83
|
|
|
* Reload Entity - if possible |
84
|
|
|
* |
85
|
|
|
* If entity is write-once - this method should decide if any real I/O would be performed |
86
|
|
|
* |
87
|
|
|
* @return static |
88
|
|
|
* |
89
|
|
|
* @throws Exception |
90
|
|
|
*/ |
91
|
|
|
public function reload() { |
92
|
|
|
// TODO - immutable entities which does not need reload |
93
|
|
|
$dbId = !empty($this->_container) ? $this->_container->id : 0; |
94
|
|
|
|
95
|
|
|
// If entity is new or no DB ID supplied - throw exception |
96
|
|
|
if ($this->isNew() || !$dbId) { |
97
|
|
|
/** @noinspection PhpUnhandledExceptionInspection */ |
98
|
|
|
throw new Exception("Can't reload empty or new Entity: isNew = '{$this->isNew()}', dbId = '{$dbId}', className = " . get_called_class()); |
99
|
|
|
} |
100
|
|
|
|
101
|
|
|
return $this->dbLoadRecord($dbId); |
|
|
|
|
102
|
|
|
} |
103
|
|
|
|
104
|
|
|
|
105
|
|
|
/** |
106
|
|
|
* Is entity is new |
107
|
|
|
* |
108
|
|
|
* True for newly created and deleted entities |
109
|
|
|
* Code should decide this from context |
110
|
|
|
* |
111
|
|
|
* @return bool |
112
|
|
|
*/ |
113
|
|
|
public function isNew() { |
114
|
|
|
return $this->_isNew; |
115
|
|
|
} |
116
|
|
|
|
117
|
|
|
/** |
118
|
|
|
* Load entity from DB |
119
|
|
|
* |
120
|
|
|
* This basic class supports transaction locking |
121
|
|
|
* |
122
|
|
|
* If entity is multi-tabled - this method should care about loading and arrange all necessary information |
123
|
|
|
* If descendant entity is lock-sensitive - this method should issue all necessary locks (including parental locks) |
124
|
|
|
* |
125
|
|
|
* @param int|float $id |
126
|
|
|
* |
127
|
|
|
* @return $this |
128
|
|
|
*/ |
129
|
|
|
public function dbLoadRecord($id) { |
130
|
|
|
$this->reset(); |
131
|
|
|
|
132
|
|
|
if (sn_db_transaction_check(false)) { |
133
|
|
|
$this->setForUpdate(); |
134
|
|
|
} |
135
|
|
|
|
136
|
|
|
/** @var ActiveRecord $className */ |
137
|
|
|
$className = $this->_activeClass; |
138
|
|
|
$container = $className::findById($id); |
139
|
|
|
if (!empty($container)) { |
140
|
|
|
$this->_isNew = false; |
141
|
|
|
$this->_container = $container; |
142
|
|
|
} |
143
|
|
|
|
144
|
|
|
return $this; |
145
|
|
|
} |
146
|
|
|
|
147
|
|
|
/** |
148
|
|
|
* Set "for update" flag |
149
|
|
|
* |
150
|
|
|
* @param bool $forUpdate - DbQuery::DB_FOR_UPDATE | DbQuery::DB_SHARED |
151
|
|
|
* |
152
|
|
|
* @return $this |
153
|
|
|
*/ |
154
|
|
|
public function setForUpdate($forUpdate = DbQuery::DB_FOR_UPDATE) { |
155
|
|
|
/** @var ActiveRecord $className */ |
156
|
|
|
$className = $this->_activeClass; |
157
|
|
|
$className::setForUpdate($forUpdate); |
158
|
|
|
|
159
|
|
|
return $this; |
160
|
|
|
} |
161
|
|
|
|
162
|
|
|
/** |
163
|
|
|
* @return bool |
164
|
|
|
*/ |
165
|
|
|
public function dbUpdate() { |
166
|
|
|
return $this->_getContainer()->update(); |
167
|
|
|
} |
168
|
|
|
|
169
|
|
|
/** |
170
|
|
|
* @return ActiveRecord |
171
|
|
|
*/ |
172
|
|
|
public function _getContainer() { |
173
|
|
|
return $this->_container; |
174
|
|
|
} |
175
|
|
|
|
176
|
|
|
/** |
177
|
|
|
* Translate entity property name to container property name |
178
|
|
|
* |
179
|
|
|
* Just a little sugar to avoid renaming all and everywhere |
180
|
|
|
* |
181
|
|
|
* @param string $name |
182
|
|
|
* |
183
|
|
|
* @return string |
184
|
|
|
*/ |
185
|
|
|
protected function _containerTranslatePropertyName($name) { |
186
|
|
|
return !empty($this->_containerTranslateNames[$name]) ? $this->_containerTranslateNames[$name] : $name; |
187
|
|
|
} |
188
|
|
|
|
189
|
|
|
/** |
190
|
|
|
* Saves entity to DB. Also handles updates (and in future - deletes. DELETE CURRENTLY NOT SUPPORTED!) |
191
|
|
|
* |
192
|
|
|
* @return bool |
193
|
|
|
* @throws \Exception |
194
|
|
|
*/ |
195
|
|
|
public function save() { |
196
|
|
|
$result = false; |
197
|
|
|
|
198
|
|
|
if ($this->isNew()) { |
199
|
|
|
// New record - INSERT |
200
|
|
|
|
201
|
|
|
// TODO - some checks that fleet recrod is valid itself |
202
|
|
|
// May be something like method isValid()? |
203
|
|
|
|
204
|
|
|
$result = !$this->isEmpty() && $this->_getContainer()->insert() && SN::$gc->repoV2->set($this); |
205
|
|
|
} elseif (!$this->isEmpty()) { |
206
|
|
|
// Record not new and not empty - UPDATE |
207
|
|
|
$result = $this->_getContainer()->update(); |
208
|
|
|
} else { |
|
|
|
|
209
|
|
|
// Record not new and empty - DELETE |
210
|
|
|
|
211
|
|
|
} |
212
|
|
|
|
213
|
|
|
return $result; |
214
|
|
|
} |
215
|
|
|
|
216
|
|
|
} |
217
|
|
|
|
Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.
The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.
This check looks for comments that seem to be mostly valid code and reports them.