Issues (104)

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->ignore) {
110
            return;
111
        }
112
        if ($this->authentication->isLogged() === false) {
113
            return;
114
        }
115
116
        $auditor = $this->auditor;
117
        $fieldIgnores = [
118
            ...$ignoreFields,
119
            ...['password', 'created_at', 'updated_at'],
120
        ];
121
122
        if ($type & self::CREATE) {
123
            $mapper->on('save', function (
124
                Entity $entity,
125
                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

125
                /** @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...
126
            ) use (
127
                $auditor,
128
                $fieldIgnores
129
            ) {
130
                $data = $entity->jsonSerialize();
131
                $entityData = Arr::except($data, $fieldIgnores);
132
                $className = Php::getShortClassName($entity);
133
134
                $auditor->setDetail(sprintf(
135
                    'Create of "%s" %s',
136
                    $className,
137
                    Str::stringify($entityData)
138
                ))
139
                ->setEvent(EventType::CREATE)
140
                ->save();
141
            });
142
        }
143
144
        if ($type & self::UPDATE) {
145
            $mapper->on('update', function (
146
                Entity $entity,
147
                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

147
                /** @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...
148
            ) use (
149
                $auditor,
150
                $fieldIgnores
151
            ) {
152
                $data = $entity->jsonSerialize();
153
                $entityData = Arr::except($data, $fieldIgnores);
154
                $className = Php::getShortClassName($entity);
155
156
                $auditor->setDetail(sprintf(
157
                    'Update of "%s" %s',
158
                    $className,
159
                    Str::stringify($entityData)
160
                ))
161
                ->setEvent(EventType::UPDATE)
162
                ->save();
163
            });
164
        }
165
166
        if ($type & self::DELETE) {
167
            $mapper->on('delete', function (
168
                Entity $entity,
169
                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

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