TransactionProcessor::processDissociations()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 2
dl 0
loc 4
c 0
b 0
f 0
rs 10
cc 2
nc 2
nop 2
1
<?php
2
3
declare(strict_types=1);
4
5
namespace DH\Auditor\Provider\Doctrine\Auditing\Transaction;
6
7
use DateTimeImmutable;
8
use DateTimeZone;
9
use DH\Auditor\Event\LifecycleEvent;
10
use DH\Auditor\Model\TransactionInterface;
11
use DH\Auditor\Provider\Doctrine\Configuration;
12
use DH\Auditor\Provider\Doctrine\DoctrineProvider;
13
use DH\Auditor\Provider\Doctrine\Model\Transaction;
14
use DH\Auditor\Provider\Doctrine\Persistence\Helper\DoctrineHelper;
15
use DH\Auditor\Transaction\TransactionProcessorInterface;
16
use Doctrine\ORM\EntityManagerInterface;
17
use Doctrine\ORM\Mapping\ClassMetadata;
18
19
/**
20
 * @see \DH\Auditor\Tests\Provider\Doctrine\Auditing\Transaction\TransactionProcessorTest
21
 */
22
final class TransactionProcessor implements TransactionProcessorInterface
23
{
24
    use AuditTrait;
0 ignored issues
show
introduced by
The trait DH\Auditor\Provider\Doct...\Transaction\AuditTrait requires some properties which are not provided by DH\Auditor\Provider\Doct...on\TransactionProcessor: $name, $fieldMappings, $embeddedClasses, $value
Loading history...
25
26
    private DoctrineProvider $provider;
27
28
    public function __construct(DoctrineProvider $provider)
29
    {
30
        $this->provider = $provider;
31
    }
32
33
    /**
34
     * @param Transaction $transaction
35
     */
36
    public function process(TransactionInterface $transaction): void
37
    {
38
        $em = $transaction->getEntityManager();
0 ignored issues
show
Bug introduced by
The method getEntityManager() does not exist on DH\Auditor\Model\TransactionInterface. It seems like you code against a sub-type of DH\Auditor\Model\TransactionInterface such as DH\Auditor\Provider\Doctrine\Model\Transaction. ( Ignorable by Annotation )

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

38
        /** @scrutinizer ignore-call */ 
39
        $em = $transaction->getEntityManager();
Loading history...
39
        $this->processInsertions($transaction, $em);
40
        $this->processUpdates($transaction, $em);
41
        $this->processAssociations($transaction, $em);
42
        $this->processDissociations($transaction, $em);
43
        $this->processDeletions($transaction, $em);
44
    }
45
46
    private function notify(array $payload): void
47
    {
48
        $dispatcher = $this->provider->getAuditor()->getEventDispatcher();
49
        $dispatcher->dispatch(new LifecycleEvent($payload));
50
    }
51
52
    /**
53
     * Adds an insert entry to the audit table.
54
     */
55
    private function insert(EntityManagerInterface $entityManager, object $entity, array $ch, string $transactionHash): void
56
    {
57
        $meta = $entityManager->getClassMetadata(DoctrineHelper::getRealClassName($entity));
58
        $this->audit([
59
            'action' => 'insert',
60
            'blame' => $this->blame(),
61
            'diff' => $this->diff($entityManager, $entity, $ch),
62
            'table' => $meta->getTableName(),
63
            'schema' => $meta->getSchemaName(),
64
            'id' => $this->id($entityManager, $entity),
65
            'transaction_hash' => $transactionHash,
66
            'discriminator' => $this->getDiscriminator($entity, $meta->inheritanceType),
67
            'entity' => $meta->getName(),
68
        ]);
69
    }
70
71
    /**
72
     * Adds an update entry to the audit table.
73
     */
74
    private function update(EntityManagerInterface $entityManager, object $entity, array $ch, string $transactionHash): void
75
    {
76
        $diff = $this->diff($entityManager, $entity, $ch);
77
        unset($diff['@source']);
78
79
        if ([] === $diff) {
80
            return; // if there is no entity diff, do not log it
81
        }
82
83
        $meta = $entityManager->getClassMetadata(DoctrineHelper::getRealClassName($entity));
84
        $this->audit([
85
            'action' => 'update',
86
            'blame' => $this->blame(),
87
            'diff' => $diff,
88
            'table' => $meta->getTableName(),
89
            'schema' => $meta->getSchemaName(),
90
            'id' => $this->id($entityManager, $entity),
91
            'transaction_hash' => $transactionHash,
92
            'discriminator' => $this->getDiscriminator($entity, $meta->inheritanceType),
93
            'entity' => $meta->getName(),
94
        ]);
95
    }
96
97
    /**
98
     * Adds a remove entry to the audit table.
99
     */
100
    private function remove(EntityManagerInterface $entityManager, object $entity, mixed $id, string $transactionHash): void
101
    {
102
        $meta = $entityManager->getClassMetadata(DoctrineHelper::getRealClassName($entity));
103
        $this->audit([
104
            'action' => 'remove',
105
            'blame' => $this->blame(),
106
            'diff' => $this->summarize($entityManager, $entity, ['id' => $id]),
107
            'table' => $meta->getTableName(),
108
            'schema' => $meta->getSchemaName(),
109
            'id' => $id,
110
            'transaction_hash' => $transactionHash,
111
            'discriminator' => $this->getDiscriminator($entity, $meta->inheritanceType),
112
            'entity' => $meta->getName(),
113
        ]);
114
    }
115
116
    /**
117
     * Adds an association entry to the audit table.
118
     */
119
    private function associate(EntityManagerInterface $entityManager, object $source, object $target, array $mapping, string $transactionHash): void
120
    {
121
        $this->associateOrDissociate('associate', $entityManager, $source, $target, $mapping, $transactionHash);
122
    }
123
124
    /**
125
     * Adds a dissociation entry to the audit table.
126
     */
127
    private function dissociate(EntityManagerInterface $entityManager, object $source, object $target, array $mapping, string $transactionHash): void
128
    {
129
        $this->associateOrDissociate('dissociate', $entityManager, $source, $target, $mapping, $transactionHash);
130
    }
131
132
    private function processInsertions(Transaction $transaction, EntityManagerInterface $entityManager): void
133
    {
134
        $uow = $entityManager->getUnitOfWork();
135
        foreach ($transaction->getInserted() as $dto) {
136
            // the changeset might be updated from UOW extra updates
137
            $ch = array_merge($dto->getChangeset(), $uow->getEntityChangeSet($dto->getSource()));
138
            $this->insert($entityManager, $dto->getSource(), $ch, $transaction->getTransactionHash());
139
        }
140
    }
141
142
    private function processUpdates(Transaction $transaction, EntityManagerInterface $entityManager): void
143
    {
144
        $uow = $entityManager->getUnitOfWork();
145
        foreach ($transaction->getUpdated() as $dto) {
146
            // the changeset might be updated from UOW extra updates
147
            $ch = array_merge($dto->getChangeset(), $uow->getEntityChangeSet($dto->getSource()));
148
            $this->update($entityManager, $dto->getSource(), $ch, $transaction->getTransactionHash());
149
        }
150
    }
151
152
    private function processAssociations(Transaction $transaction, EntityManagerInterface $entityManager): void
153
    {
154
        foreach ($transaction->getAssociated() as $dto) {
155
            $this->associate($entityManager, $dto->getSource(), $dto->getTarget(), $dto->getMapping(), $transaction->getTransactionHash());
156
        }
157
    }
158
159
    private function processDissociations(Transaction $transaction, EntityManagerInterface $entityManager): void
160
    {
161
        foreach ($transaction->getDissociated() as $dto) {
162
            $this->dissociate($entityManager, $dto->getSource(), $dto->getTarget(), $dto->getMapping(), $transaction->getTransactionHash());
163
        }
164
    }
165
166
    private function processDeletions(Transaction $transaction, EntityManagerInterface $entityManager): void
167
    {
168
        foreach ($transaction->getRemoved() as $dto) {
169
            $this->remove($entityManager, $dto->getSource(), $dto->getId(), $transaction->getTransactionHash());
170
        }
171
    }
172
173
    /**
174
     * Adds an association entry to the audit table.
175
     */
176
    private function associateOrDissociate(string $type, EntityManagerInterface $entityManager, object $source, object $target, array $mapping, string $transactionHash): void
177
    {
178
        $meta = $entityManager->getClassMetadata(DoctrineHelper::getRealClassName($source));
179
        $data = [
180
            'action' => $type,
181
            'blame' => $this->blame(),
182
            'diff' => [
183
                'source' => $this->summarize($entityManager, $source, ['field' => $mapping['fieldName']]),
184
                'target' => $this->summarize($entityManager, $target, ['field' => $mapping['isOwningSide'] ? $mapping['inversedBy'] : $mapping['mappedBy']]),
185
                'is_owning_side' => $mapping['isOwningSide'],
186
            ],
187
            'table' => $meta->getTableName(),
188
            'schema' => $meta->getSchemaName(),
189
            'id' => $this->id($entityManager, $source),
190
            'transaction_hash' => $transactionHash,
191
            'discriminator' => $this->getDiscriminator($source, $meta->inheritanceType),
192
            'entity' => $meta->getName(),
193
        ];
194
195
        if (isset($mapping['joinTable']['name'])) {
196
            $data['diff']['table'] = $mapping['joinTable']['name'];
197
        }
198
199
        $this->audit($data);
200
    }
201
202
    /**
203
     * Adds an entry to the audit table.
204
     */
205
    private function audit(array $data): void
206
    {
207
        /** @var Configuration $configuration */
208
        $configuration = $this->provider->getConfiguration();
209
        $schema = $data['schema'] ? $data['schema'].'.' : '';
210
        $auditTable = $schema.$configuration->getTablePrefix().$data['table'].$configuration->getTableSuffix();
211
        $dt = new DateTimeImmutable('now', new DateTimeZone($this->provider->getAuditor()->getConfiguration()->getTimezone()));
212
        $diff = $data['diff'];
213
        $convertCharEncoding = (\is_string($diff) || \is_array($diff));
214
        $diff = $convertCharEncoding ? mb_convert_encoding($diff, 'UTF-8', 'UTF-8') : $diff;
215
216
        $payload = [
217
            'entity' => $data['entity'],
218
            'table' => $auditTable,
219
            'type' => $data['action'],
220
            'object_id' => (string) $data['id'],
221
            'discriminator' => $data['discriminator'],
222
            'transaction_hash' => (string) $data['transaction_hash'],
223
            'diffs' => json_encode($diff, JSON_THROW_ON_ERROR),
224
            'blame_id' => $data['blame']['user_id'],
225
            'blame_user' => $data['blame']['username'],
226
            'blame_user_fqdn' => $data['blame']['user_fqdn'],
227
            'blame_user_firewall' => $data['blame']['user_firewall'],
228
            'ip' => $data['blame']['client_ip'],
229
            'created_at' => $dt->format('Y-m-d H:i:s.u'),
230
        ];
231
232
        // send an `AuditEvent` event
233
        $this->notify($payload);
234
    }
235
236
    private function getDiscriminator(object $entity, int $inheritanceType): ?string
237
    {
238
        return ClassMetadata::INHERITANCE_TYPE_SINGLE_TABLE === $inheritanceType ? DoctrineHelper::getRealClassName($entity) : null;
239
    }
240
}
241