Passed
Pull Request — master (#188)
by Damien
03:02
created

AuditTrait   A

Complexity

Total Complexity 28

Size/Duplication

Total Lines 212
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 93
c 1
b 0
f 0
dl 0
loc 212
rs 10
wmc 28

5 Methods

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