AuditTrait   A
last analyzed

Complexity

Total Complexity 32

Size/Duplication

Total Lines 220
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 96
c 1
b 0
f 0
dl 0
loc 220
rs 9.84
wmc 32

5 Methods

Rating   Name   Duplication   Size   Complexity  
B summarize() 0 35 8
B diff() 0 38 9
A id() 0 29 2
B value() 0 29 8
A blame() 0 27 5
1
<?php
2
3
namespace DH\DoctrineAuditBundle\Transaction;
4
5
use DH\DoctrineAuditBundle\Helper\DoctrineHelper;
6
use DH\DoctrineAuditBundle\User\UserInterface;
7
use Doctrine\Common\Persistence\Proxy;
8
use Doctrine\DBAL\Types\Type;
9
use Doctrine\ORM\EntityManagerInterface;
10
use Doctrine\ORM\Mapping\ClassMetadata;
11
use Doctrine\ORM\PersistentCollection;
12
13
trait AuditTrait
14
{
15
    /**
16
     * Returns the primary key value of an entity.
17
     *
18
     * @param EntityManagerInterface $em
19
     * @param object                 $entity
20
     *
21
     * @throws \Doctrine\DBAL\DBALException
22
     * @throws \Doctrine\ORM\Mapping\MappingException
23
     *
24
     * @return mixed
25
     */
26
    private function id(EntityManagerInterface $em, $entity)
27
    {
28
        /** @var ClassMetadata $meta */
29
        $meta = $em->getClassMetadata(DoctrineHelper::getRealClassName($entity));
30
        $pk = $meta->getSingleIdentifierFieldName();
31
32
        if (isset($meta->fieldMappings[$pk])) {
33
            $type = Type::getType($meta->fieldMappings[$pk]['type']);
34
35
            return $this->value($em, $type, $meta->getReflectionProperty($pk)->getValue($entity));
36
        }
37
38
        /**
39
         * Primary key is not part of fieldMapping.
40
         *
41
         * @see https://github.com/DamienHarper/DoctrineAuditBundle/issues/40
42
         * @see https://www.doctrine-project.org/projects/doctrine-orm/en/latest/tutorials/composite-primary-keys.html#identity-through-foreign-entities
43
         * We try to get it from associationMapping (will throw a MappingException if not available)
44
         */
45
        $targetEntity = $meta->getReflectionProperty($pk)->getValue($entity);
46
47
        $mapping = $meta->getAssociationMapping($pk);
48
49
        /** @var ClassMetadata $meta */
50
        $meta = $em->getClassMetadata($mapping['targetEntity']);
51
        $pk = $meta->getSingleIdentifierFieldName();
52
        $type = Type::getType($meta->fieldMappings[$pk]['type']);
53
54
        return $this->value($em, $type, $meta->getReflectionProperty($pk)->getValue($targetEntity));
55
    }
56
57
    /**
58
     * Type converts the input value and returns it.
59
     *
60
     * @param EntityManagerInterface $em
61
     * @param Type                   $type
62
     * @param mixed                  $value
63
     *
64
     * @throws \Doctrine\DBAL\DBALException
65
     *
66
     * @return mixed
67
     */
68
    private function value(EntityManagerInterface $em, Type $type, $value)
69
    {
70
        if (null === $value) {
71
            return null;
72
        }
73
74
        $platform = $em->getConnection()->getDatabasePlatform();
75
76
        switch ($type->getName()) {
77
            case DoctrineHelper::getDoctrineType('BIGINT'):
78
                $convertedValue = (string) $value;
79
80
                break;
81
            case DoctrineHelper::getDoctrineType('INTEGER'):
82
            case DoctrineHelper::getDoctrineType('SMALLINT'):
83
                $convertedValue = (int) $value;
84
85
                break;
86
            case DoctrineHelper::getDoctrineType('DECIMAL'):
87
            case DoctrineHelper::getDoctrineType('FLOAT'):
88
            case DoctrineHelper::getDoctrineType('BOOLEAN'):
89
                $convertedValue = $type->convertToPHPValue($value, $platform);
90
91
                break;
92
            default:
93
                $convertedValue = $type->convertToDatabaseValue($value, $platform);
94
        }
95
96
        return $convertedValue;
97
    }
98
99
    /**
100
     * Computes a usable diff.
101
     *
102
     * @param EntityManagerInterface $em
103
     * @param object                 $entity
104
     * @param array                  $ch
105
     *
106
     * @throws \Doctrine\DBAL\DBALException
107
     * @throws \Doctrine\ORM\Mapping\MappingException
108
     *
109
     * @return array
110
     */
111
    private function diff(EntityManagerInterface $em, $entity, array $ch): array
112
    {
113
        /** @var ClassMetadata $meta */
114
        $meta = $em->getClassMetadata(DoctrineHelper::getRealClassName($entity));
115
        $diff = [];
116
117
        foreach ($ch as $fieldName => list($old, $new)) {
118
            $o = null;
119
            $n = null;
120
121
            if (
122
                $meta->hasField($fieldName) &&
123
                !isset($meta->embeddedClasses[$fieldName]) &&
124
                $this->configuration->isAuditedField($entity, $fieldName)
125
            ) {
126
                $mapping = $meta->fieldMappings[$fieldName];
127
                $type = Type::getType($mapping['type']);
128
                $o = $this->value($em, $type, $old);
129
                $n = $this->value($em, $type, $new);
130
            } elseif (
131
                $meta->hasAssociation($fieldName) &&
132
                $meta->isSingleValuedAssociation($fieldName) &&
133
                $this->configuration->isAuditedField($entity, $fieldName)
134
            ) {
135
                $o = $this->summarize($em, $old);
136
                $n = $this->summarize($em, $new);
137
            }
138
139
            if ($o !== $n) {
140
                $diff[$fieldName] = [
141
                    'old' => $o,
142
                    'new' => $n,
143
                ];
144
            }
145
        }
146
        ksort($diff);
147
148
        return $diff;
149
    }
150
151
    /**
152
     * Returns an array describing an entity.
153
     *
154
     * @param EntityManagerInterface $em
155
     * @param object                 $entity
156
     * @param mixed                  $id
157
     *
158
     * @throws \Doctrine\DBAL\DBALException
159
     * @throws \Doctrine\ORM\Mapping\MappingException
160
     *
161
     * @return array
162
     */
163
    private function summarize(EntityManagerInterface $em, $entity = null, $id = null): ?array
164
    {
165
        if (null === $entity) {
166
            return null;
167
        }
168
169
        // ensure that proxies are initialized
170
        if (
171
            ($entity instanceof Proxy && !$entity->__isInitialized()) ||
172
            ($entity instanceof PersistentCollection && !$entity->isInitialized())
173
        ) {
174
            $em->initializeObject($entity);
175
        }
176
177
178
        /** @var ClassMetadata $meta */
179
        $meta = $em->getClassMetadata(DoctrineHelper::getRealClassName($entity));
180
        $pkName = $meta->getSingleIdentifierFieldName();
181
        $pkValue = $id ?? $this->id($em, $entity);
182
        // An added guard for proxies that fail to initialize.
183
        if (null === $pkValue) {
184
            return null;
185
        }
186
187
        if (method_exists($entity, '__toString')) {
188
            $label = (string) $entity;
189
        } else {
190
            $label = DoctrineHelper::getRealClassName($entity).'#'.$pkValue;
191
        }
192
193
        return [
194
            'label' => $label,
195
            'class' => $meta->name,
196
            'table' => $meta->getTableName(),
197
            $pkName => $pkValue,
198
        ];
199
    }
200
201
    /**
202
     * Blames an audit operation.
203
     *
204
     * @return array
205
     */
206
    private function blame(): array
207
    {
208
        $user_id = null;
209
        $username = null;
210
        $client_ip = null;
211
        $user_fqdn = null;
212
        $user_firewall = null;
213
214
        $request = $this->configuration->getRequestStack()->getCurrentRequest();
215
        if (null !== $request) {
216
            $client_ip = $request->getClientIp();
217
            $user_firewall = null === $this->configuration->getFirewallMap()->getFirewallConfig($request) ? null : $this->configuration->getFirewallMap()->getFirewallConfig($request)->getName();
218
        }
219
220
        $user = null === $this->configuration->getUserProvider() ? null : $this->configuration->getUserProvider()->getUser();
221
        if ($user instanceof UserInterface) {
222
            $user_id = $user->getId();
223
            $username = $user->getUsername();
224
            $user_fqdn = DoctrineHelper::getRealClassName($user);
225
        }
226
227
        return [
228
            'user_id' => $user_id,
229
            'username' => $username,
230
            'client_ip' => $client_ip,
231
            'user_fqdn' => $user_fqdn,
232
            'user_firewall' => $user_firewall,
233
        ];
234
    }
235
}
236