1
|
|
|
<?php |
2
|
|
|
namespace Fwk\Db\Registry; |
3
|
|
|
|
4
|
|
|
use Fwk\Db\Accessor; |
5
|
|
|
use Fwk\Db\Connection; |
6
|
|
|
use Fwk\Db\Events\AbstractEntityEvent; |
7
|
|
|
use Fwk\Db\Events\AfterSaveEvent; |
8
|
|
|
use Fwk\Db\Events\FreshEvent; |
9
|
|
|
use Fwk\Db\EventSubscriberInterface; |
10
|
|
|
use Fwk\Db\Exception; |
11
|
|
|
use Fwk\Db\Exceptions\UnregisteredEntityException; |
12
|
|
|
use Fwk\Db\Table; |
13
|
|
|
use Fwk\Db\Workers\DeleteEntityWorker; |
14
|
|
|
use Fwk\Db\Workers\SaveEntityWorker; |
15
|
|
|
use Fwk\Events\Dispatcher; |
16
|
|
|
use Fwk\Events\Event; |
17
|
|
|
use \SplObjectStorage; |
18
|
|
|
|
19
|
|
|
class Registry implements \Countable, \IteratorAggregate |
20
|
|
|
{ |
21
|
|
|
const ACTION_SAVE = 'save'; |
22
|
|
|
const ACTION_DELETE = 'delete'; |
23
|
|
|
|
24
|
|
|
/** |
25
|
|
|
* Storage handler |
26
|
|
|
* |
27
|
|
|
* @var SplObjectStorage |
28
|
|
|
*/ |
29
|
|
|
protected $store; |
30
|
|
|
|
31
|
|
|
/** |
32
|
|
|
* Table name |
33
|
|
|
* |
34
|
|
|
* @var string |
35
|
|
|
*/ |
36
|
|
|
protected $tableName; |
37
|
|
|
|
38
|
|
|
/** |
39
|
|
|
* @var integer |
40
|
|
|
*/ |
41
|
|
|
protected $_priority = \PHP_INT_MAX; |
|
|
|
|
42
|
|
|
|
43
|
|
|
/** |
44
|
|
|
* Constructor |
45
|
|
|
* |
46
|
|
|
* @param string $tableName |
47
|
|
|
* |
48
|
|
|
* @return void |
|
|
|
|
49
|
|
|
*/ |
50
|
|
|
public function __construct($tableName) |
51
|
|
|
{ |
52
|
|
|
$this->tableName = $tableName; |
53
|
|
|
$this->store = new SplObjectStorage(); |
54
|
|
|
} |
55
|
|
|
|
56
|
|
|
/** |
57
|
|
|
* Stores an object into registry |
58
|
|
|
* |
59
|
|
|
* @param mixed $object |
60
|
|
|
* |
61
|
|
|
* @return Entry |
62
|
|
|
*/ |
63
|
|
|
public function store($object, array $identifiers = array(), $state = RegistryState::UNKNOWN, array $data = array()) |
64
|
|
|
{ |
65
|
|
|
if ($this->contains($object)) { |
66
|
|
|
return $this; |
|
|
|
|
67
|
|
|
} |
68
|
|
|
|
69
|
|
|
$entry = Entry::factory($object, $identifiers, $state, $data); |
70
|
|
|
|
71
|
|
|
$dispatcher = $entry->data('dispatcher', new Dispatcher()); |
72
|
|
|
$listeners = $entry->data('listeners', array()); |
73
|
|
|
|
74
|
|
|
/** |
75
|
|
|
* @todo Put this one elsewhere |
76
|
|
|
*/ |
77
|
|
|
$dispatcher->on(AfterSaveEvent::EVENT_NAME, array($this, 'getLastInsertId')); |
78
|
|
|
|
79
|
|
|
$dispatcher->addListener($object); |
80
|
|
|
if ($object instanceof EventSubscriberInterface) { |
81
|
|
|
foreach ($object->getListeners() as $key => $listener) { |
82
|
|
|
if (is_object($listener) && !is_callable($listener)) { |
83
|
|
|
$dispatcher->addListener($listener); |
84
|
|
|
} elseif (is_callable($listener)) { |
85
|
|
|
$dispatcher->on($key, $listener); |
86
|
|
|
} |
87
|
|
|
} |
88
|
|
|
} |
89
|
|
|
|
90
|
|
|
foreach ($listeners as $key => $listener) { |
91
|
|
|
if (is_object($listener) && !is_callable($listener)) { |
92
|
|
|
$dispatcher->addListener($listener); |
93
|
|
|
} elseif (is_callable($listener)) { |
94
|
|
|
$dispatcher->on($key, $listener); |
95
|
|
|
} |
96
|
|
|
} |
97
|
|
|
|
98
|
|
|
$this->store->attach($entry); |
99
|
|
|
|
100
|
|
|
return $entry; |
101
|
|
|
} |
102
|
|
|
|
103
|
|
|
/** |
104
|
|
|
* Fetches an object |
105
|
|
|
* |
106
|
|
|
* @param array $identifiers |
107
|
|
|
* |
108
|
|
|
* @return object|null |
109
|
|
|
*/ |
110
|
|
|
public function get(array $identifiers, $entityClass = null) |
111
|
|
|
{ |
112
|
|
|
foreach ($this->store as $entry) { |
113
|
|
|
if ($entry->match($identifiers, $entityClass)) { |
114
|
|
|
return $entry->getObject(); |
115
|
|
|
} |
116
|
|
|
} |
117
|
|
|
|
118
|
|
|
return null; |
119
|
|
|
} |
120
|
|
|
|
121
|
|
|
/** |
122
|
|
|
* Returns the table name |
123
|
|
|
* |
124
|
|
|
* @return string |
125
|
|
|
*/ |
126
|
|
|
public function getTableName() |
127
|
|
|
{ |
128
|
|
|
return $this->tableName; |
129
|
|
|
} |
130
|
|
|
|
131
|
|
|
|
132
|
|
|
/** |
133
|
|
|
* |
134
|
|
|
* @param mixed $object |
135
|
|
|
* |
136
|
|
|
* @return Entry |
137
|
|
|
*/ |
138
|
|
|
public function getEntry($object) |
139
|
|
|
{ |
140
|
|
|
foreach ($this->store as $entry) { |
141
|
|
|
if ($entry->matchObject($object)) { |
142
|
|
|
return $entry; |
143
|
|
|
} |
144
|
|
|
} |
145
|
|
|
|
146
|
|
|
return false; |
|
|
|
|
147
|
|
|
} |
148
|
|
|
|
149
|
|
|
/** |
150
|
|
|
* |
151
|
|
|
* @return Entry |
152
|
|
|
*/ |
153
|
|
|
protected function getEntryByIdentifiers(array $identifiers, $className = null) |
154
|
|
|
{ |
155
|
|
|
foreach ($this->store as $entry) { |
156
|
|
|
if ($entry->match($identifiers, $className)) { |
157
|
|
|
return $entry; |
158
|
|
|
} |
159
|
|
|
} |
160
|
|
|
|
161
|
|
|
return false; |
|
|
|
|
162
|
|
|
} |
163
|
|
|
|
164
|
|
|
/** |
165
|
|
|
* Returns an Event Dispatcher attached to a stored object |
166
|
|
|
* |
167
|
|
|
* @return Dispatcher |
168
|
|
|
* @throws UnregisteredEntityException if the $object is not registered |
169
|
|
|
*/ |
170
|
|
|
public function getEventDispatcher($object) |
171
|
|
|
{ |
172
|
|
|
$entry = $this->getEntry($object); |
173
|
|
|
if ($entry === false) { |
174
|
|
|
throw new UnregisteredEntityException(sprintf('Unregistered entity (%s)', get_class($object))); |
175
|
|
|
} |
176
|
|
|
|
177
|
|
|
return $entry->data('dispatcher', new Dispatcher()); |
178
|
|
|
} |
179
|
|
|
|
180
|
|
|
/** |
181
|
|
|
* |
182
|
|
|
* @param mixed $object |
183
|
|
|
* @param \Fwk\Events\Event $event |
184
|
|
|
* |
185
|
|
|
* @return void |
186
|
|
|
*/ |
187
|
|
|
public function fireEvent($object, Event $event) |
188
|
|
|
{ |
189
|
|
|
$this->getEventDispatcher($object)->notify($event); |
190
|
|
|
} |
191
|
|
|
|
192
|
|
|
/** |
193
|
|
|
* Listener to fetch last insert ID on auto-increment columns |
194
|
|
|
* |
195
|
|
|
* @param AbstractEntityEvent $event |
196
|
|
|
* |
197
|
|
|
* @return void |
198
|
|
|
*/ |
199
|
|
|
public function getLastInsertId(AbstractEntityEvent $event) |
200
|
|
|
{ |
201
|
|
|
$connx = $event->getConnection(); |
202
|
|
|
$table = $connx->table($this->getTableName()); |
203
|
|
|
$obj = $event->getEntity(); |
204
|
|
|
$entry = $this->getEntry($obj); |
205
|
|
|
|
206
|
|
|
if (false === $entry) { |
207
|
|
|
return; |
208
|
|
|
} |
209
|
|
|
|
210
|
|
|
foreach ($table->getColumns() as $column) { |
211
|
|
|
if (!$column->getAutoincrement()) { |
212
|
|
|
continue; |
213
|
|
|
} |
214
|
|
|
|
215
|
|
|
$columnName = $column->getName(); |
216
|
|
|
$access = Accessor::factory($obj); |
217
|
|
|
$test = $access->get($columnName); |
218
|
|
|
|
219
|
|
|
if (!empty($test)) { |
220
|
|
|
continue; |
221
|
|
|
} |
222
|
|
|
|
223
|
|
|
$lastInsertId = $connx->lastInsertId(); |
224
|
|
|
$access->set($columnName, $lastInsertId); |
225
|
|
|
$ids = $entry->getIdentifiers(); |
226
|
|
|
$ids[$columnName] = $lastInsertId; |
227
|
|
|
$entry->setIdentifiers($ids)->fresh(); |
228
|
|
|
} |
229
|
|
|
} |
230
|
|
|
|
231
|
|
|
/** |
232
|
|
|
* Removes an object from the registry |
233
|
|
|
* |
234
|
|
|
* @param mixed $object |
235
|
|
|
* |
236
|
|
|
* @return Registry |
237
|
|
|
* @throws UnregisteredEntityException if the $object is not registered |
238
|
|
|
*/ |
239
|
|
|
public function remove($object) |
240
|
|
|
{ |
241
|
|
|
$entry = $this->getEntry($object); |
242
|
|
|
if ($entry === false) { |
243
|
|
|
throw new UnregisteredEntityException(sprintf('Unregistered entity (%s)', get_class($object))); |
244
|
|
|
} |
245
|
|
|
|
246
|
|
|
$this->store->detach($entry); |
247
|
|
|
unset($entry); |
248
|
|
|
|
249
|
|
|
return $this; |
250
|
|
|
} |
251
|
|
|
|
252
|
|
|
/** |
253
|
|
|
* Removes an object from its identifiers |
254
|
|
|
* |
255
|
|
|
* @param array $identifiers |
256
|
|
|
* @param string $className |
|
|
|
|
257
|
|
|
* |
258
|
|
|
* @return Registry |
259
|
|
|
*/ |
260
|
|
|
public function removeByIdentifiers(array $identifiers, $className = null) |
261
|
|
|
{ |
262
|
|
|
$entry = $this->getEntryByIdentifiers($identifiers, $className); |
263
|
|
|
if (false !== $entry) { |
264
|
|
|
$this->store->detach($entry); |
265
|
|
|
unset($entry); |
266
|
|
|
} |
267
|
|
|
|
268
|
|
|
return $this; |
269
|
|
|
} |
270
|
|
|
|
271
|
|
|
/** |
272
|
|
|
* Tells if the registry contains an instance of the object |
273
|
|
|
* |
274
|
|
|
* @param mixed $object |
275
|
|
|
* |
276
|
|
|
* @return boolean |
277
|
|
|
*/ |
278
|
|
|
public function contains($object) |
|
|
|
|
279
|
|
|
{ |
280
|
|
|
return false !== $this->getEntry($object); |
281
|
|
|
} |
282
|
|
|
|
283
|
|
|
/** |
284
|
|
|
* |
285
|
|
|
* @param object $object |
286
|
|
|
* |
287
|
|
|
* @return integer |
288
|
|
|
* @throws UnregisteredEntityException |
289
|
|
|
*/ |
290
|
|
|
public function getState($object) |
291
|
|
|
{ |
292
|
|
|
$entry = $this->getEntry($object); |
293
|
|
|
if ($entry === false) { |
294
|
|
|
return RegistryState::UNREGISTERED; |
295
|
|
|
} |
296
|
|
|
|
297
|
|
|
return $entry->getState(); |
298
|
|
|
} |
299
|
|
|
|
300
|
|
|
/** |
301
|
|
|
* |
302
|
|
|
* @return array |
303
|
|
|
*/ |
304
|
|
|
public function toArray() |
305
|
|
|
{ |
306
|
|
|
$arr = array(); |
307
|
|
|
foreach ($this->store as $entry) { |
308
|
|
|
$arr[] = $entry->getObject(); |
309
|
|
|
} |
310
|
|
|
|
311
|
|
|
return $arr; |
312
|
|
|
} |
313
|
|
|
|
314
|
|
|
/** |
315
|
|
|
* |
316
|
|
|
* @return integer |
317
|
|
|
*/ |
318
|
|
|
public function count() |
319
|
|
|
{ |
320
|
|
|
return count($this->store); |
321
|
|
|
} |
322
|
|
|
|
323
|
|
|
/** |
324
|
|
|
* |
325
|
|
|
* @return \ArrayIterator |
326
|
|
|
*/ |
327
|
|
|
public function getIterator() |
328
|
|
|
{ |
329
|
|
|
return new \ArrayIterator($this->toArray()); |
330
|
|
|
} |
331
|
|
|
|
332
|
|
|
|
333
|
|
|
public function markForAction($object, $action, array $listeners = array()) |
334
|
|
|
{ |
335
|
|
|
$entry = $this->getEntry($object); |
336
|
|
|
if (false === $entry) { |
337
|
|
|
$entry = $this->store($object, array(), RegistryState::REGISTERED, array('listeners' => $listeners)); |
338
|
|
|
} |
339
|
|
|
|
340
|
|
|
$entry->setAction($action, $this->_priority); |
341
|
|
|
$this->_priority--; |
342
|
|
|
} |
343
|
|
|
|
344
|
|
|
/** |
345
|
|
|
* |
346
|
|
|
* @return \SplPriorityQueue |
347
|
|
|
*/ |
348
|
|
|
public function getWorkersQueue() |
349
|
|
|
{ |
350
|
|
|
$queue = new \SplPriorityQueue(); |
351
|
|
|
|
352
|
|
|
foreach ($this->store as $entry) { |
353
|
|
|
if (!$entry->hasAction()) { |
354
|
|
|
continue; |
355
|
|
|
} |
356
|
|
|
|
357
|
|
|
$action = $entry->getAction(); |
358
|
|
|
$object = $entry->getObject(); |
359
|
|
|
|
360
|
|
|
$access = new Accessor($object); |
361
|
|
|
$relations = $access->getRelations(); |
362
|
|
|
foreach ($relations as $key => $relation) { |
363
|
|
|
$relation->setParent($object, $this->getEventDispatcher($object)); |
364
|
|
|
} |
365
|
|
|
|
366
|
|
|
switch ($action) { |
367
|
|
|
case self::ACTION_DELETE: |
368
|
|
|
$worker = new DeleteEntityWorker($object); |
369
|
|
|
break; |
370
|
|
|
|
371
|
|
|
case self::ACTION_SAVE: |
372
|
|
|
$worker = new SaveEntityWorker($object); |
373
|
|
|
break; |
374
|
|
|
|
375
|
|
|
default: |
376
|
|
|
throw new Exception(sprintf("Unknown registry action '%s'", $action)); |
377
|
|
|
} |
378
|
|
|
|
379
|
|
|
$worker->setRegistry($this); |
380
|
|
|
$queue->insert($worker, $entry->getActionPriority()); |
381
|
|
|
} |
382
|
|
|
|
383
|
|
|
return $queue; |
384
|
|
|
} |
385
|
|
|
|
386
|
|
|
/** |
387
|
|
|
* Mark current object values (Accessor) as initial values |
388
|
|
|
* |
389
|
|
|
* @param mixed $object |
390
|
|
|
* |
391
|
|
|
* @return void |
392
|
|
|
*/ |
393
|
|
|
public function defineInitialValues($object, Connection $connection = null, Table $table = null) |
394
|
|
|
{ |
395
|
|
|
$entry = $this->getEntry($object); |
396
|
|
|
if (false === $entry) { |
397
|
|
|
throw new UnregisteredEntityException(sprintf('Unregistered entity (%s)', get_class($object))); |
398
|
|
|
} |
399
|
|
|
|
400
|
|
|
$entry->fresh(); |
401
|
|
|
|
402
|
|
|
if ($connection !== null && $table !== null) { |
403
|
|
|
$entry->data('dispatcher')->notify(new FreshEvent($connection, $table, $object)); |
404
|
|
|
} |
405
|
|
|
} |
406
|
|
|
|
407
|
|
|
/** |
408
|
|
|
* |
409
|
|
|
* @param mixed $object |
410
|
|
|
* |
411
|
|
|
* @return array |
412
|
|
|
*/ |
413
|
|
|
public function getChangedValues($object) |
414
|
|
|
{ |
415
|
|
|
$entry = $this->getEntry($object); |
416
|
|
|
if (false === $entry) { |
417
|
|
|
throw new UnregisteredEntityException(sprintf('Unregistered entity (%s)', get_class($object))); |
418
|
|
|
} |
419
|
|
|
|
420
|
|
|
return $entry->getChangedValues(); |
421
|
|
|
} |
422
|
|
|
|
423
|
|
|
/** |
424
|
|
|
* |
425
|
|
|
*/ |
426
|
|
|
public function clear() |
427
|
|
|
{ |
428
|
|
|
unset($this->store); |
429
|
|
|
$this->store = new SplObjectStorage(); |
430
|
|
|
$this->_priority = \PHP_INT_MAX; |
431
|
|
|
|
432
|
|
|
return $this; |
433
|
|
|
} |
434
|
|
|
|
435
|
|
|
/** |
436
|
|
|
* @return SplObjectStorage |
437
|
|
|
*/ |
438
|
|
|
public function getStore() |
439
|
|
|
{ |
440
|
|
|
return $this->store; |
441
|
|
|
} |
442
|
|
|
} |
This check examines a number of code elements and verifies that they conform to the given naming conventions.
You can set conventions for local variables, abstract classes, utility classes, constant, properties, methods, parameters, interfaces, classes, exceptions and special methods.