Passed
Pull Request — master (#82)
by Damien
03:02
created

AuditReader::getAuditsByTransactionHash()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 13
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

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