Passed
Pull Request — master (#63)
by Damien
03:19
created

AuditHelper::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 1
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace DH\DoctrineAuditBundle\Helper;
4
5
use DH\DoctrineAuditBundle\AuditConfiguration;
6
use DH\DoctrineAuditBundle\User\UserInterface;
7
use Doctrine\DBAL\Types\Type;
8
use Doctrine\ORM\EntityManager;
9
10
class AuditHelper
11
{
12
    /**
13
     * @var \DH\DoctrineAuditBundle\AuditConfiguration
14
     */
15
    private $configuration;
16
17
    /**
18
     * @param AuditConfiguration $configuration
19
     */
20
    public function __construct(AuditConfiguration $configuration)
21
    {
22
        $this->configuration = $configuration;
23
    }
24
25
    /**
26
     * @return \DH\DoctrineAuditBundle\AuditConfiguration
27
     */
28
    public function getConfiguration(): AuditConfiguration
29
    {
30
        return $this->configuration;
31
    }
32
33
    /**
34
     * Returns the primary key value of an entity.
35
     *
36
     * @param EntityManager $em
37
     * @param object        $entity
38
     *
39
     * @throws \Doctrine\DBAL\DBALException
40
     * @throws \Doctrine\ORM\Mapping\MappingException
41
     *
42
     * @return mixed
43
     */
44
    public function id(EntityManager $em, $entity)
45
    {
46
        $meta = $em->getClassMetadata(\get_class($entity));
47
        $pk = $meta->getSingleIdentifierFieldName();
48
49
        if (isset($meta->fieldMappings[$pk])) {
50
            $type = Type::getType($meta->fieldMappings[$pk]['type']);
51
52
            return $this->value($em, $type, $meta->getReflectionProperty($pk)->getValue($entity));
53
        }
54
55
        // Primary key is not part of fieldMapping
56
        // @see https://github.com/DamienHarper/DoctrineAuditBundle/issues/40
57
        // @see https://www.doctrine-project.org/projects/doctrine-orm/en/latest/tutorials/composite-primary-keys.html#identity-through-foreign-entities
58
        // We try to get it from associationMapping (will throw a MappingException if not available)
59
        $targetEntity = $meta->getReflectionProperty($pk)->getValue($entity);
60
61
        $mapping = $meta->getAssociationMapping($pk);
62
        $meta = $em->getClassMetadata($mapping['targetEntity']);
63
        $pk = $meta->getSingleIdentifierFieldName();
64
        $type = Type::getType($meta->fieldMappings[$pk]['type']);
65
66
        return $this->value($em, $type, $meta->getReflectionProperty($pk)->getValue($targetEntity));
67
    }
68
69
    /**
70
     * Computes a usable diff.
71
     *
72
     * @param EntityManager $em
73
     * @param object        $entity
74
     * @param array         $ch
75
     *
76
     * @throws \Doctrine\DBAL\DBALException
77
     * @throws \Doctrine\ORM\Mapping\MappingException
78
     *
79
     * @return array
80
     */
81
    public function diff(EntityManager $em, $entity, array $ch): array
82
    {
83
        $meta = $em->getClassMetadata(\get_class($entity));
84
        $diff = [];
85
86
        foreach ($ch as $fieldName => list($old, $new)) {
87
            $o = null;
88
            $n = null;
89
90
            if (
91
                $meta->hasField($fieldName) &&
92
                !isset($meta->embeddedClasses[$fieldName]) &&
93
                $this->configuration->isAuditedField($entity, $fieldName)
94
            ) {
95
                $mapping = $meta->fieldMappings[$fieldName];
96
                $type = Type::getType($mapping['type']);
97
                $o = $this->value($em, $type, $old);
98
                $n = $this->value($em, $type, $new);
99
            } elseif (
100
                $meta->hasAssociation($fieldName) &&
101
                $meta->isSingleValuedAssociation($fieldName) &&
102
                $this->configuration->isAuditedField($entity, $fieldName)
103
            ) {
104
                $o = $this->summarize($em, $old);
105
                $n = $this->summarize($em, $new);
106
            }
107
108
            if ($o !== $n) {
109
                $diff[$fieldName] = [
110
                    'old' => $o,
111
                    'new' => $n,
112
                ];
113
            }
114
        }
115
116
        return $diff;
117
    }
118
119
    /**
120
     * Type converts the input value and returns it.
121
     *
122
     * @param EntityManager $em
123
     * @param Type          $type
124
     * @param mixed         $value
125
     *
126
     * @throws \Doctrine\DBAL\DBALException
127
     *
128
     * @return mixed
129
     */
130
    private function value(EntityManager $em, Type $type, $value)
131
    {
132
        if (null === $value) {
133
            return null;
134
        }
135
136
        $platform = $em->getConnection()->getDatabasePlatform();
137
138
        switch ($type->getName()) {
139
            case Type::DECIMAL:
140
            case Type::BIGINT:
141
                $convertedValue = (string) $value;
142
143
                break;
144
            case Type::INTEGER:
145
            case Type::SMALLINT:
146
                $convertedValue = (int) $value;
147
148
                break;
149
            case Type::FLOAT:
150
            case Type::BOOLEAN:
151
                $convertedValue = $type->convertToPHPValue($value, $platform);
152
153
                break;
154
            default:
155
                $convertedValue = $type->convertToDatabaseValue($value, $platform);
156
        }
157
158
        return $convertedValue;
159
    }
160
161
    /**
162
     * Blames an audit operation.
163
     *
164
     * @return array
165
     */
166
    public function blame(): array
167
    {
168
        $user_id = null;
169
        $username = null;
170
        $client_ip = null;
171
        $user_fqdn = null;
172
        $user_firewall = null;
173
174
        $request = $this->configuration->getRequestStack()->getCurrentRequest();
175
        if (null !== $request) {
176
            $client_ip = $request->getClientIp();
177
            $user_firewall = null === $this->configuration->getFirewallMap()->getFirewallConfig($request) ? null : $this->configuration->getFirewallMap()->getFirewallConfig($request)->getName();
178
        }
179
180
        $user = $this->configuration->getUserProvider()->getUser();
181
        if ($user instanceof UserInterface) {
182
            $user_id = $user->getId();
183
            $username = $user->getUsername();
184
            $user_fqdn = \get_class($user);
185
        }
186
187
        return [
188
            'user_id' => $user_id,
189
            'username' => $username,
190
            'client_ip' => $client_ip,
191
            'user_fqdn' => $user_fqdn,
192
            'user_firewall' => $user_firewall,
193
        ];
194
    }
195
196
    /**
197
     * Returns an array describing an entity.
198
     *
199
     * @param EntityManager $em
200
     * @param object        $entity
201
     * @param mixed         $id
202
     *
203
     * @throws \Doctrine\DBAL\DBALException
204
     * @throws \Doctrine\ORM\Mapping\MappingException
205
     *
206
     * @return array
207
     */
208
    public function summarize(EntityManager $em, $entity = null, $id = null): ?array
209
    {
210
        if (null === $entity) {
211
            return null;
212
        }
213
214
        $em->getUnitOfWork()->initializeObject($entity); // ensure that proxies are initialized
215
        $meta = $em->getClassMetadata(\get_class($entity));
216
        $pkName = $meta->getSingleIdentifierFieldName();
217
        $pkValue = $id ?? $this->id($em, $entity);
218
219
        if (method_exists($entity, '__toString')) {
220
            $label = (string) $entity;
221
        } else {
222
            $label = \get_class($entity).'#'.$pkValue;
223
        }
224
225
        return [
226
            'label' => $label,
227
            'class' => $meta->name,
228
            'table' => $meta->getTableName(),
229
            $pkName => $pkValue,
230
        ];
231
    }
232
233
    /**
234
     * Return columns of audit tables.
235
     *
236
     * @return array
237
     */
238
    public function getAuditTableColumns(): array
239
    {
240
        $columns = [
241
            'id' => [
242
                'type' => Type::INTEGER,
243
                'options' => [
244
                    'autoincrement' => true,
245
                    'unsigned' => true,
246
                ],
247
            ],
248
            'type' => [
249
                'type' => Type::STRING,
250
                'options' => [
251
                    'notnull' => true,
252
                    'length' => 10,
253
                ],
254
            ],
255
            'object_id' => [
256
                'type' => Type::STRING,
257
                'options' => [
258
                    'notnull' => true,
259
                ],
260
            ],
261
            'diffs' => [
262
                'type' => Type::JSON_ARRAY,
263
                'options' => [
264
                    'default' => null,
265
                    'notnull' => false,
266
                ],
267
            ],
268
            'blame_id' => [
269
                'type' => Type::INTEGER,
270
                'options' => [
271
                    'default' => null,
272
                    'notnull' => false,
273
                    'unsigned' => true,
274
                ],
275
            ],
276
            'blame_user' => [
277
                'type' => Type::STRING,
278
                'options' => [
279
                    'default' => null,
280
                    'notnull' => false,
281
                    'length' => 255,
282
                ],
283
            ],
284
            'blame_user_fqdn' => [
285
                'type' => Type::STRING,
286
                'options' => [
287
                    'default' => null,
288
                    'notnull' => false,
289
                    'length' => 255,
290
                ],
291
            ],
292
            'blame_user_firewall' => [
293
                'type' => Type::STRING,
294
                'options' => [
295
                    'default' => null,
296
                    'notnull' => false,
297
                    'length' => 100,
298
                ],
299
            ],
300
            'ip' => [
301
                'type' => Type::STRING,
302
                'options' => [
303
                    'default' => null,
304
                    'notnull' => false,
305
                    'length' => 45,
306
                ],
307
            ],
308
            'created_at' => [
309
                'type' => Type::DATETIME,
310
                'options' => [
311
                    'notnull' => true,
312
                ],
313
            ],
314
        ];
315
316
        return $columns;
317
    }
318
319
    public function getAuditTableIndices(string $tablename): array
320
    {
321
        $indices = [
322
            'id' => [
323
                'type' => 'primary',
324
            ],
325
            'type' => [
326
                'type' => 'index',
327
                'name' => 'type_'.md5($tablename).'_idx',
328
            ],
329
            'object_id' => [
330
                'type' => 'index',
331
                'name' => 'object_id_'.md5($tablename).'_idx',
332
            ],
333
            'blame_id' => [
334
                'type' => 'index',
335
                'name' => 'blame_id_'.md5($tablename).'_idx',
336
            ],
337
            'created_at' => [
338
                'type' => 'index',
339
                'name' => 'created_at_'.md5($tablename).'_idx',
340
            ],
341
        ];
342
343
        return $indices;
344
    }
345
346
    public static function paramToNamespace(string $entity): string
347
    {
348
        return str_replace('-', '\\', $entity);
349
    }
350
351
    public static function namespaceToParam(string $entity): string
352
    {
353
        return str_replace('\\', '-', $entity);
354
    }
355
}
356