Passed
Pull Request — master (#82)
by Damien
02:42
created

AuditManager::getTransactionHash()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

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