Passed
Push — master ( ecadea...14db22 )
by Damien
03:21
created

Manager   A

Complexity

Total Complexity 30

Size/Duplication

Total Lines 366
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 111
dl 0
loc 366
rs 10
c 0
b 0
f 0
wmc 30

20 Methods

Rating   Name   Duplication   Size   Complexity  
A processUpdates() 0 8 2
A getConfiguration() 0 3 1
A notify() 0 10 2
A processDeletions() 0 5 2
A setHelper() 0 3 1
A update() 0 18 2
A processAssociations() 0 5 2
A remove() 0 14 1
A process() 0 7 1
A insert() 0 14 1
A __construct() 0 4 1
A associate() 0 3 1
A processDissociations() 0 5 2
A getHelper() 0 3 1
A dissociate() 0 3 1
A associateOrDissociate() 0 24 2
A processInsertions() 0 8 2
A selectStorageSpace() 0 3 1
A getDiscriminator() 0 3 2
A audit() 0 24 2
1
<?php
2
3
namespace DH\DoctrineAuditBundle\Manager;
4
5
use DateTime;
6
use DateTimeZone;
7
use DH\DoctrineAuditBundle\Configuration;
8
use DH\DoctrineAuditBundle\Event\LifecycleEvent;
9
use DH\DoctrineAuditBundle\Helper\AuditHelper;
10
use DH\DoctrineAuditBundle\Helper\DoctrineHelper;
11
use Doctrine\ORM\EntityManagerInterface;
12
use Doctrine\ORM\Mapping\ClassMetadata;
13
use Exception;
14
15
class Manager
16
{
17
    /**
18
     * @var \DH\DoctrineAuditBundle\Configuration
19
     */
20
    private $configuration;
21
22
    /**
23
     * @var AuditHelper
24
     */
25
    private $helper;
26
27
    public function __construct(Configuration $configuration, AuditHelper $helper)
28
    {
29
        $this->configuration = $configuration;
30
        $this->helper = $helper;
31
    }
32
33
    /**
34
     * @return \DH\DoctrineAuditBundle\Configuration
35
     */
36
    public function getConfiguration(): Configuration
37
    {
38
        return $this->configuration;
39
    }
40
41
    /**
42
     * @param array $payload
43
     */
44
    public function notify(array $payload): void
45
    {
46
        $dispatcher = $this->configuration->getEventDispatcher();
47
48
        if ($this->configuration->isPre43Dispatcher()) {
49
            // Symfony 3.x
50
            $dispatcher->dispatch(LifecycleEvent::class, new LifecycleEvent($payload));
0 ignored issues
show
Bug introduced by
DH\DoctrineAuditBundle\Event\LifecycleEvent::class of type string is incompatible with the type object expected by parameter $event of Symfony\Contracts\EventD...erInterface::dispatch(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

50
            $dispatcher->dispatch(/** @scrutinizer ignore-type */ LifecycleEvent::class, new LifecycleEvent($payload));
Loading history...
51
        } else {
52
            // Symfony 4.x
53
            $dispatcher->dispatch(new LifecycleEvent($payload));
54
        }
55
    }
56
57
    /**
58
     * @param Transaction $transaction
59
     *
60
     * @throws \Doctrine\DBAL\DBALException
61
     * @throws \Doctrine\ORM\Mapping\MappingException
62
     */
63
    public function process(Transaction $transaction): void
64
    {
65
        $this->processInsertions($transaction);
66
        $this->processUpdates($transaction);
67
        $this->processAssociations($transaction);
68
        $this->processDissociations($transaction);
69
        $this->processDeletions($transaction);
70
    }
71
72
    /**
73
     * Adds an insert entry to the audit table.
74
     *
75
     * @param EntityManagerInterface $em
76
     * @param object                 $entity
77
     * @param array                  $ch
78
     * @param string                 $transactionHash
79
     *
80
     * @throws \Doctrine\DBAL\DBALException
81
     * @throws \Doctrine\ORM\Mapping\MappingException
82
     */
83
    public function insert(EntityManagerInterface $em, $entity, array $ch, string $transactionHash): void
84
    {
85
        /** @var ClassMetadata $meta */
86
        $meta = $em->getClassMetadata(DoctrineHelper::getRealClassName($entity));
87
        $this->audit([
88
            'action' => 'insert',
89
            'blame' => $this->helper->blame(),
90
            'diff' => $this->helper->diff($em, $entity, $ch),
91
            'table' => $meta->getTableName(),
92
            'schema' => $meta->getSchemaName(),
93
            'id' => $this->helper->id($em, $entity),
94
            'transaction_hash' => $transactionHash,
95
            'discriminator' => $this->getDiscriminator($entity, $meta->inheritanceType),
96
            'entity' => $meta->getName(),
97
        ]);
98
    }
99
100
    /**
101
     * Adds an update entry to the audit table.
102
     *
103
     * @param EntityManagerInterface $em
104
     * @param object                 $entity
105
     * @param array                  $ch
106
     * @param string                 $transactionHash
107
     *
108
     * @throws \Doctrine\DBAL\DBALException
109
     * @throws \Doctrine\ORM\Mapping\MappingException
110
     */
111
    public function update(EntityManagerInterface $em, $entity, array $ch, string $transactionHash): void
112
    {
113
        $diff = $this->helper->diff($em, $entity, $ch);
114
        if (0 === \count($diff)) {
115
            return; // if there is no entity diff, do not log it
116
        }
117
        /** @var ClassMetadata $meta */
118
        $meta = $em->getClassMetadata(DoctrineHelper::getRealClassName($entity));
119
        $this->audit([
120
            'action' => 'update',
121
            'blame' => $this->helper->blame(),
122
            'diff' => $diff,
123
            'table' => $meta->getTableName(),
124
            'schema' => $meta->getSchemaName(),
125
            'id' => $this->helper->id($em, $entity),
126
            'transaction_hash' => $transactionHash,
127
            'discriminator' => $this->getDiscriminator($entity, $meta->inheritanceType),
128
            'entity' => $meta->getName(),
129
        ]);
130
    }
131
132
    /**
133
     * Adds a remove entry to the audit table.
134
     *
135
     * @param EntityManagerInterface $em
136
     * @param object                 $entity
137
     * @param mixed                  $id
138
     * @param string                 $transactionHash
139
     *
140
     * @throws \Doctrine\DBAL\DBALException
141
     * @throws \Doctrine\ORM\Mapping\MappingException
142
     */
143
    public function remove(EntityManagerInterface $em, $entity, $id, string $transactionHash): void
144
    {
145
        /** @var ClassMetadata $meta */
146
        $meta = $em->getClassMetadata(DoctrineHelper::getRealClassName($entity));
147
        $this->audit([
148
            'action' => 'remove',
149
            'blame' => $this->helper->blame(),
150
            'diff' => $this->helper->summarize($em, $entity, $id),
151
            'table' => $meta->getTableName(),
152
            'schema' => $meta->getSchemaName(),
153
            'id' => $id,
154
            'transaction_hash' => $transactionHash,
155
            'discriminator' => $this->getDiscriminator($entity, $meta->inheritanceType),
156
            'entity' => $meta->getName(),
157
        ]);
158
    }
159
160
    /**
161
     * Adds an association entry to the audit table.
162
     *
163
     * @param EntityManagerInterface $em
164
     * @param object                 $source
165
     * @param object                 $target
166
     * @param array                  $mapping
167
     * @param string                 $transactionHash
168
     *
169
     * @throws \Doctrine\DBAL\DBALException
170
     * @throws \Doctrine\ORM\Mapping\MappingException
171
     */
172
    public function associate(EntityManagerInterface $em, $source, $target, array $mapping, string $transactionHash): void
173
    {
174
        $this->associateOrDissociate('associate', $em, $source, $target, $mapping, $transactionHash);
175
    }
176
177
    /**
178
     * Adds a dissociation entry to the audit table.
179
     *
180
     * @param EntityManagerInterface $em
181
     * @param object                 $source
182
     * @param object                 $target
183
     * @param array                  $mapping
184
     * @param string                 $transactionHash
185
     *
186
     * @throws \Doctrine\DBAL\DBALException
187
     * @throws \Doctrine\ORM\Mapping\MappingException
188
     */
189
    public function dissociate(EntityManagerInterface $em, $source, $target, array $mapping, string $transactionHash): void
190
    {
191
        $this->associateOrDissociate('dissociate', $em, $source, $target, $mapping, $transactionHash);
192
    }
193
194
    /**
195
     * Set the value of helper.
196
     *
197
     * @param AuditHelper $helper
198
     */
199
    public function setHelper(AuditHelper $helper): void
200
    {
201
        $this->helper = $helper;
202
    }
203
204
    /**
205
     * Get the value of helper.
206
     *
207
     * @return AuditHelper
208
     */
209
    public function getHelper(): AuditHelper
210
    {
211
        return $this->helper;
212
    }
213
214
    /**
215
     * @param Transaction $transaction
216
     *
217
     * @throws \Doctrine\DBAL\DBALException
218
     * @throws \Doctrine\ORM\Mapping\MappingException
219
     */
220
    public function processInsertions(Transaction $transaction): void
221
    {
222
        $em = $transaction->getEntityManager();
223
        $uow = $em->getUnitOfWork();
224
        foreach ($transaction->getInserted() as list($entity, $ch)) {
225
            // the changeset might be updated from UOW extra updates
226
            $ch = array_merge($ch, $uow->getEntityChangeSet($entity));
227
            $this->insert($em, $entity, $ch, $transaction->getTransactionHash());
228
        }
229
    }
230
231
    /**
232
     * @param Transaction $transaction
233
     *
234
     * @throws \Doctrine\DBAL\DBALException
235
     * @throws \Doctrine\ORM\Mapping\MappingException
236
     */
237
    public function processUpdates(Transaction $transaction): void
238
    {
239
        $em = $transaction->getEntityManager();
240
        $uow = $em->getUnitOfWork();
241
        foreach ($transaction->getUpdated() as list($entity, $ch)) {
242
            // the changeset might be updated from UOW extra updates
243
            $ch = array_merge($ch, $uow->getEntityChangeSet($entity));
244
            $this->update($em, $entity, $ch, $transaction->getTransactionHash());
245
        }
246
    }
247
248
    /**
249
     * @param Transaction $transaction
250
     *
251
     * @throws \Doctrine\DBAL\DBALException
252
     * @throws \Doctrine\ORM\Mapping\MappingException
253
     */
254
    public function processAssociations(Transaction $transaction): void
255
    {
256
        $em = $transaction->getEntityManager();
257
        foreach ($transaction->getAssociated() as list($source, $target, $mapping)) {
258
            $this->associate($em, $source, $target, $mapping, $transaction->getTransactionHash());
259
        }
260
    }
261
262
    /**
263
     * @param Transaction $transaction
264
     *
265
     * @throws \Doctrine\DBAL\DBALException
266
     * @throws \Doctrine\ORM\Mapping\MappingException
267
     */
268
    public function processDissociations(Transaction $transaction): void
269
    {
270
        $em = $transaction->getEntityManager();
271
        foreach ($transaction->getDissociated() as list($source, $target, $id, $mapping)) {
272
            $this->dissociate($em, $source, $target, $mapping, $transaction->getTransactionHash());
273
        }
274
    }
275
276
    /**
277
     * @param Transaction $transaction
278
     *
279
     * @throws \Doctrine\DBAL\DBALException
280
     * @throws \Doctrine\ORM\Mapping\MappingException
281
     */
282
    public function processDeletions(Transaction $transaction): void
283
    {
284
        $em = $transaction->getEntityManager();
285
        foreach ($transaction->getRemoved() as list($entity, $id)) {
286
            $this->remove($em, $entity, $id, $transaction->getTransactionHash());
287
        }
288
    }
289
290
    /**
291
     * @param EntityManagerInterface $em
292
     *
293
     * @return EntityManagerInterface
294
     */
295
    public function selectStorageSpace(EntityManagerInterface $em): EntityManagerInterface
296
    {
297
        return $this->configuration->getEntityManager() ?? $em;
298
    }
299
300
    /**
301
     * Adds an association entry to the audit table.
302
     *
303
     * @param string                 $type
304
     * @param EntityManagerInterface $em
305
     * @param object                 $source
306
     * @param object                 $target
307
     * @param array                  $mapping
308
     * @param string                 $transactionHash
309
     *
310
     * @throws \Doctrine\DBAL\DBALException
311
     * @throws \Doctrine\ORM\Mapping\MappingException
312
     */
313
    private function associateOrDissociate(string $type, EntityManagerInterface $em, $source, $target, array $mapping, string $transactionHash): void
314
    {
315
        /** @var ClassMetadata $meta */
316
        $meta = $em->getClassMetadata(DoctrineHelper::getRealClassName($source));
317
        $data = [
318
            'action' => $type,
319
            'blame' => $this->helper->blame(),
320
            'diff' => [
321
                'source' => $this->helper->summarize($em, $source),
322
                'target' => $this->helper->summarize($em, $target),
323
            ],
324
            'table' => $meta->getTableName(),
325
            'schema' => $meta->getSchemaName(),
326
            'id' => $this->helper->id($em, $source),
327
            'transaction_hash' => $transactionHash,
328
            'discriminator' => $this->getDiscriminator($source, $meta->inheritanceType),
329
            'entity' => $meta->getName(),
330
        ];
331
332
        if (isset($mapping['joinTable']['name'])) {
333
            $data['diff']['table'] = $mapping['joinTable']['name'];
334
        }
335
336
        $this->audit($data);
337
    }
338
339
    /**
340
     * Adds an entry to the audit table.
341
     *
342
     * @param array $data
343
     *
344
     * @throws Exception
345
     */
346
    private function audit(array $data): void
347
    {
348
        $schema = $data['schema'] ? $data['schema'].'.' : '';
349
        $auditTable = $schema.$this->configuration->getTablePrefix().$data['table'].$this->configuration->getTableSuffix();
350
        $dt = new DateTime('now', new DateTimeZone($this->getConfiguration()->getTimezone()));
351
352
        $payload = [
353
            'entity' => $data['entity'],
354
            'table' => $auditTable,
355
            'type' => $data['action'],
356
            'object_id' => (string) $data['id'],
357
            'discriminator' => $data['discriminator'],
358
            'transaction_hash' => (string) $data['transaction_hash'],
359
            'diffs' => json_encode($data['diff']),
360
            'blame_id' => $data['blame']['user_id'],
361
            'blame_user' => $data['blame']['username'],
362
            'blame_user_fqdn' => $data['blame']['user_fqdn'],
363
            'blame_user_firewall' => $data['blame']['user_firewall'],
364
            'ip' => $data['blame']['client_ip'],
365
            'created_at' => $dt->format('Y-m-d H:i:s'),
366
        ];
367
368
        // send an `AuditEvent` event
369
        $this->notify($payload);
370
    }
371
372
    /**
373
     * @param object $entity
374
     * @param int    $inheritanceType
375
     *
376
     * @return null|string
377
     */
378
    private function getDiscriminator($entity, int $inheritanceType): ?string
379
    {
380
        return ClassMetadata::INHERITANCE_TYPE_SINGLE_TABLE === $inheritanceType ? DoctrineHelper::getRealClassName($entity) : null;
381
    }
382
}
383