Passed
Push — master ( cb03cc...7adcbb )
by Damien
14:28
created

DoctrineProvider   A

Complexity

Total Complexity 36

Size/Duplication

Total Lines 298
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 36
eloc 105
dl 0
loc 298
rs 9.52
c 1
b 0
f 0

21 Methods

Rating   Name   Duplication   Size   Complexity  
A registerStorageService() 0 12 1
A getStorageServiceForEntity() 0 10 3
A isStorageMapperRequired() 0 3 1
A setIpProvider() 0 5 1
A isAuditable() 0 12 2
A isAuditedField() 0 32 6
A checkStorageMapper() 0 12 3
A loadAnnotations() 0 11 1
A getUserProvider() 0 3 1
A getStorageMapper() 0 3 1
A getIpProvider() 0 3 1
A setUserProvider() 0 5 1
A supportsStorage() 0 3 1
A persist() 0 37 2
A registerAuditingService() 0 15 1
A setRoleChecker() 0 5 1
A __construct() 0 4 1
A supportsAuditing() 0 3 1
A isAudited() 0 28 5
A getRoleChecker() 0 3 1
A setStorageMapper() 0 5 1
1
<?php
2
3
namespace DH\Auditor\Provider\Doctrine;
4
5
use Closure;
6
use DH\Auditor\Event\LifecycleEvent;
7
use DH\Auditor\Exception\ProviderException;
8
use DH\Auditor\Provider\AbstractProvider;
9
use DH\Auditor\Provider\ConfigurationInterface;
10
use DH\Auditor\Provider\Doctrine\Auditing\Annotation\AnnotationLoader;
11
use DH\Auditor\Provider\Doctrine\Auditing\Event\DoctrineSubscriber;
12
use DH\Auditor\Provider\Doctrine\Auditing\Transaction\TransactionManager;
13
use DH\Auditor\Provider\Doctrine\Persistence\Event\CreateSchemaListener;
14
use DH\Auditor\Provider\Doctrine\Persistence\Helper\DoctrineHelper;
15
use DH\Auditor\Provider\Doctrine\Service\AuditingService;
16
use DH\Auditor\Provider\Doctrine\Service\StorageService;
17
use DH\Auditor\Provider\ProviderInterface;
18
use DH\Auditor\Provider\Service\AuditingServiceInterface;
19
use DH\Auditor\Provider\Service\StorageServiceInterface;
20
use Doctrine\ORM\EntityManagerInterface;
21
use Gedmo\SoftDeleteable\SoftDeleteableListener;
22
23
class DoctrineProvider extends AbstractProvider
24
{
25
    public const BOTH = 3;
26
27
    /**
28
     * @var TransactionManager
29
     */
30
    private $transactionManager;
31
32
    /**
33
     * @var Closure
34
     */
35
    private $storageMapper;
36
37
    /**
38
     * @var Closure
39
     */
40
    private $userProvider;
41
42
    /**
43
     * @var Closure
44
     */
45
    private $roleChecker;
46
47
    /**
48
     * @var Closure
49
     */
50
    private $ipProvider;
51
52
    public function __construct(ConfigurationInterface $configuration)
53
    {
54
        $this->configuration = $configuration;
55
        $this->transactionManager = new TransactionManager($this);
56
    }
57
58
    public function registerAuditingService(AuditingServiceInterface $service): ProviderInterface
59
    {
60
        parent::registerAuditingService($service);
61
62
        /** @var AuditingService $service */
63
        $entityManager = $service->getEntityManager();
64
        $evm = $entityManager->getEventManager();
65
66
        // Register subscribers
67
        $evm->addEventSubscriber(new DoctrineSubscriber($this->transactionManager));
68
        $evm->addEventSubscriber(new SoftDeleteableListener());
69
70
        $this->loadAnnotations($entityManager);
71
72
        return $this;
73
    }
74
75
    public function registerStorageService(StorageServiceInterface $service): ProviderInterface
76
    {
77
        parent::registerStorageService($service);
78
79
        /** @var StorageService $service */
80
        $entityManager = $service->getEntityManager();
81
        $evm = $entityManager->getEventManager();
82
83
        // Register subscribers
84
        $evm->addEventSubscriber(new CreateSchemaListener($this));
85
86
        return $this;
87
    }
88
89
    public function setStorageMapper(Closure $mapper): self
90
    {
91
        $this->storageMapper = $mapper;
92
93
        return $this;
94
    }
95
96
    public function getStorageMapper(): ?Closure
97
    {
98
        return $this->storageMapper;
99
    }
100
101
    public function isStorageMapperRequired(): bool
102
    {
103
        return \count($this->getStorageServices()) > 1;
104
    }
105
106
    public function setUserProvider(Closure $userProvider): self
107
    {
108
        $this->userProvider = $userProvider;
109
110
        return $this;
111
    }
112
113
    public function getUserProvider(): ?Closure
114
    {
115
        return $this->userProvider;
116
    }
117
118
    public function setRoleChecker(Closure $roleChecker): self
119
    {
120
        $this->roleChecker = $roleChecker;
121
122
        return $this;
123
    }
124
125
    public function getRoleChecker(): ?Closure
126
    {
127
        return $this->roleChecker;
128
    }
129
130
    public function setIpProvider(Closure $ipProvider): self
131
    {
132
        $this->ipProvider = $ipProvider;
133
134
        return $this;
135
    }
136
137
    public function getIpProvider(): ?Closure
138
    {
139
        return $this->ipProvider;
140
    }
141
142
    public function getStorageServiceForEntity(string $entity): StorageServiceInterface
143
    {
144
        $this->checkStorageMapper();
145
146
        if (null === $this->storageMapper && 1 === \count($this->getStorageServices())) {
147
            // No mapper and only 1 storage entity manager
148
            return array_values($this->getStorageServices())[0];
149
        }
150
151
        return $this->storageMapper->call($this, $entity, $this->getStorageServices());
152
    }
153
154
    public function persist(LifecycleEvent $event): void
155
    {
156
        $payload = $event->getPayload();
157
        $auditTable = $payload['table'];
158
        $entity = $payload['entity'];
159
        unset($payload['table'], $payload['entity']);
160
161
        $fields = [
162
            'type' => ':type',
163
            'object_id' => ':object_id',
164
            'discriminator' => ':discriminator',
165
            'transaction_hash' => ':transaction_hash',
166
            'diffs' => ':diffs',
167
            'blame_id' => ':blame_id',
168
            'blame_user' => ':blame_user',
169
            'blame_user_fqdn' => ':blame_user_fqdn',
170
            'blame_user_firewall' => ':blame_user_firewall',
171
            'ip' => ':ip',
172
            'created_at' => ':created_at',
173
        ];
174
175
        $query = sprintf(
176
            'INSERT INTO %s (%s) VALUES (%s)',
177
            $auditTable,
178
            implode(', ', array_keys($fields)),
179
            implode(', ', array_values($fields))
180
        );
181
182
        /** @var StorageService $storageService */
183
        $storageService = $this->getStorageServiceForEntity($entity);
184
        $statement = $storageService->getEntityManager()->getConnection()->prepare($query);
185
186
        foreach ($payload as $key => $value) {
187
            $statement->bindValue($key, $value);
188
        }
189
190
        $statement->execute();
191
    }
192
193
    /**
194
     * Returns true if $entity is auditable.
195
     *
196
     * @param object|string $entity
197
     */
198
    public function isAuditable($entity): bool
199
    {
200
        $class = DoctrineHelper::getRealClassName($entity);
201
        // is $entity part of audited entities?
202
        /** @var Configuration $configuration */
203
        $configuration = $this->configuration;
204
        if (!\array_key_exists($class, $configuration->getEntities())) {
205
            // no => $entity is not audited
206
            return false;
207
        }
208
209
        return true;
210
    }
211
212
    /**
213
     * Returns true if $entity is audited.
214
     *
215
     * @param object|string $entity
216
     */
217
    public function isAudited($entity): bool
218
    {
219
        if (!$this->auditor->getConfiguration()->isEnabled()) {
220
            return false;
221
        }
222
223
        /** @var Configuration $configuration */
224
        $configuration = $this->configuration;
225
        $class = DoctrineHelper::getRealClassName($entity);
226
227
        // is $entity part of audited entities?
228
        if (!\array_key_exists($class, $configuration->getEntities())) {
229
            // no => $entity is not audited
230
            return false;
231
        }
232
233
        $entityOptions = $configuration->getEntities()[$class];
234
235
        if (null === $entityOptions) {
236
            // no option defined => $entity is audited
237
            return true;
238
        }
239
240
        if (isset($entityOptions['enabled'])) {
241
            return (bool) $entityOptions['enabled'];
242
        }
243
244
        return true;
245
    }
246
247
    /**
248
     * Returns true if $field is audited.
249
     *
250
     * @param object|string $entity
251
     */
252
    public function isAuditedField($entity, string $field): bool
253
    {
254
        // is $field is part of globally ignored columns?
255
        /** @var Configuration $configuration */
256
        $configuration = $this->configuration;
257
        if (\in_array($field, $configuration->getIgnoredColumns(), true)) {
258
            // yes => $field is not audited
259
            return false;
260
        }
261
262
        // is $entity audited?
263
        if (!$this->isAudited($entity)) {
264
            // no => $field is not audited
265
            return false;
266
        }
267
268
        $class = DoctrineHelper::getRealClassName($entity);
269
        $entityOptions = $configuration->getEntities()[$class];
270
271
        if (null === $entityOptions) {
272
            // no option defined => $field is audited
273
            return true;
274
        }
275
276
        // are columns excluded and is field part of them?
277
        if (isset($entityOptions['ignored_columns']) &&
278
            \in_array($field, $entityOptions['ignored_columns'], true)) {
279
            // yes => $field is not audited
280
            return false;
281
        }
282
283
        return true;
284
    }
285
286
    public function supportsStorage(): bool
287
    {
288
        return true;
289
    }
290
291
    public function supportsAuditing(): bool
292
    {
293
        return true;
294
    }
295
296
    private function loadAnnotations(EntityManagerInterface $entityManager): self
297
    {
298
        /** @var Configuration $configuration */
299
        $configuration = $this->configuration;
300
        $annotationLoader = new AnnotationLoader($entityManager);
301
        $configuration->setEntities(array_merge(
302
            $configuration->getEntities(),
303
            $annotationLoader->load()
304
        ));
305
306
        return $this;
307
    }
308
309
    private function checkStorageMapper(): self
310
    {
311
        if (null === $this->storageMapper && $this->isStorageMapperRequired()) {
312
            throw new ProviderException('You must provide a mapper function to map audits to storage.');
313
        }
314
315
//        if (null === $this->storageMapper && 1 === count($this->getStorageServices())) {
316
//            // No mapper and only 1 storage entity manager
317
//            return array_values($this->storageServices)[0];
318
//        }
319
320
        return $this;
321
    }
322
}
323