Passed
Push — master ( 356dd9...ecadea )
by Damien
02:16
created

AuditHelper::value()   B

Complexity

Conditions 8
Paths 8

Size

Total Lines 29
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

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