Passed
Push — master ( 004f45...b6a6d3 )
by Damien
03:00
created

AuditManager::softDelete()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

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