Passed
Push — master ( 8001cb...0d19a8 )
by Damien
03:58
created

Reader::getEntityTableName()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 2
dl 0
loc 6
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
1
<?php
2
3
namespace DH\Auditor\Provider\Doctrine\Persistence\Reader;
4
5
use ArrayIterator;
6
use DH\Auditor\Exception\AccessDeniedException;
7
use DH\Auditor\Exception\InvalidArgumentException;
8
use DH\Auditor\Provider\Doctrine\Auditing\Annotation\Security;
9
use DH\Auditor\Provider\Doctrine\Configuration;
10
use DH\Auditor\Provider\Doctrine\DoctrineProvider;
11
use DH\Auditor\Provider\Doctrine\Service\StorageService;
12
use Doctrine\ORM\Mapping\ClassMetadata as ORMMetadata;
13
use Symfony\Component\OptionsResolver\OptionsResolver;
14
15
class Reader
16
{
17
    public const PAGE_SIZE = 50;
18
19
    /**
20
     * @var DoctrineProvider
21
     */
22
    private $provider;
23
24
    /**
25
     * AuditReader constructor.
26
     */
27
    public function __construct(DoctrineProvider $provider)
28
    {
29
        $this->provider = $provider;
30
    }
31
32
    public function getProvider(): DoctrineProvider
33
    {
34
        return $this->provider;
35
    }
36
37
    public function createQuery(string $entity, array $options = []): Query
38
    {
39
        $this->checkAuditable($entity);
40
        $this->checkRoles($entity, Security::VIEW_SCOPE);
41
42
        $resolver = new OptionsResolver();
43
        $this->configureOptions($resolver);
44
        $config = $resolver->resolve($options);
45
46
        /** @var StorageService $storageService */
47
        $storageService = $this->provider->getStorageServiceForEntity($entity);
48
        $entityManager = $storageService->getEntityManager();
49
50
        $query = new Query($this->getEntityAuditTableName($entity), $entityManager->getConnection());
51
        $query
52
            ->addOrderBy(Query::CREATED_AT, 'DESC')
53
            ->addOrderBy(Query::ID, 'DESC')
54
        ;
55
56
        if (null !== $config['type']) {
57
            $query->addFilter(Query::TYPE, $config['type']);
58
        }
59
60
        if (null !== $config['object_id']) {
61
            $query->addFilter(Query::OBJECT_ID, $config['object_id']);
62
        }
63
64
        if (null !== $config['transaction_hash']) {
65
            $query->addFilter(Query::TRANSACTION_HASH, $config['transaction_hash']);
66
        }
67
68
        if (null !== $config['page'] && null !== $config['page_size']) {
69
            $query->limit($config['page_size'], ($config['page'] - 1) * $config['page_size']);
70
        }
71
72
        $metadata = $entityManager->getClassMetadata($entity);
73
        if (
74
            $config['strict'] &&
75
            $metadata instanceof ORMMetadata &&
76
            ORMMetadata::INHERITANCE_TYPE_SINGLE_TABLE === $metadata->inheritanceType
77
        ) {
78
            $query->addFilter(Query::DISCRIMINATOR, $entity);
79
        }
80
81
        return $query;
82
    }
83
84
    public function configureOptions(OptionsResolver $resolver): void
85
    {
86
        // https://symfony.com/doc/current/components/options_resolver.html
87
        $resolver
88
            ->setDefaults([
89
                'type' => null,
90
                'object_id' => null,
91
                'transaction_hash' => null,
92
                'page' => 1,
93
                'page_size' => self::PAGE_SIZE,
94
                'strict' => true,
95
            ])
96
            ->setAllowedTypes('type', ['null', 'string', 'array'])
97
            ->setAllowedTypes('object_id', ['null', 'int', 'string', 'array'])
98
            ->setAllowedTypes('transaction_hash', ['null', 'string', 'array'])
99
            ->setAllowedTypes('page', ['null', 'int'])
100
            ->setAllowedTypes('page_size', ['null', 'int'])
101
            ->setAllowedTypes('strict', ['null', 'bool'])
102
            ->setAllowedValues('page', static function ($value) {
103
                return null === $value || $value >= 1;
104
            })
105
            ->setAllowedValues('page_size', static function ($value) {
106
                return null === $value || $value >= 1;
107
            })
108
        ;
109
    }
110
111
    /**
112
     * Returns an array of all audited entries/operations for a given transaction hash
113
     * indexed by entity FQCN.
114
     */
115
    public function getAuditsByTransactionHash(string $transactionHash): array
116
    {
117
        /** @var Configuration $configuration */
118
        $configuration = $this->provider->getConfiguration();
119
        $results = [];
120
121
        $entities = $configuration->getEntities();
122
        foreach ($entities as $entity => $tablename) {
123
            try {
124
                $audits = $this->createQuery($entity, ['transaction_hash' => $transactionHash])->execute();
125
                if (\count($audits) > 0) {
126
                    $results[$entity] = $audits;
127
                }
128
            } catch (AccessDeniedException $e) {
129
                // acces denied
130
            }
131
        }
132
133
        return $results;
134
    }
135
136
    public function paginate(Query $query, int $page = 1, int $pageSize = self::PAGE_SIZE): array
137
    {
138
        $numResults = $query->count();
139
        $currentPage = $page < 1 ? 1 : $page;
140
        $hasPreviousPage = $currentPage > 1;
141
        $hasNextPage = ($currentPage * $pageSize) < $numResults;
142
143
        return [
144
            'results' => new ArrayIterator($query->execute()),
145
            'currentPage' => $currentPage,
146
            'hasPreviousPage' => $hasPreviousPage,
147
            'hasNextPage' => $hasNextPage,
148
            'previousPage' => $hasPreviousPage ? $currentPage - 1 : null,
149
            'nextPage' => $hasNextPage ? $currentPage + 1 : null,
150
            'numPages' => (int) ceil($numResults / $pageSize),
151
            'haveToPaginate' => $numResults > $pageSize,
152
        ];
153
    }
154
155
    /**
156
     * Returns the table name of $entity.
157
     */
158
    public function getEntityTableName(string $entity): string
159
    {
160
        /** @var StorageService $storageService */
161
        $storageService = $this->provider->getStorageServiceForEntity($entity);
162
163
        return $storageService->getEntityManager()->getClassMetadata($entity)->getTableName();
164
    }
165
166
    /**
167
     * Returns the audit table name for $entity.
168
     */
169
    public function getEntityAuditTableName(string $entity): string
170
    {
171
        /** @var Configuration $configuration */
172
        $configuration = $this->provider->getConfiguration();
173
174
        /** @var StorageService $storageService */
175
        $storageService = $this->provider->getStorageServiceForEntity($entity);
176
        $entityManager = $storageService->getEntityManager();
177
        $schema = '';
178
        if ($entityManager->getClassMetadata($entity)->getSchemaName()) {
179
            $schema = $entityManager->getClassMetadata($entity)->getSchemaName().'.';
180
        }
181
182
        return sprintf(
183
            '%s%s%s%s',
184
            $schema,
185
            $configuration->getTablePrefix(),
186
            $this->getEntityTableName($entity),
187
            $configuration->getTableSuffix()
188
        );
189
    }
190
191
    /**
192
     * Throws an InvalidArgumentException if given entity is not auditable.
193
     *
194
     * @throws InvalidArgumentException
195
     */
196
    private function checkAuditable(string $entity): void
197
    {
198
        if (!$this->provider->isAuditable($entity)) {
199
            throw new InvalidArgumentException('Entity '.$entity.' is not auditable.');
200
        }
201
    }
202
203
    /**
204
     * Throws an AccessDeniedException if user not is granted to access audits for the given entity.
205
     *
206
     * @throws AccessDeniedException
207
     */
208
    private function checkRoles(string $entity, string $scope): void
209
    {
210
        $roleChecker = $this->provider->getConfiguration()->getRoleChecker();
0 ignored issues
show
Bug introduced by
The method getRoleChecker() does not exist on DH\Auditor\Provider\ConfigurationInterface. Since it exists in all sub-types, consider adding an abstract or default implementation to DH\Auditor\Provider\ConfigurationInterface. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

210
        $roleChecker = $this->provider->getConfiguration()->/** @scrutinizer ignore-call */ getRoleChecker();
Loading history...
211
212
        if (null === $roleChecker || $roleChecker->call($this, $entity, $scope)) {
213
            return;
214
        }
215
216
        // access denied
217
        throw new AccessDeniedException('You are not allowed to access audits of "'.$entity.'" entity.');
218
    }
219
}
220