1
|
|
|
<?php declare(strict_types=1); |
2
|
|
|
namespace samsonframework\orm; |
3
|
|
|
|
4
|
|
|
use samsonframework\core\RenderInterface; |
5
|
|
|
|
6
|
|
|
/** |
7
|
|
|
* ORM Active record class. |
8
|
|
|
* |
9
|
|
|
* @author Vitaly Iegorov <[email protected]> |
10
|
|
|
* @author Nikita Kotenko <[email protected]> |
11
|
|
|
* |
12
|
|
|
* TODO: Remove render interface |
13
|
|
|
* TODO: Remove ArrayAccess interface |
14
|
|
|
* TODO: Remove activerecord pattern from entity |
15
|
|
|
*/ |
16
|
|
|
class Record implements RenderInterface, \ArrayAccess, RecordInterface |
17
|
|
|
{ |
18
|
|
|
/** |
19
|
|
|
* @var array Collection of instances for caching |
20
|
|
|
* @deprecated |
21
|
|
|
*/ |
22
|
|
|
public static $instances = array(); |
23
|
|
|
|
24
|
|
|
/** |
25
|
|
|
* Collection of class fields that would not be passed to module view |
26
|
|
|
* @deprecated |
27
|
|
|
*/ |
28
|
|
|
public static $restricted = array('attached', 'oneToOne', 'oneToMany', 'value'); |
29
|
|
|
|
30
|
|
|
/** |
31
|
|
|
* @var array Collection of joined entities |
32
|
|
|
* @deprecated |
33
|
|
|
*/ |
34
|
|
|
public $joined = []; |
35
|
|
|
/** |
36
|
|
|
* @var string Entity class name |
37
|
|
|
* @deprecated |
38
|
|
|
*/ |
39
|
|
|
public $className; |
40
|
|
|
/** |
41
|
|
|
* @var array Related OTO records grouped by entity class name |
42
|
|
|
* @deprecated |
43
|
|
|
*/ |
44
|
|
|
public $oneToOne = array(); |
45
|
|
|
/** |
46
|
|
|
* @var array Related OTM records grouped by entity class name |
47
|
|
|
* @deprecated |
48
|
|
|
*/ |
49
|
|
|
public $oneToMany = array(); |
50
|
|
|
/** |
51
|
|
|
* @var bool Flag if this object has a database record |
52
|
|
|
* @deprecated |
53
|
|
|
*/ |
54
|
|
|
public $attached = false; |
55
|
|
|
|
56
|
|
|
/** @var DatabaseInterface Database layer */ |
57
|
|
|
protected $database; |
58
|
|
|
/** @var TableMetadata */ |
59
|
|
|
protected $metadata; |
60
|
|
|
/** @var int Identifier */ |
61
|
|
|
public $id; |
62
|
|
|
|
63
|
|
|
/** |
64
|
|
|
* Record constructor. |
65
|
|
|
* |
66
|
|
|
* @param DatabaseInterface|null $database |
67
|
|
|
*/ |
68
|
|
|
public function __construct(DatabaseInterface $database = null, TableMetadata $metadata = null) |
69
|
|
|
{ |
70
|
|
|
// Get database layer |
71
|
|
|
$this->database = $database; |
72
|
|
|
|
73
|
|
|
// TODO: !IMPORTANT THIS NEEDS TO BE REMOVED! |
74
|
|
|
// FIXME: Dependency resolving |
75
|
|
|
if (array_key_exists('__core', $GLOBALS)) { |
76
|
|
|
$this->database = $GLOBALS['__core']->getContainer()->get('database'); |
77
|
|
|
} |
78
|
|
|
|
79
|
|
|
// Get current class name if none is passed |
80
|
|
|
$this->className = get_class($this); |
|
|
|
|
81
|
|
|
|
82
|
|
|
// Get table metadata |
83
|
|
|
// FIXME: Dependency resolving |
84
|
|
|
$this->metadata = $metadata ?? TableMetadata::fromClassName($this->className); |
|
|
|
|
85
|
|
|
} |
86
|
|
|
|
87
|
|
|
/** |
88
|
|
|
* Find database record by primary key value. |
89
|
|
|
* This is generic method that should be used in nested classes to find its |
90
|
|
|
* records by some its primary key value. |
91
|
|
|
* |
92
|
|
|
* @param QueryInterface $query Query object instance |
93
|
|
|
* @param string $identifier Primary key value |
94
|
|
|
* @param mixed $return Variable to return found database record |
95
|
|
|
* @return bool|null|self Record instance or null if 3rd parameter not passed |
96
|
|
|
* @deprecated Record should not be queryable, query class ancestor must be used |
97
|
|
|
*/ |
98
|
|
|
public static function byID(QueryInterface $query, $identifier, &$return = null) |
99
|
|
|
{ |
100
|
|
|
/** @var Field $record Cache field object */ |
101
|
|
|
$return = isset(self::$instances[$identifier]) |
|
|
|
|
102
|
|
|
// Get record from cache by identifier |
103
|
|
|
? self::$instances[$identifier] |
|
|
|
|
104
|
|
|
// Find record by identifier |
105
|
|
|
: self::$instances[$identifier] = static::oneByColumn( |
|
|
|
|
106
|
|
|
$query, |
107
|
|
|
static::$_primary, |
108
|
|
|
$identifier |
109
|
|
|
); |
110
|
|
|
|
111
|
|
|
// Return bool or record depending on parameters passed |
112
|
|
|
return func_num_args() > 2 ? isset($return) : $return; |
113
|
|
|
} |
114
|
|
|
|
115
|
|
|
/** |
116
|
|
|
* Find database record by column name and its value. |
117
|
|
|
* This is generic method that should be used in nested classes to find its |
118
|
|
|
* records by some its column values. |
119
|
|
|
* |
120
|
|
|
* @param QueryInterface $query Query object instance |
121
|
|
|
* @param string $columnValue Column name for searching in calling class |
122
|
|
|
* @param string $columnName Column value |
123
|
|
|
* @return null|self Record instance if it was found and 4th variable has NOT been passed, |
124
|
|
|
* NULL if record has NOT been found and 4th variable has NOT been passed |
125
|
|
|
* @deprecated Record should not be queryable, query class ancestor must be used |
126
|
|
|
*/ |
127
|
|
|
public static function oneByColumn(QueryInterface $query, $columnName, $columnValue) |
128
|
|
|
{ |
129
|
|
|
// Perform db request and get materials |
130
|
|
|
return $query->entity(get_called_class()) |
131
|
|
|
->where($columnName, $columnValue) |
132
|
|
|
->first(); |
133
|
|
|
} |
134
|
|
|
|
135
|
|
|
/** |
136
|
|
|
* Find database record collection by column name and its value. |
137
|
|
|
* This is generic method that should be used in nested classes to find its |
138
|
|
|
* records by some its column values. |
139
|
|
|
* |
140
|
|
|
* @param QueryInterface $query Query object instance |
141
|
|
|
* @param string $columnName Column name for searching in calling class |
142
|
|
|
* @param mixed $columnValue Column value |
143
|
|
|
* @return self[] Record instance if it was found and 4th variable has NOT been passed, |
144
|
|
|
* NULL if record has NOT been found and 4th variable has NOT been passed |
145
|
|
|
* @deprecated Record should not be queryable, query class ancestor must be used |
146
|
|
|
*/ |
147
|
|
|
public static function collectionByColumn(QueryInterface $query, $columnName, $columnValue) |
148
|
|
|
{ |
149
|
|
|
// Perform db request and get materials |
150
|
|
|
return $query->className(get_called_class()) |
151
|
|
|
->cond($columnName, $columnValue) |
152
|
|
|
->exec(); |
153
|
|
|
} |
154
|
|
|
|
155
|
|
|
/** @see idbRecord::delete() */ |
156
|
|
|
public function delete() |
157
|
|
|
{ |
158
|
|
|
// Если запись привязана к БД то удалим её оттуда |
159
|
|
|
if ($this->attached) { |
|
|
|
|
160
|
|
|
$this->database->delete($this->className, $this); |
|
|
|
|
161
|
|
|
} |
162
|
|
|
} |
163
|
|
|
|
164
|
|
|
/** Special method called when object has been filled with data */ |
165
|
|
|
public function filled() |
166
|
|
|
{ |
167
|
|
|
|
168
|
|
|
} |
169
|
|
|
|
170
|
|
|
/** |
171
|
|
|
* Обработчик клонирования записи |
172
|
|
|
* Этот метод выполняется при системном вызове функции clone |
173
|
|
|
* и выполняет создание записи в БД и привязку клонированного объекта к ней |
174
|
|
|
* @deprecated Data entites should be cloned normally |
175
|
|
|
*/ |
176
|
|
|
public function __clone() |
177
|
|
|
{ |
178
|
|
|
// Выполним создание записи в БД |
179
|
|
|
$this->id = $this->database->create($this->className, $this); |
|
|
|
|
180
|
|
|
|
181
|
|
|
// Установим флаг что мы привязались к БД |
182
|
|
|
$this->attached = true; |
|
|
|
|
183
|
|
|
|
184
|
|
|
// Сохраним запись в БД |
185
|
|
|
$this->save(); |
186
|
|
|
} |
187
|
|
|
|
188
|
|
|
/** |
189
|
|
|
* @see idbRecord::save() |
190
|
|
|
*/ |
191
|
|
|
public function save() |
192
|
|
|
{ |
193
|
|
|
$attributes = []; |
194
|
|
|
foreach ($this->metadata->columnAliases as $columnAlias => $columnName) { |
195
|
|
|
$attributes[$columnName] = $this->$columnAlias; |
196
|
|
|
} |
197
|
|
|
|
198
|
|
|
if ($this->attached) { |
|
|
|
|
199
|
|
|
$this->database->update($this->metadata, $attributes); |
200
|
|
|
} else { |
201
|
|
|
$newIdentifier = $this->database->insert($this->metadata, $attributes); |
202
|
|
|
|
203
|
|
|
$rows = $this->database->fetchArray( |
204
|
|
|
'SELECT * FROM `' . $this->metadata->tableName . '` WHERE ' . |
205
|
|
|
'`' . $this->metadata->tableName . '`.`' . $this->metadata->primaryField . '` = ' . $newIdentifier |
206
|
|
|
); |
207
|
|
|
|
208
|
|
|
if (count($rows)) { |
209
|
|
|
foreach ($this->metadata->columnAliases as $columnAlias => $columnName) { |
210
|
|
|
$this->$columnAlias = $rows[0][$columnName]; |
211
|
|
|
} |
212
|
|
|
} else { |
213
|
|
|
throw new \InvalidArgumentException('Failed ' . get_class($this) . ' entoty creation'); |
214
|
|
|
} |
215
|
|
|
} |
216
|
|
|
|
217
|
|
|
// Store instance in cache |
218
|
|
|
self::$instances[$this->className][$this->id] = &$this; |
|
|
|
|
219
|
|
|
} |
220
|
|
|
|
221
|
|
|
/** |
222
|
|
|
* @see idbRecord::create() |
223
|
|
|
* @deprecated |
224
|
|
|
*/ |
225
|
|
|
public function create() |
226
|
|
|
{ |
227
|
|
|
} |
228
|
|
|
|
229
|
|
|
/** |
230
|
|
|
* @see \samson\core\iModuleViewable::toView() |
231
|
|
|
* @deprecated |
232
|
|
|
*/ |
233
|
|
|
public function toView($prefix = null, array $restricted = array()) |
234
|
|
|
{ |
235
|
|
|
// Create resulting view data array, add identifier field |
236
|
|
|
$values = array($prefix . 'id' => $this->id); |
237
|
|
|
|
238
|
|
|
// Учтем поля которые не нужно превращать в массив |
239
|
|
|
$restricted = array_merge(self::$restricted, $restricted); |
|
|
|
|
240
|
|
|
|
241
|
|
|
// Пробежимся по переменным класса |
242
|
|
|
foreach (get_object_vars($this) as $var => $value) { |
243
|
|
|
// Если это не системное поле записи - запишем его значение |
244
|
|
|
if (!in_array($var, $restricted)) { |
245
|
|
|
$values[$prefix . $var] = is_string($value) ? trim($value) : $value; |
246
|
|
|
} |
247
|
|
|
} |
248
|
|
|
|
249
|
|
|
// Вернем массив атрибутов представляющий запись БД |
250
|
|
|
return $values; |
251
|
|
|
} |
252
|
|
|
|
253
|
|
|
/** |
254
|
|
|
* Create full entity copy from |
255
|
|
|
* @param mixed $object Variable to return copied object |
256
|
|
|
* @return Record New copied object |
257
|
|
|
* @deprecated Not supported yet |
258
|
|
|
*/ |
259
|
|
|
public function ©(&$object = null) |
260
|
|
|
{ |
261
|
|
|
// Get current entity class |
262
|
|
|
$entity = get_class($this); |
263
|
|
|
|
264
|
|
|
// Create object instance |
265
|
|
|
$object = new $entity(false); |
266
|
|
|
|
267
|
|
|
// PHP 5.2 compliant get attributes |
268
|
|
|
$attributes = array(); |
269
|
|
|
eval('$attributes = ' . $entity . '::$_attributes;'); |
270
|
|
|
|
271
|
|
|
// Iterate all object attributes |
272
|
|
|
foreach ($attributes as $attribute) { |
273
|
|
|
// If we have this attribute set |
274
|
|
|
if (isset($this[$attribute])) { |
275
|
|
|
// Store it in copied object |
276
|
|
|
$object[$attribute] = $this[$attribute]; |
277
|
|
|
} |
278
|
|
|
} |
279
|
|
|
|
280
|
|
|
// Save object in database |
281
|
|
|
$object->save(); |
282
|
|
|
|
283
|
|
|
// Return created copied object |
284
|
|
|
return $object; |
285
|
|
|
} |
286
|
|
|
|
287
|
|
|
public function __isset($name) |
288
|
|
|
{ |
289
|
|
|
return $this->offsetExists($name); |
290
|
|
|
} |
291
|
|
|
|
292
|
|
|
public function __set($name, $value) |
293
|
|
|
{ |
294
|
|
|
$this->offsetSet($name, $value); |
295
|
|
|
} |
296
|
|
|
|
297
|
|
|
public function __get($name) |
298
|
|
|
{ |
299
|
|
|
return $this->offsetGet($name); |
300
|
|
|
} |
301
|
|
|
|
302
|
|
|
/** |
303
|
|
|
* @see ArrayAccess::offsetSet() |
304
|
|
|
* @throws \InvalidArgumentException |
305
|
|
|
*/ |
306
|
|
|
public function offsetSet($offset, $value) |
307
|
|
|
{ |
308
|
|
|
if ($this->metadata->isColumnExists($offset)) { |
309
|
|
|
$fieldName = $this->metadata->getTableColumnAlias($offset); |
310
|
|
|
$this->$fieldName = $value; |
311
|
|
|
} else { |
312
|
|
|
$this->$offset = $value; |
313
|
|
|
} |
314
|
|
|
} |
315
|
|
|
|
316
|
|
|
/** |
317
|
|
|
* @see ArrayAccess::offsetGet() |
318
|
|
|
* @throws \InvalidArgumentException |
319
|
|
|
*/ |
320
|
|
|
public function offsetGet($offset) |
321
|
|
|
{ |
322
|
|
|
if ($this->metadata->isColumnExists($offset)) { |
323
|
|
|
$fieldName = $this->metadata->getTableColumnAlias($offset); |
324
|
|
|
return $this->$fieldName; |
325
|
|
|
} |
326
|
|
|
|
327
|
|
|
return $this->$offset; |
328
|
|
|
} |
329
|
|
|
|
330
|
|
|
/** @see ArrayAccess::offsetUnset() */ |
331
|
|
|
public function offsetUnset($offset) |
332
|
|
|
{ |
333
|
|
|
unset($this->$offset); |
334
|
|
|
} |
335
|
|
|
|
336
|
|
|
/** @see ArrayAccess::offsetExists() */ |
337
|
|
|
public function offsetExists($offset) |
338
|
|
|
{ |
339
|
|
|
return property_exists($this, $offset) || $this->metadata->isColumnExists($offset); |
340
|
|
|
} |
341
|
|
|
} |
342
|
|
|
|
This property has been deprecated. The supplier of the class has supplied an explanatory message.
The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.