Issues (96)

src/Helper/EntityHelper.php (3 issues)

Severity
1
<?php
2
3
/**
4
 * Platine Framework
5
 *
6
 * Platine Framework is a lightweight, high-performance, simple and elegant
7
 * PHP Web framework
8
 *
9
 * This content is released under the MIT License (MIT)
10
 *
11
 * Copyright (c) 2020 Platine Framework
12
 *
13
 * Permission is hereby granted, free of charge, to any person obtaining a copy
14
 * of this software and associated documentation files (the "Software"), to deal
15
 * in the Software without restriction, including without limitation the rights
16
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17
 * copies of the Software, and to permit persons to whom the Software is
18
 * furnished to do so, subject to the following conditions:
19
 *
20
 * The above copyright notice and this permission notice shall be included in all
21
 * copies or substantial portions of the Software.
22
 *
23
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29
 * SOFTWARE.
30
 */
31
32
declare(strict_types=1);
33
34
namespace Platine\Framework\Helper;
35
36
use Platine\Framework\Audit\Auditor;
37
use Platine\Framework\Audit\Enum\EventType;
38
use Platine\Framework\Auth\AuthenticationInterface;
39
use Platine\Orm\Entity;
40
use Platine\Orm\Mapper\DataMapper;
41
use Platine\Orm\Mapper\EntityMapperInterface;
42
use Platine\Stdlib\Helper\Arr;
43
use Platine\Stdlib\Helper\Php;
44
use Platine\Stdlib\Helper\Str;
45
46
/**
47
 * @class EntityHelper
48
 * @package Platine\Framework\Helper
49
 * @template TEntity as Entity
50
 */
51
class EntityHelper
52
{
53
    public const NONE    = 0;
54
    public const DELETE    = 1;
55
    public const CREATE  = 2;
56
    public const UPDATE    = 4;
57
    public const ALL = 7;
58
59
60
    /**
61
     * Whether to ignore audit
62
     * @var bool
63
     */
64
    protected bool $ignore = false;
65
66
    /**
67
     * Create new instance
68
     * @param Auditor $auditor
69
     */
70
    public function __construct(
71
        protected Auditor $auditor,
72
        protected AuthenticationInterface $authentication,
73
    ) {
74
    }
75
76
    /**
77
     *
78
     * @return bool
79
     */
80
    public function isIgnore(): bool
81
    {
82
        return $this->ignore;
83
    }
84
85
    /**
86
     *
87
     * @param bool $ignore
88
     * @return $this
89
     */
90
    public function setIgnore(bool $ignore): self
91
    {
92
        $this->ignore = $ignore;
93
        return $this;
94
    }
95
96
97
    /**
98
     * Subscribe to entity event "save", "update", "delete"
99
     * @param EntityMapperInterface<TEntity> $mapper
100
     * @param int $type
101
     * @param array<string> $ignoreFields
102
     * @return void
103
     */
104
    public function subscribeEvents(
105
        EntityMapperInterface $mapper,
106
        int $type = self::ALL,
107
        array $ignoreFields = []
108
    ): void {
109
        if ($this->authentication->isLogged() === false) {
110
            return;
111
        }
112
113
        $auditor = $this->auditor;
114
        $ignore = $this->ignore;
115
        $fieldIgnores = [
116
            ...$ignoreFields,
117
            ...['password', 'created_at', 'updated_at'],
118
        ];
119
120
        if ($type & self::CREATE) {
121
            $mapper->on('save', function (
122
                Entity $entity,
123
                DataMapper $dm
0 ignored issues
show
The parameter $dm is not used and could be removed. ( Ignorable by Annotation )

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

123
                /** @scrutinizer ignore-unused */ DataMapper $dm

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
124
            ) use (
125
                $auditor,
126
                $ignore,
127
                $fieldIgnores
128
) {
129
                if ($ignore) {
130
                    return;
131
                }
132
133
                $data = $entity->jsonSerialize();
134
                $entityData = Arr::except($data, $fieldIgnores);
135
                $className = Php::getShortClassName($entity);
136
137
                $auditor->setDetail(sprintf(
138
                    'Create of "%s" %s',
139
                    $className,
140
                    Str::stringify($entityData)
141
                ))
142
                ->setEvent(EventType::CREATE)
143
                ->save();
144
            });
145
        }
146
147
        if ($type & self::UPDATE) {
148
            $mapper->on('update', function (
149
                Entity $entity,
150
                DataMapper $dm
0 ignored issues
show
The parameter $dm is not used and could be removed. ( Ignorable by Annotation )

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

150
                /** @scrutinizer ignore-unused */ DataMapper $dm

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
151
            ) use (
152
                $auditor,
153
                $ignore,
154
                $fieldIgnores
155
            ) {
156
                if ($ignore) {
157
                    return;
158
                }
159
160
                $data = $entity->jsonSerialize();
161
                $entityData = Arr::except($data, $fieldIgnores);
162
                $className = Php::getShortClassName($entity);
163
164
                $auditor->setDetail(sprintf(
165
                    'Update of %s %s',
166
                    $className,
167
                    Str::stringify($entityData)
168
                ))
169
                ->setEvent(EventType::UPDATE)
170
                ->save();
171
            });
172
        }
173
174
        if ($type & self::DELETE) {
175
            $mapper->on('delete', function (
176
                Entity $entity,
177
                DataMapper $dm
0 ignored issues
show
The parameter $dm is not used and could be removed. ( Ignorable by Annotation )

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

177
                /** @scrutinizer ignore-unused */ DataMapper $dm

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
178
            ) use (
179
                $auditor,
180
                $ignore,
181
                $fieldIgnores
182
            ) {
183
                if ($ignore) {
184
                    return;
185
                }
186
187
                $data = $entity->jsonSerialize();
188
                $entityData = Arr::except($data, $fieldIgnores);
189
                $className = Php::getShortClassName($entity);
190
191
                $auditor->setDetail(sprintf(
192
                    'Delete of %s %s',
193
                    $className,
194
                    Str::stringify($entityData)
195
                ))
196
                ->setEvent(EventType::DELETE)
197
                ->save();
198
            });
199
        }
200
    }
201
202
    /**
203
     * Return the changes between two entities
204
     * @param Entity|null $original
205
     * @param Entity|null $updated
206
     * @param array<string, array<string, mixed>> $fields
207
     * @return array<array{name: string, old:mixed, new:mixed}>
208
     */
209
    public static function getEntityChanges(
210
        ?Entity $original,
211
        ?Entity $updated,
212
        array $fields = []
213
    ): array {
214
        if ($original === null && $updated === null) {
215
            return [];
216
        }
217
218
        $oldColumnValue = null;
219
        $oldValue = null;
220
        $newColumnValue = null;
221
        $newValue = null;
222
223
        $results = [];
224
225
        // Closure to set entity relation data
226
        $setRelation = function (Entity $e, array|string $relation): string {
227
            $relation = Arr::wrap($relation);
228
            $text = [];
229
            foreach ($relation as $val) {
230
                $text[] =  $e->{$val};
231
            }
232
            return Arr::toString($text, ' ');
233
        };
234
235
        foreach ($fields as $field => $row) {
236
            $displayField = $row['display'] ?? $field;
237
            $displayText = $row['description'] ?? $field;
238
            $relation = $row['relation'] ?? $field;
239
            $enum = $row['enum'] ?? null;
240
            if ($original !== null) {
241
                $oldColumnValue = $original->{$field};
242
                $oldValue = $original->{$displayField};
243
            }
244
245
            if ($updated !== null) {
246
                $newColumnValue = $updated->{$field};
247
                $newValue = $updated->{$displayField};
248
            }
249
250
            if ($oldColumnValue !== $newColumnValue) {
251
                if ($oldValue instanceof Entity) {
252
                    $oldValue = $setRelation($oldValue, $relation);
253
                }
254
255
                if ($newValue instanceof Entity) {
256
                    $newValue = $setRelation($newValue, $relation);
257
                }
258
259
                if ($enum !== null) {
260
                    $oldValue = $enum[$oldValue] ?? '';
261
                    $newValue = $enum[$newValue] ?? '';
262
                }
263
264
                $results[] = [
265
                    'name' => $displayText,
266
                    'old' => $oldValue,
267
                    'new' => $newValue,
268
                ];
269
            }
270
        }
271
272
        return $results;
273
    }
274
275
    /**
276
     * Return the changes between the given attributes
277
     * @param array<int, array<string, mixed>> $original
278
     * @param array<int, array<string, mixed>> $updated
279
     * @return array<array{name: string, old:mixed, new:mixed}>
280
     */
281
    public static function getAttributeChanges(
282
        array $original,
283
        array $updated
284
    ): array {
285
        $originalKeys = array_keys($original);
286
        $updatedKeys = array_keys($updated);
287
        $insertKeys = array_diff($updatedKeys, $originalKeys);
288
        $results = [];
289
290
        foreach ($original as $attrId => $data) {
291
            $updateValue = $updated[$attrId]['value'] ?? null;
292
            if ($data['value'] !== $updateValue) {
293
                $results[] = [
294
                    'name' => $data['name'],
295
                    'old' => $data['value'],
296
                    'new' => $updateValue,
297
                ];
298
            }
299
        }
300
301
        foreach ($insertKeys as $attrId) {
302
            $value = $updated[$attrId]['value'] ?? null;
303
            $name = $updated[$attrId]['name'] ?? null;
304
305
            $results[] = [
306
                'name' => $name,
307
                'old' => null,
308
                'new' => $value,
309
            ];
310
        }
311
312
        return $results;
313
    }
314
}
315