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

AuditReader::getAuditsByTransactionHash()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 5
c 0
b 0
f 0
nc 2
nop 1
dl 0
loc 10
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
            $results[$entity] = $this->getAudits($entity, null, null, null, $transactionHash);
152
        }
153
154
        return $results;
155
    }
156
157
    /**
158
     * Returns an array of audited entries/operations.
159
     *
160
     * @param object|string $entity
161
     * @param int|string    $id
162
     * @param int           $page
163
     * @param int           $pageSize
164
     *
165
     * @return Pagerfanta
166
     */
167
    public function getAuditsPager($entity, $id = null, int $page = 1, int $pageSize = self::PAGE_SIZE): Pagerfanta
168
    {
169
        $queryBuilder = $this->getAuditsQueryBuilder($entity, $id);
170
171
        $adapter = new DoctrineDbalSingleTableAdapter($queryBuilder, 'at.id');
172
173
        $pagerfanta = new Pagerfanta($adapter);
174
        $pagerfanta
175
            ->setMaxPerPage($pageSize)
176
            ->setCurrentPage($page)
177
        ;
178
179
        return $pagerfanta;
180
    }
181
182
    /**
183
     * Returns the amount of audited entries/operations.
184
     *
185
     * @param object|string $entity
186
     * @param int|string    $id
187
     *
188
     * @return int
189
     */
190
    public function getAuditsCount($entity, $id = null): int
191
    {
192
        $queryBuilder = $this->getAuditsQueryBuilder($entity, $id);
193
194
        $result = $queryBuilder
195
            ->resetQueryPart('select')
196
            ->resetQueryPart('orderBy')
197
            ->select('COUNT(id)')
198
            ->execute()
199
            ->fetchColumn(0)
200
        ;
201
202
        return false === $result ? 0 : $result;
203
    }
204
205
    /**
206
     * Returns an array of audited entries/operations.
207
     *
208
     * @param object|string $entity
209
     * @param int|string    $id
210
     * @param int           $page
211
     * @param int           $pageSize
212
     * @param null|string   $transactionHash
213
     *
214
     * @return QueryBuilder
215
     */
216
    private function getAuditsQueryBuilder($entity, $id = null, ?int $page = null, ?int $pageSize = null, ?string $transactionHash = null): QueryBuilder
217
    {
218
        if (null !== $page && $page < 1) {
219
            throw new \InvalidArgumentException('$page must be greater or equal than 1.');
220
        }
221
222
        if (null !== $pageSize && $pageSize < 1) {
223
            throw new \InvalidArgumentException('$pageSize must be greater or equal than 1.');
224
        }
225
226
        $storage = $this->selectStorage();
227
        $connection = $storage->getConnection();
228
229
        $queryBuilder = $connection->createQueryBuilder();
230
        $queryBuilder
231
            ->select('*')
232
            ->from($this->getEntityAuditTableName($entity), 'at')
233
            ->orderBy('created_at', 'DESC')
234
            ->addOrderBy('id', 'DESC')
235
        ;
236
237
        if (null !== $pageSize) {
238
            $queryBuilder
239
                ->setFirstResult(($page - 1) * $pageSize)
240
                ->setMaxResults($pageSize)
241
            ;
242
        }
243
244
        if (null !== $id) {
245
            $queryBuilder
246
                ->andWhere('object_id = :object_id')
247
                ->setParameter('object_id', $id)
248
            ;
249
        }
250
251
        if (null !== $this->filter) {
252
            $queryBuilder
253
                ->andWhere('type = :filter')
254
                ->setParameter('filter', $this->filter)
255
            ;
256
        }
257
258
        if (null !== $transactionHash) {
259
            $queryBuilder
260
                ->andWhere('transaction_hash = :transaction_hash')
261
                ->setParameter('transaction_hash', $transactionHash)
262
            ;
263
        }
264
265
        return $queryBuilder;
266
    }
267
268
    /**
269
     * @param object|string $entity
270
     * @param int|string    $id
271
     *
272
     * @return mixed
273
     */
274
    public function getAudit($entity, $id)
275
    {
276
        $connection = $this->entityManager->getConnection();
277
278
        /**
279
         * @var \Doctrine\DBAL\Query\QueryBuilder
280
         */
281
        $queryBuilder = $connection->createQueryBuilder();
282
        $queryBuilder
283
            ->select('*')
284
            ->from($this->getEntityAuditTableName($entity))
285
            ->where('id = :id')
286
            ->setParameter('id', $id)
287
        ;
288
289
        if (null !== $this->filter) {
290
            $queryBuilder
291
                ->andWhere('type = :filter')
292
                ->setParameter('filter', $this->filter)
293
            ;
294
        }
295
296
        /** @var Statement $statement */
297
        $statement = $queryBuilder->execute();
298
        $statement->setFetchMode(\PDO::FETCH_CLASS, AuditEntry::class);
299
300
        return $statement->fetchAll();
301
    }
302
303
    private function getClassMetadata($entity): ClassMetadata
304
    {
305
        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...
306
            ->entityManager
307
            ->getClassMetadata($entity)
308
        ;
309
    }
310
311
    /**
312
     * Returns the table name of $entity.
313
     *
314
     * @param object|string $entity
315
     *
316
     * @return string
317
     */
318
    public function getEntityTableName($entity): string
319
    {
320
        return $this->getClassMetadata($entity)
321
            ->getTableName()
322
        ;
323
    }
324
325
    /**
326
     * Returns the audit table name for $entity.
327
     *
328
     * @param mixed $entity
329
     *
330
     * @return string
331
     */
332
    public function getEntityAuditTableName($entity): string
333
    {
334
        $entityName = \is_string($entity) ? $entity : \get_class($entity);
335
        $schema = $this->getClassMetadata($entityName)->getSchemaName() ? $this->getClassMetadata($entityName)->getSchemaName().'.' : '';
336
337
        return sprintf('%s%s%s%s', $schema, $this->configuration->getTablePrefix(), $this->getEntityTableName($entityName), $this->configuration->getTableSuffix());
338
    }
339
340
    /**
341
     * @return EntityManagerInterface
342
     */
343
    private function selectStorage(): EntityManagerInterface
344
    {
345
        return $this->configuration->getCustomStorageEntityManager() ?? $this->entityManager;
346
    }
347
}
348