Passed
Pull Request — master (#20)
by Damien
07:23
created

TransactionProcessor::dissociate()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 1
dl 0
loc 3
c 0
b 0
f 0
rs 10
cc 1
nc 1
nop 5
1
<?php
2
3
namespace DH\Auditor\Provider\Doctrine\Auditing\Transaction;
4
5
use DateTime;
6
use DateTimeZone;
7
use DH\Auditor\Event\LifecycleEvent;
8
use DH\Auditor\Model\TransactionInterface;
9
use DH\Auditor\Provider\Doctrine\Configuration;
10
use DH\Auditor\Provider\Doctrine\DoctrineProvider;
11
use DH\Auditor\Provider\Doctrine\Model\Transaction;
12
use DH\Auditor\Provider\Doctrine\Persistence\Helper\DoctrineHelper;
13
use DH\Auditor\Transaction\TransactionProcessorInterface;
14
use Doctrine\ORM\EntityManagerInterface;
15
use Doctrine\ORM\Mapping\ClassMetadata;
16
17
class TransactionProcessor implements TransactionProcessorInterface
18
{
19
    use AuditTrait;
20
21
    /**
22
     * @var DoctrineProvider
23
     */
24
    private $provider;
25
26
    public function __construct(DoctrineProvider $provider)
27
    {
28
        $this->provider = $provider;
29
    }
30
31
    /**
32
     * @param Transaction $transaction
33
     */
34
    public function process(TransactionInterface $transaction): void
35
    {
36
        $this->processInsertions($transaction, $transaction->getEntityManager());
37
        $this->processUpdates($transaction, $transaction->getEntityManager());
38
        $this->processAssociations($transaction, $transaction->getEntityManager());
39
        $this->processDissociations($transaction, $transaction->getEntityManager());
40
        $this->processDeletions($transaction, $transaction->getEntityManager());
41
    }
42
43
    private function notify(array $payload): void
44
    {
45
        $dispatcher = $this->provider->getAuditor()->getEventDispatcher();
46
47
        if ($this->provider->getAuditor()->isPre43Dispatcher()) {
48
            // Symfony 3.x
49
            $dispatcher->dispatch(LifecycleEvent::class, new LifecycleEvent($payload));
50
        } else {
51
            // Symfony >= 4.x
52
            $dispatcher->dispatch(new LifecycleEvent($payload));
53
        }
54
    }
55
56
    /**
57
     * Adds an insert entry to the audit table.
58
     */
59
    private function insert(EntityManagerInterface $entityManager, object $entity, array $ch, string $transactionHash): void
60
    {
61
        $meta = $entityManager->getClassMetadata(DoctrineHelper::getRealClassName($entity));
62
        $this->audit([
63
            'action' => 'insert',
64
            'blame' => $this->blame(),
65
            'diff' => $this->diff($entityManager, $entity, $ch),
66
            'table' => $meta->getTableName(),
67
            'schema' => $meta->getSchemaName(),
68
            'id' => $this->id($entityManager, $entity),
69
            'transaction_hash' => $transactionHash,
70
            'discriminator' => $this->getDiscriminator($entity, $meta->inheritanceType),
71
            'entity' => $meta->getName(),
72
        ]);
73
    }
74
75
    /**
76
     * Adds an update entry to the audit table.
77
     */
78
    private function update(EntityManagerInterface $entityManager, object $entity, array $ch, string $transactionHash): void
79
    {
80
        $diff = $this->diff($entityManager, $entity, $ch);
81
        if (0 === \count($diff)) {
82
            return; // if there is no entity diff, do not log it
83
        }
84
85
        $meta = $entityManager->getClassMetadata(DoctrineHelper::getRealClassName($entity));
86
        $this->audit([
87
            'action' => 'update',
88
            'blame' => $this->blame(),
89
            'diff' => $diff,
90
            'table' => $meta->getTableName(),
91
            'schema' => $meta->getSchemaName(),
92
            'id' => $this->id($entityManager, $entity),
93
            'transaction_hash' => $transactionHash,
94
            'discriminator' => $this->getDiscriminator($entity, $meta->inheritanceType),
95
            'entity' => $meta->getName(),
96
        ]);
97
    }
98
99
    /**
100
     * Adds a remove entry to the audit table.
101
     *
102
     * @param mixed $id
103
     */
104
    private function remove(EntityManagerInterface $entityManager, object $entity, $id, string $transactionHash): void
105
    {
106
        $meta = $entityManager->getClassMetadata(DoctrineHelper::getRealClassName($entity));
107
        $this->audit([
108
            'action' => 'remove',
109
            'blame' => $this->blame(),
110
            'diff' => $this->summarize($entityManager, $entity, $id),
111
            'table' => $meta->getTableName(),
112
            'schema' => $meta->getSchemaName(),
113
            'id' => $id,
114
            'transaction_hash' => $transactionHash,
115
            'discriminator' => $this->getDiscriminator($entity, $meta->inheritanceType),
116
            'entity' => $meta->getName(),
117
        ]);
118
    }
119
120
    /**
121
     * Adds an association entry to the audit table.
122
     */
123
    private function associate(EntityManagerInterface $entityManager, object $source, object $target, array $mapping, string $transactionHash): void
124
    {
125
        $this->associateOrDissociate('associate', $entityManager, $source, $target, $mapping, $transactionHash);
126
    }
127
128
    /**
129
     * Adds a dissociation entry to the audit table.
130
     */
131
    private function dissociate(EntityManagerInterface $entityManager, object $source, object $target, array $mapping, string $transactionHash): void
132
    {
133
        $this->associateOrDissociate('dissociate', $entityManager, $source, $target, $mapping, $transactionHash);
134
    }
135
136
    private function processInsertions(Transaction $transaction, EntityManagerInterface $entityManager): void
137
    {
138
        $uow = $entityManager->getUnitOfWork();
139
        foreach ($transaction->getInserted() as [$entity, $ch]) {
140
            // the changeset might be updated from UOW extra updates
141
            $ch = array_merge($ch, $uow->getEntityChangeSet($entity));
142
            $this->insert($entityManager, $entity, $ch, $transaction->getTransactionHash());
143
        }
144
    }
145
146
    private function processUpdates(Transaction $transaction, EntityManagerInterface $entityManager): void
147
    {
148
        $uow = $entityManager->getUnitOfWork();
149
        foreach ($transaction->getUpdated() as [$entity, $ch]) {
150
            // the changeset might be updated from UOW extra updates
151
            $ch = array_merge($ch, $uow->getEntityChangeSet($entity));
152
            $this->update($entityManager, $entity, $ch, $transaction->getTransactionHash());
153
        }
154
    }
155
156
    private function processAssociations(Transaction $transaction, EntityManagerInterface $entityManager): void
157
    {
158
        foreach ($transaction->getAssociated() as [$source, $target, $mapping]) {
159
            $this->associate($entityManager, $source, $target, $mapping, $transaction->getTransactionHash());
160
        }
161
    }
162
163
    private function processDissociations(Transaction $transaction, EntityManagerInterface $entityManager): void
164
    {
165
        foreach ($transaction->getDissociated() as [$source, $target, $id, $mapping]) {
166
            $this->dissociate($entityManager, $source, $target, $mapping, $transaction->getTransactionHash());
167
        }
168
    }
169
170
    private function processDeletions(Transaction $transaction, EntityManagerInterface $entityManager): void
171
    {
172
        foreach ($transaction->getRemoved() as [$entity, $id]) {
173
            $this->remove($entityManager, $entity, $id, $transaction->getTransactionHash());
174
        }
175
    }
176
177
    /**
178
     * Adds an association entry to the audit table.
179
     */
180
    private function associateOrDissociate(string $type, EntityManagerInterface $entityManager, object $source, object $target, array $mapping, string $transactionHash): void
181
    {
182
        $meta = $entityManager->getClassMetadata(DoctrineHelper::getRealClassName($source));
183
        $data = [
184
            'action' => $type,
185
            'blame' => $this->blame(),
186
            'diff' => [
187
                'source' => $this->summarize($entityManager, $source),
188
                'target' => $this->summarize($entityManager, $target),
189
                'is_owning_side' => $mapping['isOwningSide'],
190
            ],
191
            'table' => $meta->getTableName(),
192
            'schema' => $meta->getSchemaName(),
193
            'id' => $this->id($entityManager, $source),
194
            'transaction_hash' => $transactionHash,
195
            'discriminator' => $this->getDiscriminator($source, $meta->inheritanceType),
196
            'entity' => $meta->getName(),
197
        ];
198
199
        if (isset($mapping['joinTable']['name'])) {
200
            $data['diff']['table'] = $mapping['joinTable']['name'];
201
        }
202
203
        $this->audit($data);
204
    }
205
206
    /**
207
     * Adds an entry to the audit table.
208
     */
209
    private function audit(array $data): void
210
    {
211
        /** @var Configuration $configuration */
212
        $configuration = $this->provider->getConfiguration();
213
        $schema = $data['schema'] ? $data['schema'].'.' : '';
214
        $auditTable = $schema.$configuration->getTablePrefix().$data['table'].$configuration->getTableSuffix();
215
        $dt = new DateTime('now', new DateTimeZone($this->provider->getAuditor()->getConfiguration()->getTimezone()));
216
217
        $payload = [
218
            'entity' => $data['entity'],
219
            'table' => $auditTable,
220
            'type' => $data['action'],
221
            'object_id' => (string) $data['id'],
222
            'discriminator' => $data['discriminator'],
223
            'transaction_hash' => (string) $data['transaction_hash'],
224
            'diffs' => json_encode($data['diff']),
225
            'blame_id' => $data['blame']['user_id'],
226
            'blame_user' => $data['blame']['username'],
227
            'blame_user_fqdn' => $data['blame']['user_fqdn'],
228
            'blame_user_firewall' => $data['blame']['user_firewall'],
229
            'ip' => $data['blame']['client_ip'],
230
            'created_at' => $dt->format('Y-m-d H:i:s'),
231
        ];
232
233
        // send an `AuditEvent` event
234
        $this->notify($payload);
235
    }
236
237
    private function getDiscriminator(object $entity, int $inheritanceType): ?string
238
    {
239
        return ClassMetadata::INHERITANCE_TYPE_SINGLE_TABLE === $inheritanceType ? DoctrineHelper::getRealClassName($entity) : null;
240
    }
241
}
242