Passed
Pull Request — master (#93)
by Damien
02:57
created

AuditReader::getEntityAuditTableName()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 3
nc 4
nop 1
dl 0
loc 6
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace DH\DoctrineAuditBundle\Reader;
4
5
use DH\DoctrineAuditBundle\AuditConfiguration;
6
use Doctrine\Common\Persistence\Mapping\ClassMetadata;
7
use Doctrine\DBAL\Query\QueryBuilder;
8
use Doctrine\DBAL\Statement;
9
use Doctrine\ORM\EntityManagerInterface;
10
use Doctrine\ORM\Mapping\ClassMetadata as ORMMetadata;
11
use Pagerfanta\Adapter\DoctrineDbalSingleTableAdapter;
12
use Pagerfanta\Pagerfanta;
13
14
class AuditReader
15
{
16
    public const UPDATE = 'update';
17
    public const ASSOCIATE = 'associate';
18
    public const DISSOCIATE = 'dissociate';
19
    public const INSERT = 'insert';
20
    public const REMOVE = 'remove';
21
22
    public const PAGE_SIZE = 50;
23
24
    /**
25
     * @var AuditConfiguration
26
     */
27
    private $configuration;
28
29
    /**
30
     * @var EntityManagerInterface
31
     */
32
    private $entityManager;
33
34
    /**
35
     * @var ?string
36
     */
37
    private $filter;
38
39
    /**
40
     * AuditReader constructor.
41
     *
42
     * @param AuditConfiguration     $configuration
43
     * @param EntityManagerInterface $entityManager
44
     */
45
    public function __construct(
46
        AuditConfiguration $configuration,
47
        EntityManagerInterface $entityManager
48
    ) {
49
        $this->configuration = $configuration;
50
        $this->entityManager = $entityManager;
51
    }
52
53
    /**
54
     * @return AuditConfiguration
55
     */
56
    public function getConfiguration(): AuditConfiguration
57
    {
58
        return $this->configuration;
59
    }
60
61
    /**
62
     * Set the filter for AuditEntry retrieving.
63
     *
64
     * @param string $filter
65
     *
66
     * @return AuditReader
67
     */
68
    public function filterBy(string $filter): self
69
    {
70
        if (!\in_array($filter, [self::UPDATE, self::ASSOCIATE, self::DISSOCIATE, self::INSERT, self::REMOVE], true)) {
71
            $this->filter = null;
72
        } else {
73
            $this->filter = $filter;
74
        }
75
76
        return $this;
77
    }
78
79
    /**
80
     * Returns current filter.
81
     *
82
     * @return null|string
83
     */
84
    public function getFilter(): ?string
85
    {
86
        return $this->filter;
87
    }
88
89
    /**
90
     * Returns an array of audit table names indexed by entity FQN.
91
     *
92
     * @throws \Doctrine\ORM\ORMException
93
     *
94
     * @return array
95
     */
96
    public function getEntities(): array
97
    {
98
        $metadataDriver = $this->entityManager->getConfiguration()->getMetadataDriverImpl();
99
        $entities = [];
100
        if (null !== $metadataDriver) {
101
            $entities = $metadataDriver->getAllClassNames();
102
        }
103
        $audited = [];
104
        foreach ($entities as $entity) {
105
            if ($this->configuration->isAuditable($entity)) {
106
                $audited[$entity] = $this->getEntityTableName($entity);
107
            }
108
        }
109
        ksort($audited);
110
111
        return $audited;
112
    }
113
114
    /**
115
     * Returns an array of audited entries/operations.
116
     *
117
     * @param object|string $entity
118
     * @param int|string    $id
119
     * @param null|int      $page
120
     * @param null|int      $pageSize
121
     * @param null|string   $transactionHash
122
     * @param null|bool     $strict
123
     *
124
     * @return array
125
     */
126
    public function getAudits($entity, $id = null, ?int $page = null, ?int $pageSize = null, ?string $transactionHash = null, ?bool $strict = true): array
127
    {
128
        $queryBuilder = $this->getAuditsQueryBuilder($entity, $id, $page, $pageSize, $transactionHash, $strict);
129
130
        /** @var Statement $statement */
131
        $statement = $queryBuilder->execute();
132
        $statement->setFetchMode(\PDO::FETCH_CLASS, AuditEntry::class);
133
134
        return $statement->fetchAll();
135
    }
136
137
    /**
138
     * Returns an array of all audited entries/operations for a given transaction hash
139
     * indexed by entity FQCN.
140
     *
141
     * @param string $transactionHash
142
     *
143
     * @throws \Doctrine\ORM\ORMException
144
     *
145
     * @return array
146
     */
147
    public function getAuditsByTransactionHash(string $transactionHash): array
148
    {
149
        $results = [];
150
151
        $entities = $this->getEntities();
152
        foreach ($entities as $entity => $tablename) {
153
            $audits = $this->getAudits($entity, null, null, null, $transactionHash);
154
            if (\count($audits) > 0) {
155
                $results[$entity] = $audits;
156
            }
157
        }
158
159
        return $results;
160
    }
161
162
    /**
163
     * Returns an array of audited entries/operations.
164
     *
165
     * @param object|string $entity
166
     * @param int|string    $id
167
     * @param int           $page
168
     * @param int           $pageSize
169
     *
170
     * @return Pagerfanta
171
     */
172
    public function getAuditsPager($entity, $id = null, int $page = 1, int $pageSize = self::PAGE_SIZE): Pagerfanta
173
    {
174
        $queryBuilder = $this->getAuditsQueryBuilder($entity, $id);
175
176
        $adapter = new DoctrineDbalSingleTableAdapter($queryBuilder, 'at.id');
177
178
        $pagerfanta = new Pagerfanta($adapter);
179
        $pagerfanta
180
            ->setMaxPerPage($pageSize)
181
            ->setCurrentPage($page)
182
        ;
183
184
        return $pagerfanta;
185
    }
186
187
    /**
188
     * Returns the amount of audited entries/operations.
189
     *
190
     * @param object|string $entity
191
     * @param int|string    $id
192
     *
193
     * @return int
194
     */
195
    public function getAuditsCount($entity, $id = null): int
196
    {
197
        $queryBuilder = $this->getAuditsQueryBuilder($entity, $id);
198
199
        $result = $queryBuilder
200
            ->resetQueryPart('select')
201
            ->resetQueryPart('orderBy')
202
            ->select('COUNT(id)')
203
            ->execute()
204
            ->fetchColumn(0)
205
        ;
206
207
        return false === $result ? 0 : $result;
208
    }
209
210
    /**
211
     * Returns an array of audited entries/operations.
212
     *
213
     * @param object|string $entity
214
     * @param int|string    $id
215
     * @param int           $page
216
     * @param int           $pageSize
217
     * @param null|string   $transactionHash
218
     * @param null|bool     $strict          Only applies with SINGLE_TABLE inheritance
219
     *
220
     * @return QueryBuilder
221
     */
222
    private function getAuditsQueryBuilder($entity, $id = null, ?int $page = null, ?int $pageSize = null, ?string $transactionHash = null, ?bool $strict = true): QueryBuilder
223
    {
224
        if (null !== $page && $page < 1) {
225
            throw new \InvalidArgumentException('$page must be greater or equal than 1.');
226
        }
227
228
        if (null !== $pageSize && $pageSize < 1) {
229
            throw new \InvalidArgumentException('$pageSize must be greater or equal than 1.');
230
        }
231
232
        $storage = $this->selectStorage();
233
        $connection = $storage->getConnection();
234
235
        $queryBuilder = $connection->createQueryBuilder();
236
        $queryBuilder
237
            ->select('*')
238
            ->from($this->getEntityAuditTableName($entity), 'at')
239
            ->orderBy('created_at', 'DESC')
240
            ->addOrderBy('id', 'DESC')
241
        ;
242
243
        $metadata = $this->getClassMetadata($entity);
244
        if ($strict && $metadata instanceof ORMMetadata && ORMMetadata::INHERITANCE_TYPE_SINGLE_TABLE === $metadata->inheritanceType) {
245
            $queryBuilder
246
                ->andWhere('discriminator = :discriminator')
247
                ->setParameter('discriminator', \is_object($entity) ? \get_class($entity) : $entity)
248
            ;
249
        }
250
251
        if (null !== $pageSize) {
252
            $queryBuilder
253
                ->setFirstResult(($page - 1) * $pageSize)
254
                ->setMaxResults($pageSize)
255
            ;
256
        }
257
258
        if (null !== $id) {
259
            $queryBuilder
260
                ->andWhere('object_id = :object_id')
261
                ->setParameter('object_id', $id)
262
            ;
263
        }
264
265
        if (null !== $this->filter) {
266
            $queryBuilder
267
                ->andWhere('type = :filter')
268
                ->setParameter('filter', $this->filter)
269
            ;
270
        }
271
272
        if (null !== $transactionHash) {
273
            $queryBuilder
274
                ->andWhere('transaction_hash = :transaction_hash')
275
                ->setParameter('transaction_hash', $transactionHash)
276
            ;
277
        }
278
279
        return $queryBuilder;
280
    }
281
282
    /**
283
     * @param object|string $entity
284
     * @param int|string    $id
285
     *
286
     * @return mixed
287
     */
288
    public function getAudit($entity, $id)
289
    {
290
        $connection = $this->entityManager->getConnection();
291
292
        /**
293
         * @var \Doctrine\DBAL\Query\QueryBuilder
294
         */
295
        $queryBuilder = $connection->createQueryBuilder();
296
        $queryBuilder
297
            ->select('*')
298
            ->from($this->getEntityAuditTableName($entity))
299
            ->where('id = :id')
300
            ->setParameter('id', $id)
301
        ;
302
303
        if (null !== $this->filter) {
304
            $queryBuilder
305
                ->andWhere('type = :filter')
306
                ->setParameter('filter', $this->filter)
307
            ;
308
        }
309
310
        /** @var Statement $statement */
311
        $statement = $queryBuilder->execute();
312
        $statement->setFetchMode(\PDO::FETCH_CLASS, AuditEntry::class);
313
314
        return $statement->fetchAll();
315
    }
316
317
    private function getClassMetadata($entity): ClassMetadata
318
    {
319
        return $this
320
            ->entityManager
321
            ->getClassMetadata($entity)
322
        ;
323
    }
324
325
    /**
326
     * Returns the table name of $entity.
327
     *
328
     * @param object|string $entity
329
     *
330
     * @return string
331
     */
332
    public function getEntityTableName($entity): string
333
    {
334
        return $this->getClassMetadata($entity)
335
            ->getTableName()
336
        ;
337
    }
338
339
    /**
340
     * Returns the audit table name for $entity.
341
     *
342
     * @param mixed $entity
343
     *
344
     * @return string
345
     */
346
    public function getEntityAuditTableName($entity): string
347
    {
348
        $entityName = \is_string($entity) ? $entity : \get_class($entity);
349
        $schema = $this->getClassMetadata($entityName)->getSchemaName() ? $this->getClassMetadata($entityName)->getSchemaName().'.' : '';
350
351
        return sprintf('%s%s%s%s', $schema, $this->configuration->getTablePrefix(), $this->getEntityTableName($entityName), $this->configuration->getTableSuffix());
352
    }
353
354
    /**
355
     * @return EntityManagerInterface
356
     */
357
    private function selectStorage(): EntityManagerInterface
358
    {
359
        return $this->configuration->getEntityManager() ?? $this->entityManager;
360
    }
361
362
    /**
363
     * @return EntityManagerInterface
364
     */
365
    public function getEntityManager(): EntityManagerInterface
366
    {
367
        return $this->entityManager;
368
    }
369
}
370