Passed
Push — master ( 2a3d08...8001cb )
by Damien
03:13
created

DoctrineProvider::setStorageMapper()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 2
c 1
b 0
f 0
dl 0
loc 5
rs 10
cc 1
nc 1
nop 1
1
<?php
2
3
namespace DH\Auditor\Provider\Doctrine;
4
5
use DH\Auditor\Event\LifecycleEvent;
6
use DH\Auditor\Exception\ProviderException;
7
use DH\Auditor\Provider\AbstractProvider;
8
use DH\Auditor\Provider\ConfigurationInterface;
9
use DH\Auditor\Provider\Doctrine\Auditing\Annotation\AnnotationLoader;
10
use DH\Auditor\Provider\Doctrine\Auditing\Event\DoctrineSubscriber;
11
use DH\Auditor\Provider\Doctrine\Auditing\Transaction\TransactionManager;
12
use DH\Auditor\Provider\Doctrine\Persistence\Event\CreateSchemaListener;
13
use DH\Auditor\Provider\Doctrine\Persistence\Helper\DoctrineHelper;
14
use DH\Auditor\Provider\Doctrine\Service\AuditingService;
15
use DH\Auditor\Provider\Doctrine\Service\StorageService;
16
use DH\Auditor\Provider\ProviderInterface;
17
use DH\Auditor\Provider\Service\AuditingServiceInterface;
18
use DH\Auditor\Provider\Service\StorageServiceInterface;
19
use Doctrine\ORM\EntityManagerInterface;
20
use Gedmo\SoftDeleteable\SoftDeleteableListener;
21
22
class DoctrineProvider extends AbstractProvider
23
{
24
    public const BOTH = 3;
25
26
    /**
27
     * @var TransactionManager
28
     */
29
    private $transactionManager;
30
31
    public function __construct(ConfigurationInterface $configuration)
32
    {
33
        $this->configuration = $configuration;
34
        $this->transactionManager = new TransactionManager($this);
35
    }
36
37
    public function registerAuditingService(AuditingServiceInterface $service): ProviderInterface
38
    {
39
        parent::registerAuditingService($service);
40
41
        /** @var AuditingService $service */
42
        $entityManager = $service->getEntityManager();
43
        $evm = $entityManager->getEventManager();
44
45
        // Register subscribers
46
        $evm->addEventSubscriber(new DoctrineSubscriber($this->transactionManager));
47
        $evm->addEventSubscriber(new SoftDeleteableListener());
48
49
        $this->loadAnnotations($entityManager);
50
51
        return $this;
52
    }
53
54
    public function registerStorageService(StorageServiceInterface $service): ProviderInterface
55
    {
56
        parent::registerStorageService($service);
57
58
        /** @var StorageService $service */
59
        $entityManager = $service->getEntityManager();
60
        $evm = $entityManager->getEventManager();
61
62
        // Register subscribers
63
        $evm->addEventSubscriber(new CreateSchemaListener($this));
64
65
        return $this;
66
    }
67
68
    public function isStorageMapperRequired(): bool
69
    {
70
        return \count($this->getStorageServices()) > 1;
71
    }
72
73
    public function getStorageServiceForEntity(string $entity): StorageServiceInterface
74
    {
75
        $this->checkStorageMapper();
76
77
        if (null === $this->getConfiguration()->getStorageMapper() && 1 === \count($this->getStorageServices())) {
0 ignored issues
show
Bug introduced by
The method getStorageMapper() does not exist on DH\Auditor\Provider\ConfigurationInterface. Since it exists in all sub-types, consider adding an abstract or default implementation to DH\Auditor\Provider\ConfigurationInterface. ( Ignorable by Annotation )

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

77
        if (null === $this->getConfiguration()->/** @scrutinizer ignore-call */ getStorageMapper() && 1 === \count($this->getStorageServices())) {
Loading history...
78
            // No mapper and only 1 storage entity manager
79
            return array_values($this->getStorageServices())[0];
80
        }
81
82
        return $this->getConfiguration()->getStorageMapper()->call($this, $entity, $this->getStorageServices());
83
    }
84
85
    public function persist(LifecycleEvent $event): void
86
    {
87
        $payload = $event->getPayload();
88
        $auditTable = $payload['table'];
89
        $entity = $payload['entity'];
90
        unset($payload['table'], $payload['entity']);
91
92
        $fields = [
93
            'type' => ':type',
94
            'object_id' => ':object_id',
95
            'discriminator' => ':discriminator',
96
            'transaction_hash' => ':transaction_hash',
97
            'diffs' => ':diffs',
98
            'blame_id' => ':blame_id',
99
            'blame_user' => ':blame_user',
100
            'blame_user_fqdn' => ':blame_user_fqdn',
101
            'blame_user_firewall' => ':blame_user_firewall',
102
            'ip' => ':ip',
103
            'created_at' => ':created_at',
104
        ];
105
106
        $query = sprintf(
107
            'INSERT INTO %s (%s) VALUES (%s)',
108
            $auditTable,
109
            implode(', ', array_keys($fields)),
110
            implode(', ', array_values($fields))
111
        );
112
113
        /** @var StorageService $storageService */
114
        $storageService = $this->getStorageServiceForEntity($entity);
115
        $statement = $storageService->getEntityManager()->getConnection()->prepare($query);
116
117
        foreach ($payload as $key => $value) {
118
            $statement->bindValue($key, $value);
119
        }
120
121
        $statement->execute();
122
    }
123
124
    /**
125
     * Returns true if $entity is auditable.
126
     *
127
     * @param object|string $entity
128
     */
129
    public function isAuditable($entity): bool
130
    {
131
        $class = DoctrineHelper::getRealClassName($entity);
132
        // is $entity part of audited entities?
133
        /** @var Configuration $configuration */
134
        $configuration = $this->configuration;
135
        if (!\array_key_exists($class, $configuration->getEntities())) {
136
            // no => $entity is not audited
137
            return false;
138
        }
139
140
        return true;
141
    }
142
143
    /**
144
     * Returns true if $entity is audited.
145
     *
146
     * @param object|string $entity
147
     */
148
    public function isAudited($entity): bool
149
    {
150
        if (!$this->auditor->getConfiguration()->isEnabled()) {
151
            return false;
152
        }
153
154
        /** @var Configuration $configuration */
155
        $configuration = $this->configuration;
156
        $class = DoctrineHelper::getRealClassName($entity);
157
158
        // is $entity part of audited entities?
159
        if (!\array_key_exists($class, $configuration->getEntities())) {
160
            // no => $entity is not audited
161
            return false;
162
        }
163
164
        $entityOptions = $configuration->getEntities()[$class];
165
166
        if (null === $entityOptions) {
167
            // no option defined => $entity is audited
168
            return true;
169
        }
170
171
        if (isset($entityOptions['enabled'])) {
172
            return (bool) $entityOptions['enabled'];
173
        }
174
175
        return true;
176
    }
177
178
    /**
179
     * Returns true if $field is audited.
180
     *
181
     * @param object|string $entity
182
     */
183
    public function isAuditedField($entity, string $field): bool
184
    {
185
        // is $field is part of globally ignored columns?
186
        /** @var Configuration $configuration */
187
        $configuration = $this->configuration;
188
        if (\in_array($field, $configuration->getIgnoredColumns(), true)) {
189
            // yes => $field is not audited
190
            return false;
191
        }
192
193
        // is $entity audited?
194
        if (!$this->isAudited($entity)) {
195
            // no => $field is not audited
196
            return false;
197
        }
198
199
        $class = DoctrineHelper::getRealClassName($entity);
200
        $entityOptions = $configuration->getEntities()[$class];
201
202
        if (null === $entityOptions) {
203
            // no option defined => $field is audited
204
            return true;
205
        }
206
207
        // are columns excluded and is field part of them?
208
        if (isset($entityOptions['ignored_columns']) &&
209
            \in_array($field, $entityOptions['ignored_columns'], true)) {
210
            // yes => $field is not audited
211
            return false;
212
        }
213
214
        return true;
215
    }
216
217
    public function supportsStorage(): bool
218
    {
219
        return true;
220
    }
221
222
    public function supportsAuditing(): bool
223
    {
224
        return true;
225
    }
226
227
    private function loadAnnotations(EntityManagerInterface $entityManager): self
228
    {
229
        /** @var Configuration $configuration */
230
        $configuration = $this->configuration;
231
        $annotationLoader = new AnnotationLoader($entityManager);
232
        $configuration->setEntities(array_merge(
233
            $configuration->getEntities(),
234
            $annotationLoader->load()
235
        ));
236
237
        return $this;
238
    }
239
240
    private function checkStorageMapper(): self
241
    {
242
        if (null === $this->getConfiguration()->getStorageMapper() && $this->isStorageMapperRequired()) {
243
            throw new ProviderException('You must provide a mapper function to map audits to storage.');
244
        }
245
246
//        if (null === $this->getStorageMapper() && 1 === count($this->getStorageServices())) {
247
//            // No mapper and only 1 storage entity manager
248
//            return array_values($this->storageServices)[0];
249
//        }
250
251
        return $this;
252
    }
253
}
254