Passed
Pull Request — master (#78)
by Damien
02:47
created

AuditReader::selectStorage()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
c 1
b 0
f 0
nc 1
nop 0
dl 0
loc 3
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
    const UPDATE = 'update';
16
    const ASSOCIATE = 'associate';
17
    const DISSOCIATE = 'dissociate';
18
    const INSERT = 'insert';
19
    const REMOVE = 'remove';
20
21
    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
     *
121
     * @return array
122
     */
123
    public function getAudits($entity, $id = null, ?int $page = null, ?int $pageSize = null): array
124
    {
125
        $queryBuilder = $this->getAuditsQueryBuilder($entity, $id, $page, $pageSize);
126
127
        /** @var Statement $statement */
128
        $statement = $queryBuilder->execute();
129
        $statement->setFetchMode(\PDO::FETCH_CLASS, AuditEntry::class);
130
131
        return $statement->fetchAll();
132
    }
133
134
    /**
135
     * Returns an array of audited entries/operations.
136
     *
137
     * @param object|string $entity
138
     * @param int|string    $id
139
     * @param null|int      $page
140
     * @param null|int      $pageSize
141
     *
142
     * @return Pagerfanta
143
     */
144
    public function getAuditsPager($entity, $id = null, int $page = 1, int $pageSize = self::PAGE_SIZE): Pagerfanta
145
    {
146
        $queryBuilder = $this->getAuditsQueryBuilder($entity, $id);
147
148
        $adapter = new DoctrineDbalSingleTableAdapter($queryBuilder, 'at.id');
149
150
        $pagerfanta = new Pagerfanta($adapter);
151
        $pagerfanta
152
            ->setMaxPerPage($pageSize)
153
            ->setCurrentPage($page)
154
        ;
155
156
        return $pagerfanta;
157
    }
158
159
    /**
160
     * Returns the amount of audited entries/operations.
161
     *
162
     * @param object|string $entity
163
     * @param int|string    $id
164
     *
165
     * @throws \Doctrine\ORM\NonUniqueResultException
166
     *
167
     * @return int
168
     */
169
    public function getAuditsCount($entity, $id = null): int
170
    {
171
        $queryBuilder = $this->getAuditsQueryBuilder($entity, $id);
172
173
        $result = $queryBuilder
174
            ->resetQueryPart('select')
175
            ->resetQueryPart('orderBy')
176
            ->select('COUNT(id)')
177
            ->execute()
178
            ->fetchColumn(0)
179
        ;
180
181
        return false === $result ? 0 : $result;
182
    }
183
184
    /**
185
     * Returns an array of audited entries/operations.
186
     *
187
     * @param object|string $entity
188
     * @param int|string    $id
189
     * @param int           $page
190
     * @param int           $pageSize
191
     *
192
     * @return QueryBuilder
193
     */
194
    private function getAuditsQueryBuilder($entity, $id = null, ?int $page = null, ?int $pageSize = null): QueryBuilder
195
    {
196
        if (null !== $page && $page < 1) {
197
            throw new \InvalidArgumentException('$page must be greater or equal than 1.');
198
        }
199
200
        if (null !== $pageSize && $pageSize < 1) {
201
            throw new \InvalidArgumentException('$pageSize must be greater or equal than 1.');
202
        }
203
204
        $storage = $this->selectStorage();
205
        $connection = $storage->getConnection();
206
207
        $queryBuilder = $connection->createQueryBuilder();
208
        $queryBuilder
209
            ->select('*')
210
            ->from($this->getEntityAuditTableName($entity), 'at')
211
            ->orderBy('created_at', 'DESC')
212
            ->addOrderBy('id', 'DESC')
213
        ;
214
215
        if (null !== $pageSize) {
216
            $queryBuilder
217
                ->setFirstResult(($page - 1) * $pageSize)
218
                ->setMaxResults($pageSize)
219
            ;
220
        }
221
222
        if (null !== $id) {
223
            $queryBuilder
224
                ->andWhere('object_id = :object_id')
225
                ->setParameter('object_id', $id);
226
        }
227
228
        if (null !== $this->filter) {
229
            $queryBuilder
230
                ->andWhere('type = :filter')
231
                ->setParameter('filter', $this->filter);
232
        }
233
234
        return $queryBuilder;
235
    }
236
237
    /**
238
     * @param object|string $entity
239
     * @param int|string    $id
240
     *
241
     * @return mixed
242
     */
243
    public function getAudit($entity, $id)
244
    {
245
        $connection = $this->entityManager->getConnection();
246
247
        /**
248
         * @var \Doctrine\DBAL\Query\QueryBuilder
249
         */
250
        $queryBuilder = $connection->createQueryBuilder();
251
        $queryBuilder
252
            ->select('*')
253
            ->from($this->getEntityAuditTableName($entity))
254
            ->where('id = :id')
255
            ->setParameter('id', $id);
256
257
        if (null !== $this->filter) {
258
            $queryBuilder
259
                ->andWhere('type = :filter')
260
                ->setParameter('filter', $this->filter);
261
        }
262
263
        /** @var Statement $statement */
264
        $statement = $queryBuilder->execute();
265
        $statement->setFetchMode(\PDO::FETCH_CLASS, AuditEntry::class);
266
267
        return $statement->fetchAll();
268
    }
269
270
    private function getClassMetadata($entity): ClassMetadata
271
    {
272
        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...
273
            ->entityManager
274
            ->getClassMetadata($entity);
275
    }
276
277
    /**
278
     * Returns the table name of $entity.
279
     *
280
     * @param object|string $entity
281
     *
282
     * @return string
283
     */
284
    public function getEntityTableName($entity): string
285
    {
286
        return $this->getClassMetadata($entity)
287
            ->getTableName();
288
    }
289
290
    /**
291
     * Returns the audit table name for $entity.
292
     *
293
     * @param $entity
294
     *
295
     * @return string
296
     */
297
    public function getEntityAuditTableName($entity): string
298
    {
299
        $entityName = \is_string($entity) ? $entity : \get_class($entity);
300
        $schema = $this->getClassMetadata($entityName)->getSchemaName() ? $this->getClassMetadata($entityName)->getSchemaName().'.' : '';
301
302
        return sprintf('%s%s%s%s', $schema, $this->configuration->getTablePrefix(), $this->getEntityTableName($entityName), $this->configuration->getTableSuffix());
303
    }
304
305
    /**
306
     * @return EntityManagerInterface
307
     */
308
    private function selectStorage(): EntityManagerInterface
309
    {
310
        return $this->configuration->getCustomStorageEntityManager() ?? $this->entityManager;
311
    }
312
}
313