Passed
Pull Request — master (#24)
by Damien
05:39
created

DoctrineProvider::supportsAuditing()   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 0
1
<?php
2
3
namespace DH\Auditor\Provider\Doctrine;
4
5
use DH\Auditor\Event\LifecycleEvent;
6
use DH\Auditor\Exception\InvalidArgumentException;
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 Exception;
22
23
class DoctrineProvider extends AbstractProvider
24
{
25
    /**
26
     * @var TransactionManager
27
     */
28
    private $transactionManager;
29
30
    public function __construct(ConfigurationInterface $configuration)
31
    {
32
        $this->configuration = $configuration;
33
        $this->transactionManager = new TransactionManager($this);
34
    }
35
36
    public function registerAuditingService(AuditingServiceInterface $service): ProviderInterface
37
    {
38
        parent::registerAuditingService($service);
39
40
        \assert($service instanceof AuditingService);    // helps PHPStan
41
        $entityManager = $service->getEntityManager();
42
        $evm = $entityManager->getEventManager();
43
44
        // Register subscribers
45
        $evm->addEventSubscriber(new CreateSchemaListener($this));
46
        $evm->addEventSubscriber(new DoctrineSubscriber($this->transactionManager));
47
48
        $this->loadAnnotations($entityManager);
49
50
        return $this;
51
    }
52
53
    public function isStorageMapperRequired(): bool
54
    {
55
        return \count($this->getStorageServices()) > 1;
56
    }
57
58
    public function getAuditingServiceForEntity(string $entity): AuditingServiceInterface
59
    {
60
        foreach ($this->auditingServices as $name => $service) {
61
            \assert($service instanceof AuditingService);   // helps PHPStan
62
63
            try {
64
                // entity is managed by the entity manager of this service
65
                $service->getEntityManager()->getClassMetadata($entity)->getTableName();
66
67
                return $service;
68
            } catch (Exception $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
69
            }
70
        }
71
72
        throw new InvalidArgumentException(sprintf('Auditing service not found for "%s".', $entity));
73
    }
74
75
    public function getStorageServiceForEntity(string $entity): StorageServiceInterface
76
    {
77
        $this->checkStorageMapper();
78
79
        \assert($this->configuration instanceof Configuration);   // helps PHPStan
80
        $storageMapper = $this->configuration->getStorageMapper();
81
82
        if (null === $storageMapper || 1 === \count($this->getStorageServices())) {
83
            // No mapper and only 1 storage entity manager
84
            return array_values($this->getStorageServices())[0];
85
        }
86
87
        return $storageMapper($entity, $this->getStorageServices());
88
    }
89
90
    public function persist(LifecycleEvent $event): void
91
    {
92
        $payload = $event->getPayload();
93
        $auditTable = $payload['table'];
94
        $entity = $payload['entity'];
95
        unset($payload['table'], $payload['entity']);
96
97
        $fields = [
98
            'type' => ':type',
99
            'object_id' => ':object_id',
100
            'discriminator' => ':discriminator',
101
            'transaction_hash' => ':transaction_hash',
102
            'diffs' => ':diffs',
103
            'blame_id' => ':blame_id',
104
            'blame_user' => ':blame_user',
105
            'blame_user_fqdn' => ':blame_user_fqdn',
106
            'blame_user_firewall' => ':blame_user_firewall',
107
            'ip' => ':ip',
108
            'created_at' => ':created_at',
109
        ];
110
111
        $query = sprintf(
112
            'INSERT INTO %s (%s) VALUES (%s)',
113
            $auditTable,
114
            implode(', ', array_keys($fields)),
115
            implode(', ', array_values($fields))
116
        );
117
118
        /** @var StorageService $storageService */
119
        $storageService = $this->getStorageServiceForEntity($entity);
120
        $statement = $storageService->getEntityManager()->getConnection()->prepare($query);
121
122
        foreach ($payload as $key => $value) {
123
            $statement->bindValue($key, $value);
124
        }
125
126
        $statement->execute();
127
    }
128
129
    /**
130
     * Returns true if $entity is auditable.
131
     *
132
     * @param object|string $entity
133
     */
134
    public function isAuditable($entity): bool
135
    {
136
        $class = DoctrineHelper::getRealClassName($entity);
137
        // is $entity part of audited entities?
138
        \assert($this->configuration instanceof Configuration);   // helps PHPStan
139
        if (!\array_key_exists($class, $this->configuration->getEntities())) {
140
            // no => $entity is not audited
141
            return false;
142
        }
143
144
        return true;
145
    }
146
147
    /**
148
     * Returns true if $entity is audited.
149
     *
150
     * @param object|string $entity
151
     */
152
    public function isAudited($entity): bool
153
    {
154
        if (!$this->auditor->getConfiguration()->isEnabled()) {
155
            return false;
156
        }
157
158
        /** @var Configuration $configuration */
159
        $configuration = $this->configuration;
160
        $class = DoctrineHelper::getRealClassName($entity);
161
162
        // is $entity part of audited entities?
163
        if (!\array_key_exists($class, $configuration->getEntities())) {
164
            // no => $entity is not audited
165
            return false;
166
        }
167
168
        $entityOptions = $configuration->getEntities()[$class];
169
170
        if (null === $entityOptions) {
171
            // no option defined => $entity is audited
172
            return true;
173
        }
174
175
        if (isset($entityOptions['enabled'])) {
176
            return (bool) $entityOptions['enabled'];
177
        }
178
179
        return true;
180
    }
181
182
    /**
183
     * Returns true if $field is audited.
184
     *
185
     * @param object|string $entity
186
     */
187
    public function isAuditedField($entity, string $field): bool
188
    {
189
        // is $field is part of globally ignored columns?
190
        \assert($this->configuration instanceof Configuration);   // helps PHPStan
191
        if (\in_array($field, $this->configuration->getIgnoredColumns(), true)) {
192
            // yes => $field is not audited
193
            return false;
194
        }
195
196
        // is $entity audited?
197
        if (!$this->isAudited($entity)) {
198
            // no => $field is not audited
199
            return false;
200
        }
201
202
        $class = DoctrineHelper::getRealClassName($entity);
203
        $entityOptions = $this->configuration->getEntities()[$class];
204
205
        if (null === $entityOptions) {
206
            // no option defined => $field is audited
207
            return true;
208
        }
209
210
        // are columns excluded and is field part of them?
211
        if (isset($entityOptions['ignored_columns'])
212
            && \in_array($field, $entityOptions['ignored_columns'], true)) {
213
            // yes => $field is not audited
214
            return false;
215
        }
216
217
        return true;
218
    }
219
220
    public function supportsStorage(): bool
221
    {
222
        return true;
223
    }
224
225
    public function supportsAuditing(): bool
226
    {
227
        return true;
228
    }
229
230
    public function setStorageMapper(callable $storageMapper): void
231
    {
232
        \assert($this->configuration instanceof Configuration);   // helps PHPStan
233
        $this->configuration->setStorageMapper($storageMapper);
234
    }
235
236
    private function loadAnnotations(EntityManagerInterface $entityManager): self
237
    {
238
        \assert($this->configuration instanceof Configuration);   // helps PHPStan
239
        $annotationLoader = new AnnotationLoader($entityManager);
240
        $this->configuration->setEntities(array_merge(
241
            $this->configuration->getEntities(),
242
            $annotationLoader->load()
243
        ));
244
245
        return $this;
246
    }
247
248
    private function checkStorageMapper(): self
249
    {
250
        \assert($this->configuration instanceof Configuration);   // helps PHPStan
251
        if (null === $this->configuration->getStorageMapper() && $this->isStorageMapperRequired()) {
0 ignored issues
show
introduced by
The condition null === $this->configuration->getStorageMapper() is always false.
Loading history...
252
            throw new ProviderException('You must provide a mapper function to map audits to storage.');
253
        }
254
255
//        if (null === $this->getStorageMapper() && 1 === count($this->getStorageServices())) {
256
//            // No mapper and only 1 storage entity manager
257
//            return array_values($this->storageServices)[0];
258
//        }
259
260
        return $this;
261
    }
262
}
263