Passed
Pull Request — master (#98)
by
unknown
03:15
created

AuditManager::revert()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 17
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 8
c 1
b 0
f 0
nc 1
nop 6
dl 0
loc 17
rs 10
1
<?php
2
3
namespace DH\DoctrineAuditBundle\Manager;
4
5
use DH\DoctrineAuditBundle\AuditConfiguration;
6
use DH\DoctrineAuditBundle\AuditEntry;
7
use DH\DoctrineAuditBundle\Exception\AccessDeniedException;
8
use DH\DoctrineAuditBundle\Exception\InvalidArgumentException;
9
use DH\DoctrineAuditBundle\Helper\AuditHelper;
10
use DH\DoctrineAuditBundle\Reader\AuditReader;
11
use Doctrine\ORM\EntityManager;
12
use Doctrine\ORM\EntityManagerInterface;
13
use Doctrine\ORM\Mapping\ClassMetadata;
14
15
class AuditManager
16
{
17
    /**
18
     * @var \DH\DoctrineAuditBundle\AuditConfiguration
19
     */
20
    private $configuration;
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
     * @param AuditTransaction $transaction
43
     *
44
     * @throws \Doctrine\DBAL\DBALException
45
     * @throws \Doctrine\ORM\Mapping\MappingException
46
     */
47
    public function process(AuditTransaction $transaction): void
48
    {
49
        $this->processInsertions($transaction);
50
        $this->processUpdates($transaction);
51
        $this->processAssociations($transaction);
52
        $this->processDissociations($transaction);
53
        $this->processDeletions($transaction);
54
    }
55
56
    /**
57
     * Adds an insert entry to the audit table.
58
     *
59
     * @param EntityManager $em
60
     * @param object        $entity
61
     * @param array         $ch
62
     * @param string        $transactionHash
63
     *
64
     * @throws \Doctrine\DBAL\DBALException
65
     * @throws \Doctrine\ORM\Mapping\MappingException
66
     */
67
    public function insert(EntityManager $em, $entity, array $ch, string $transactionHash): void
68
    {
69
        $meta = $em->getClassMetadata(\get_class($entity));
70
        $this->audit($em, [
71
            'action' => 'insert',
72
            'blame' => $this->helper->blame(),
73
            'diff' => $this->helper->diff($em, $entity, $ch),
74
            'table' => $meta->getTableName(),
75
            'schema' => $meta->getSchemaName(),
76
            'id' => $this->helper->id($em, $entity),
77
            'transaction_hash' => $transactionHash,
78
            'discriminator' => ClassMetadata::INHERITANCE_TYPE_SINGLE_TABLE === $meta->inheritanceType ? \get_class($entity) : null,
79
        ]);
80
    }
81
82
    /**
83
     * Adds an update entry to the audit table.
84
     *
85
     * @param EntityManager $em
86
     * @param object        $entity
87
     * @param array         $ch
88
     * @param string        $transactionHash
89
     *
90
     * @throws \Doctrine\DBAL\DBALException
91
     * @throws \Doctrine\ORM\Mapping\MappingException
92
     */
93
    public function update(EntityManager $em, $entity, array $ch, string $transactionHash): void
94
    {
95
        $diff = $this->helper->diff($em, $entity, $ch);
96
        if (0 === \count($diff)) {
97
            return; // if there is no entity diff, do not log it
98
        }
99
        $meta = $em->getClassMetadata(\get_class($entity));
100
        $this->audit($em, [
101
            'action' => 'update',
102
            'blame' => $this->helper->blame(),
103
            'diff' => $diff,
104
            'table' => $meta->getTableName(),
105
            'schema' => $meta->getSchemaName(),
106
            'id' => $this->helper->id($em, $entity),
107
            'transaction_hash' => $transactionHash,
108
            'discriminator' => ClassMetadata::INHERITANCE_TYPE_SINGLE_TABLE === $meta->inheritanceType ? \get_class($entity) : null,
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
     * @param string        $transactionHash
119
     *
120
     * @throws \Doctrine\DBAL\DBALException
121
     * @throws \Doctrine\ORM\Mapping\MappingException
122
     */
123
    public function remove(EntityManager $em, $entity, $id, string $transactionHash): void
124
    {
125
        $meta = $em->getClassMetadata(\get_class($entity));
126
        $this->audit($em, [
127
            'action' => 'remove',
128
            'blame' => $this->helper->blame(),
129
            'diff' => $this->helper->summarize($em, $entity, $id),
130
            'table' => $meta->getTableName(),
131
            'schema' => $meta->getSchemaName(),
132
            'id' => $id,
133
            'transaction_hash' => $transactionHash,
134
            'discriminator' => ClassMetadata::INHERITANCE_TYPE_SINGLE_TABLE === $meta->inheritanceType ? \get_class($entity) : null,
135
        ]);
136
    }
137
138
    /**
139
     * Adds an association entry to the audit table.
140
     *
141
     * @param EntityManager $em
142
     * @param object        $source
143
     * @param object        $target
144
     * @param array         $mapping
145
     * @param string        $transactionHash
146
     *
147
     * @throws \Doctrine\DBAL\DBALException
148
     * @throws \Doctrine\ORM\Mapping\MappingException
149
     */
150
    public function associate(EntityManager $em, $source, $target, array $mapping, string $transactionHash): void
151
    {
152
        $this->associateOrDissociate('associate', $em, $source, $target, $mapping, $transactionHash);
153
    }
154
155
    /**
156
     * Adds a dissociation entry to the audit table.
157
     *
158
     * @param EntityManager $em
159
     * @param object        $source
160
     * @param object        $target
161
     * @param array         $mapping
162
     * @param string        $transactionHash
163
     *
164
     * @throws \Doctrine\DBAL\DBALException
165
     * @throws \Doctrine\ORM\Mapping\MappingException
166
     */
167
    public function dissociate(EntityManager $em, $source, $target, array $mapping, string $transactionHash): void
168
    {
169
        $this->associateOrDissociate('dissociate', $em, $source, $target, $mapping, $transactionHash);
170
    }
171
172
    /**
173
     * Adds an association entry to the audit table.
174
     *
175
     * @param string        $type
176
     * @param EntityManager $em
177
     * @param object        $source
178
     * @param object        $target
179
     * @param array         $mapping
180
     * @param string        $transactionHash
181
     *
182
     * @throws \Doctrine\DBAL\DBALException
183
     * @throws \Doctrine\ORM\Mapping\MappingException
184
     */
185
    private function associateOrDissociate(string $type, EntityManager $em, $source, $target, array $mapping, string $transactionHash): void
186
    {
187
        $meta = $em->getClassMetadata(\get_class($source));
188
        $data = [
189
            'action' => $type,
190
            'blame' => $this->helper->blame(),
191
            'diff' => [
192
                'source' => $this->helper->summarize($em, $source),
193
                'target' => $this->helper->summarize($em, $target),
194
            ],
195
            'table' => $meta->getTableName(),
196
            'schema' => $meta->getSchemaName(),
197
            'id' => $this->helper->id($em, $source),
198
            'transaction_hash' => $transactionHash,
199
            'discriminator' => ClassMetadata::INHERITANCE_TYPE_SINGLE_TABLE === $meta->inheritanceType ? \get_class($source) : null,
200
        ];
201
202
        if (isset($mapping['joinTable']['name'])) {
203
            $data['diff']['table'] = $mapping['joinTable']['name'];
204
        }
205
206
        $this->audit($em, $data);
207
    }
208
209
    /**
210
     * Adds an entry to the audit table.
211
     *
212
     * @param EntityManager $em
213
     * @param array         $data
214
     *
215
     * @throws \Doctrine\DBAL\DBALException
216
     */
217
    private function audit(EntityManager $em, array $data): void
218
    {
219
        $schema = $data['schema'] ? $data['schema'].'.' : '';
220
        $auditTable = $schema.$this->configuration->getTablePrefix().$data['table'].$this->configuration->getTableSuffix();
221
        $fields = [
222
            'type' => ':type',
223
            'object_id' => ':object_id',
224
            'discriminator' => ':discriminator',
225
            'transaction_hash' => ':transaction_hash',
226
            'diffs' => ':diffs',
227
            'blame_id' => ':blame_id',
228
            'blame_user' => ':blame_user',
229
            'blame_user_fqdn' => ':blame_user_fqdn',
230
            'blame_user_firewall' => ':blame_user_firewall',
231
            'ip' => ':ip',
232
            'created_at' => ':created_at',
233
        ];
234
235
        $query = sprintf(
236
            'INSERT INTO %s (%s) VALUES (%s)',
237
            $auditTable,
238
            implode(', ', array_keys($fields)),
239
            implode(', ', array_values($fields))
240
        );
241
242
        $storage = $this->selectStorageSpace($em);
243
        $statement = $storage->getConnection()->prepare($query);
244
245
        $dt = new \DateTime('now', new \DateTimeZone($this->getConfiguration()->getTimezone()));
246
        $statement->bindValue('type', $data['action']);
247
        $statement->bindValue('object_id', (string) $data['id']);
248
        $statement->bindValue('discriminator', $data['discriminator']);
249
        $statement->bindValue('transaction_hash', (string) $data['transaction_hash']);
250
        $statement->bindValue('diffs', json_encode($data['diff']));
251
        $statement->bindValue('blame_id', $data['blame']['user_id']);
252
        $statement->bindValue('blame_user', $data['blame']['username']);
253
        $statement->bindValue('blame_user_fqdn', $data['blame']['user_fqdn']);
254
        $statement->bindValue('blame_user_firewall', $data['blame']['user_firewall']);
255
        $statement->bindValue('ip', $data['blame']['client_ip']);
256
        $statement->bindValue('created_at', $dt->format('Y-m-d H:i:s'));
257
        $statement->execute();
258
    }
259
260
    /**
261
     * Set the value of helper.
262
     *
263
     * @param AuditHelper $helper
264
     */
265
    public function setHelper(AuditHelper $helper): void
266
    {
267
        $this->helper = $helper;
268
    }
269
270
    /**
271
     * Get the value of helper.
272
     *
273
     * @return AuditHelper
274
     */
275
    public function getHelper(): AuditHelper
276
    {
277
        return $this->helper;
278
    }
279
280
    /**
281
     * @param AuditTransaction $transaction
282
     *
283
     * @throws \Doctrine\DBAL\DBALException
284
     * @throws \Doctrine\ORM\Mapping\MappingException
285
     */
286
    public function processInsertions(AuditTransaction $transaction): void
287
    {
288
        $em = $transaction->getEntityManager();
289
        $uow = $em->getUnitOfWork();
290
        foreach ($transaction->getInserted() as list($entity, $ch)) {
291
            // the changeset might be updated from UOW extra updates
292
            $ch = array_merge($ch, $uow->getEntityChangeSet($entity));
293
            $this->insert($em, $entity, $ch, $transaction->getTransactionHash());
294
        }
295
    }
296
297
    /**
298
     * @param AuditTransaction $transaction
299
     *
300
     * @throws \Doctrine\DBAL\DBALException
301
     * @throws \Doctrine\ORM\Mapping\MappingException
302
     */
303
    public function processUpdates(AuditTransaction $transaction): void
304
    {
305
        $em = $transaction->getEntityManager();
306
        $uow = $em->getUnitOfWork();
307
        foreach ($transaction->getUpdated() as list($entity, $ch)) {
308
            // the changeset might be updated from UOW extra updates
309
            $ch = array_merge($ch, $uow->getEntityChangeSet($entity));
310
            $this->update($em, $entity, $ch, $transaction->getTransactionHash());
311
        }
312
    }
313
314
    /**
315
     * @param AuditTransaction $transaction
316
     *
317
     * @throws \Doctrine\DBAL\DBALException
318
     * @throws \Doctrine\ORM\Mapping\MappingException
319
     */
320
    public function processAssociations(AuditTransaction $transaction): void
321
    {
322
        $em = $transaction->getEntityManager();
323
        foreach ($transaction->getAssociated() as list($source, $target, $mapping)) {
324
            $this->associate($em, $source, $target, $mapping, $transaction->getTransactionHash());
325
        }
326
    }
327
328
    /**
329
     * @param AuditTransaction $transaction
330
     *
331
     * @throws \Doctrine\DBAL\DBALException
332
     * @throws \Doctrine\ORM\Mapping\MappingException
333
     */
334
    public function processDissociations(AuditTransaction $transaction): void
335
    {
336
        $em = $transaction->getEntityManager();
337
        foreach ($transaction->getDissociated() as list($source, $target, $id, $mapping)) {
338
            $this->dissociate($em, $source, $target, $mapping, $transaction->getTransactionHash());
339
        }
340
    }
341
342
    /**
343
     * @param AuditTransaction $transaction
344
     *
345
     * @throws \Doctrine\DBAL\DBALException
346
     * @throws \Doctrine\ORM\Mapping\MappingException
347
     */
348
    public function processDeletions(AuditTransaction $transaction): void
349
    {
350
        $em = $transaction->getEntityManager();
351
        foreach ($transaction->getRemoved() as list($entity, $id)) {
352
            $this->remove($em, $entity, $id, $transaction->getTransactionHash());
353
        }
354
    }
355
356
    /**
357
     * @param EntityManagerInterface $em
358
     *
359
     * @return EntityManagerInterface
360
     */
361
    private function selectStorageSpace(EntityManagerInterface $em): EntityManagerInterface
362
    {
363
        return $this->configuration->getEntityManager() ?? $em;
364
    }
365
366
    /**
367
     * @param AuditReader            $reader
368
     * @param EntityManagerInterface $em
369
     * @param                        $entity
370
     * @param                        $id
371
     * @param                        $object
372
     * @param                        $field
373
     *
374
     * @return object|null
375
     * @throws AccessDeniedException
376
     * @throws InvalidArgumentException
377
     */
378
    public function revert(AuditReader $reader, EntityManagerInterface $em, $entity, $id, $object, $field)
379
    {
380
        /** @var AuditEntry $entity_audit */
381
        $entity_audit = $reader->getAudit(AuditHelper::paramToNamespace($entity), $id);
382
        $audited_entity = AuditHelper::paramToNamespace($entity);
383
        $current_entity = $em->getRepository($audited_entity)->find($object);
384
385
        // Get all differences
386
        $diffs = $entity_audit[0]->getDiffs();
387
        // get field value to revert
388
        $field_value = $diffs[$field]['old'];
389
390
        $setMethod = "set{$field}";
391
392
        $current_entity->{$setMethod}($field_value);
393
394
        return $current_entity;
395
    }
396
}
397