Passed
Push — master ( 6d951a...571c51 )
by Damien
02:56
created

AuditManager::selectStorageSpace()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
c 1
b 0
f 0
nc 1
nop 1
dl 0
loc 3
rs 10
1
<?php
2
3
namespace DH\DoctrineAuditBundle;
4
5
use DH\DoctrineAuditBundle\Helper\AuditHelper;
6
use Doctrine\ORM\EntityManager;
7
8
class AuditManager
9
{
10
    /**
11
     * @var \DH\DoctrineAuditBundle\AuditConfiguration
12
     */
13
    private $configuration;
14
15
    private $inserted = [];     // [$source, $changeset]
16
    private $updated = [];      // [$source, $changeset]
17
    private $removed = [];      // [$source, $id]
18
    private $associated = [];   // [$source, $target, $mapping]
19
    private $dissociated = [];  // [$source, $target, $id, $mapping]
20
21
    /**
22
     * @var AuditHelper
23
     */
24
    private $helper;
25
26
    /**
27
     * @var EntityManager|null
28
     */
29
    private $customStorageEntityManager;
30
31
    public function __construct(AuditConfiguration $configuration, AuditHelper $helper, ?EntityManager $customStorageEntityManager = null)
32
    {
33
        $this->configuration = $configuration;
34
        $this->helper = $helper;
35
        $this->customStorageEntityManager = $customStorageEntityManager;
36
    }
37
38
    /**
39
     * @return \DH\DoctrineAuditBundle\AuditConfiguration
40
     */
41
    public function getConfiguration(): AuditConfiguration
42
    {
43
        return $this->configuration;
44
    }
45
46
    /**
47
     * Adds an insert entry to the audit table.
48
     *
49
     * @param EntityManager $em
50
     * @param object        $entity
51
     * @param array         $ch
52
     *
53
     * @throws \Doctrine\DBAL\DBALException
54
     * @throws \Doctrine\ORM\Mapping\MappingException
55
     */
56
    public function insert(EntityManager $em, $entity, array $ch): void
57
    {
58
        $meta = $em->getClassMetadata(\get_class($entity));
59
        $this->audit($em, [
60
            'action' => 'insert',
61
            'blame' => $this->helper->blame(),
62
            'diff' => $this->helper->diff($em, $entity, $ch),
63
            'table' => $meta->getTableName(),
64
            'schema' => $meta->getSchemaName(),
65
            'id' => $this->helper->id($em, $entity),
66
        ]);
67
    }
68
69
    /**
70
     * Adds an update entry to the audit table.
71
     *
72
     * @param EntityManager $em
73
     * @param object        $entity
74
     * @param array         $ch
75
     *
76
     * @throws \Doctrine\DBAL\DBALException
77
     * @throws \Doctrine\ORM\Mapping\MappingException
78
     */
79
    public function update(EntityManager $em, $entity, array $ch): void
80
    {
81
        $diff = $this->helper->diff($em, $entity, $ch);
82
        if (!$diff) {
83
            return; // if there is no entity diff, do not log it
84
        }
85
        $meta = $em->getClassMetadata(\get_class($entity));
86
        $this->audit($em, [
87
            'action' => 'update',
88
            'blame' => $this->helper->blame(),
89
            'diff' => $diff,
90
            'table' => $meta->getTableName(),
91
            'schema' => $meta->getSchemaName(),
92
            'id' => $this->helper->id($em, $entity),
93
        ]);
94
    }
95
96
    /**
97
     * Adds a remove entry to the audit table.
98
     *
99
     * @param EntityManager $em
100
     * @param object        $entity
101
     * @param mixed         $id
102
     *
103
     * @throws \Doctrine\DBAL\DBALException
104
     * @throws \Doctrine\ORM\Mapping\MappingException
105
     */
106
    public function remove(EntityManager $em, $entity, $id): void
107
    {
108
        $meta = $em->getClassMetadata(\get_class($entity));
109
        $this->audit($em, [
110
            'action' => 'remove',
111
            'blame' => $this->helper->blame(),
112
            'diff' => $this->helper->summarize($em, $entity, $id),
113
            'table' => $meta->getTableName(),
114
            'schema' => $meta->getSchemaName(),
115
            'id' => $id,
116
        ]);
117
    }
118
119
    /**
120
     * Adds an association entry to the audit table.
121
     *
122
     * @param EntityManager $em
123
     * @param object        $source
124
     * @param object        $target
125
     * @param array         $mapping
126
     *
127
     * @throws \Doctrine\DBAL\DBALException
128
     * @throws \Doctrine\ORM\Mapping\MappingException
129
     */
130
    public function associate(EntityManager $em, $source, $target, array $mapping): void
131
    {
132
        $this->associateOrDissociate('associate', $em, $source, $target, $mapping);
133
    }
134
135
    /**
136
     * Adds a dissociation entry to the audit table.
137
     *
138
     * @param EntityManager $em
139
     * @param object        $source
140
     * @param object        $target
141
     * @param array         $mapping
142
     *
143
     * @throws \Doctrine\DBAL\DBALException
144
     * @throws \Doctrine\ORM\Mapping\MappingException
145
     */
146
    public function dissociate(EntityManager $em, $source, $target, array $mapping): void
147
    {
148
        $this->associateOrDissociate('dissociate', $em, $source, $target, $mapping);
149
    }
150
151
    /**
152
     * @param EntityManager $em
153
     * @param object        $entity
154
     *
155
     * @throws \Doctrine\DBAL\DBALException
156
     * @throws \Doctrine\ORM\Mapping\MappingException
157
     */
158
    public function softDelete(EntityManager $em, $entity): void
159
    {
160
        if ($this->configuration->isAudited($entity)) {
161
            $this->removed[] = [
162
                $entity,
163
                $this->helper->id($em, $entity),
164
            ];
165
        }
166
    }
167
168
    /**
169
     * Adds an association entry to the audit table.
170
     *
171
     * @param string        $type
172
     * @param EntityManager $em
173
     * @param object        $source
174
     * @param object        $target
175
     * @param array         $mapping
176
     *
177
     * @throws \Doctrine\DBAL\DBALException
178
     * @throws \Doctrine\ORM\Mapping\MappingException
179
     */
180
    private function associateOrDissociate(string $type, EntityManager $em, $source, $target, array $mapping): void
181
    {
182
        $meta = $em->getClassMetadata(\get_class($source));
183
        $data = [
184
            'action' => $type,
185
            'blame' => $this->helper->blame(),
186
            'diff' => [
187
                'source' => $this->helper->summarize($em, $source),
188
                'target' => $this->helper->summarize($em, $target),
189
            ],
190
            'table' => $meta->getTableName(),
191
            'schema' => $meta->getSchemaName(),
192
            'id' => $this->helper->id($em, $source),
193
        ];
194
195
        if (isset($mapping['joinTable']['name'])) {
196
            $data['diff']['table'] = $mapping['joinTable']['name'];
197
        }
198
199
        $this->audit($em, $data);
200
    }
201
202
    /**
203
     * Adds an entry to the audit table.
204
     *
205
     * @param EntityManager $em
206
     * @param array         $data
207
     *
208
     * @throws \Doctrine\DBAL\DBALException
209
     */
210
    private function audit(EntityManager $em, array $data): void
211
    {
212
        $schema = $data['schema'] ? $data['schema'].'.' : '';
213
        $auditTable = $schema.$this->configuration->getTablePrefix().$data['table'].$this->configuration->getTableSuffix();
214
        $fields = [
215
            'type' => ':type',
216
            'object_id' => ':object_id',
217
            'diffs' => ':diffs',
218
            'blame_id' => ':blame_id',
219
            'blame_user' => ':blame_user',
220
            'blame_user_fqdn' => ':blame_user_fqdn',
221
            'blame_user_firewall' => ':blame_user_firewall',
222
            'ip' => ':ip',
223
            'created_at' => ':created_at',
224
        ];
225
226
        $query = sprintf(
227
            'INSERT INTO %s (%s) VALUES (%s)',
228
            $auditTable,
229
            implode(', ', array_keys($fields)),
230
            implode(', ', array_values($fields))
231
        );
232
233
        $storage = $this->selectStorageSpace($em);
234
        $statement = $storage->getConnection()->prepare($query);
235
236
        $dt = new \DateTime('now', new \DateTimeZone($this->getConfiguration()->getTimezone()));
237
        $statement->bindValue('type', $data['action']);
238
        $statement->bindValue('object_id', (string) $data['id']);
239
        $statement->bindValue('diffs', json_encode($data['diff']));
240
        $statement->bindValue('blame_id', $data['blame']['user_id']);
241
        $statement->bindValue('blame_user', $data['blame']['username']);
242
        $statement->bindValue('blame_user_fqdn', $data['blame']['user_fqdn']);
243
        $statement->bindValue('blame_user_firewall', $data['blame']['user_firewall']);
244
        $statement->bindValue('ip', $data['blame']['client_ip']);
245
        $statement->bindValue('created_at', $dt->format('Y-m-d H:i:s'));
246
        $statement->execute();
247
    }
248
249
    /**
250
     * Set the value of helper.
251
     *
252
     * @param AuditHelper $helper
253
     */
254
    public function setHelper(AuditHelper $helper): void
255
    {
256
        $this->helper = $helper;
257
    }
258
259
    /**
260
     * Get the value of helper.
261
     *
262
     * @return AuditHelper
263
     */
264
    public function getHelper(): AuditHelper
265
    {
266
        return $this->helper;
267
    }
268
269
    /**
270
     * @param \Doctrine\ORM\UnitOfWork $uow
271
     */
272
    public function collectScheduledInsertions(\Doctrine\ORM\UnitOfWork $uow): void
273
    {
274
        foreach ($uow->getScheduledEntityInsertions() as $entity) {
275
            if ($this->configuration->isAudited($entity)) {
276
                $this->inserted[] = [
277
                    $entity,
278
                    $uow->getEntityChangeSet($entity),
279
                ];
280
            }
281
        }
282
    }
283
284
    /**
285
     * @param \Doctrine\ORM\UnitOfWork $uow
286
     */
287
    public function collectScheduledUpdates(\Doctrine\ORM\UnitOfWork $uow): void
288
    {
289
        foreach ($uow->getScheduledEntityUpdates() as $entity) {
290
            if ($this->configuration->isAudited($entity)) {
291
                $this->updated[] = [
292
                    $entity,
293
                    $uow->getEntityChangeSet($entity),
294
                ];
295
            }
296
        }
297
    }
298
299
    /**
300
     * @param \Doctrine\ORM\UnitOfWork $uow
301
     * @param EntityManager            $em
302
     *
303
     * @throws \Doctrine\DBAL\DBALException
304
     * @throws \Doctrine\ORM\Mapping\MappingException
305
     */
306
    public function collectScheduledDeletions(\Doctrine\ORM\UnitOfWork $uow, EntityManager $em): void
307
    {
308
        foreach ($uow->getScheduledEntityDeletions() as $entity) {
309
            if ($this->configuration->isAudited($entity)) {
310
                $uow->initializeObject($entity);
311
                $this->removed[] = [
312
                    $entity,
313
                    $this->helper->id($em, $entity),
314
                ];
315
            }
316
        }
317
    }
318
319
    /**
320
     * @param \Doctrine\ORM\UnitOfWork $uow
321
     * @param EntityManager            $em
322
     *
323
     * @throws \Doctrine\DBAL\DBALException
324
     * @throws \Doctrine\ORM\Mapping\MappingException
325
     */
326
    public function collectScheduledCollectionUpdates(\Doctrine\ORM\UnitOfWork $uow, EntityManager $em): void
327
    {
328
        foreach ($uow->getScheduledCollectionUpdates() as $collection) {
329
            if ($this->configuration->isAudited($collection->getOwner())) {
330
                $mapping = $collection->getMapping();
331
                foreach ($collection->getInsertDiff() as $entity) {
332
                    if ($this->configuration->isAudited($entity)) {
333
                        $this->associated[] = [
334
                            $collection->getOwner(),
335
                            $entity,
336
                            $mapping,
337
                        ];
338
                    }
339
                }
340
                foreach ($collection->getDeleteDiff() as $entity) {
341
                    if ($this->configuration->isAudited($entity)) {
342
                        $this->dissociated[] = [
343
                            $collection->getOwner(),
344
                            $entity,
345
                            $this->helper->id($em, $entity),
346
                            $mapping,
347
                        ];
348
                    }
349
                }
350
            }
351
        }
352
    }
353
354
    /**
355
     * @param \Doctrine\ORM\UnitOfWork $uow
356
     * @param EntityManager            $em
357
     *
358
     * @throws \Doctrine\DBAL\DBALException
359
     * @throws \Doctrine\ORM\Mapping\MappingException
360
     */
361
    public function collectScheduledCollectionDeletions(\Doctrine\ORM\UnitOfWork $uow, EntityManager $em): void
362
    {
363
        foreach ($uow->getScheduledCollectionDeletions() as $collection) {
364
            if ($this->configuration->isAudited($collection->getOwner())) {
365
                $mapping = $collection->getMapping();
366
                foreach ($collection->toArray() as $entity) {
367
                    if (!$this->configuration->isAudited($entity)) {
368
                        continue;
369
                    }
370
                    $this->dissociated[] = [
371
                        $collection->getOwner(),
372
                        $entity,
373
                        $this->helper->id($em, $entity),
374
                        $mapping,
375
                    ];
376
                }
377
            }
378
        }
379
    }
380
381
    /**
382
     * @param EntityManager            $em
383
     * @param \Doctrine\ORM\UnitOfWork $uow
384
     *
385
     * @throws \Doctrine\DBAL\DBALException
386
     * @throws \Doctrine\ORM\Mapping\MappingException
387
     */
388
    public function processInsertions(EntityManager $em, \Doctrine\ORM\UnitOfWork $uow): void
389
    {
390
        foreach ($this->inserted as list($entity, $ch)) {
391
            // the changeset might be updated from UOW extra updates
392
            $ch = array_merge($ch, $uow->getEntityChangeSet($entity));
393
            $this->insert($em, $entity, $ch);
394
        }
395
    }
396
397
    /**
398
     * @param EntityManager            $em
399
     * @param \Doctrine\ORM\UnitOfWork $uow
400
     *
401
     * @throws \Doctrine\DBAL\DBALException
402
     * @throws \Doctrine\ORM\Mapping\MappingException
403
     */
404
    public function processUpdates(EntityManager $em, \Doctrine\ORM\UnitOfWork $uow): void
405
    {
406
        foreach ($this->updated as list($entity, $ch)) {
407
            // the changeset might be updated from UOW extra updates
408
            $ch = array_merge($ch, $uow->getEntityChangeSet($entity));
409
            $this->update($em, $entity, $ch);
410
        }
411
    }
412
413
    /**
414
     * @param EntityManager $em
415
     *
416
     * @throws \Doctrine\DBAL\DBALException
417
     * @throws \Doctrine\ORM\Mapping\MappingException
418
     */
419
    public function processAssociations(EntityManager $em): void
420
    {
421
        foreach ($this->associated as list($source, $target, $mapping)) {
422
            $this->associate($em, $source, $target, $mapping);
423
        }
424
    }
425
426
    /**
427
     * @param EntityManager $em
428
     *
429
     * @throws \Doctrine\DBAL\DBALException
430
     * @throws \Doctrine\ORM\Mapping\MappingException
431
     */
432
    public function processDissociations(EntityManager $em): void
433
    {
434
        foreach ($this->dissociated as list($source, $target, $id, $mapping)) {
435
            $this->dissociate($em, $source, $target, $mapping);
436
        }
437
    }
438
439
    /**
440
     * @param EntityManager $em
441
     *
442
     * @throws \Doctrine\DBAL\DBALException
443
     * @throws \Doctrine\ORM\Mapping\MappingException
444
     */
445
    public function processDeletions(EntityManager $em): void
446
    {
447
        foreach ($this->removed as list($entity, $id)) {
448
            $this->remove($em, $entity, $id);
449
        }
450
    }
451
452
    public function reset(): void
453
    {
454
        $this->inserted = [];
455
        $this->updated = [];
456
        $this->removed = [];
457
        $this->associated = [];
458
        $this->dissociated = [];
459
    }
460
461
    /**
462
     * @param EntityManager $em
463
     * @return EntityManager
464
     */
465
    private function selectStorageSpace(EntityManager $em): EntityManager
466
    {
467
        return $this->customStorageEntityManager ?? $em;
468
    }
469
}
470