Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
78.18% covered (warning)
78.18%
43 / 55
40.00% covered (danger)
40.00%
2 / 5
CRAP
0.00% covered (danger)
0.00%
0 / 1
EntitySnapshot
78.18% covered (warning)
78.18%
43 / 55
40.00% covered (danger)
40.00%
2 / 5
20.00
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 applies
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 normalize
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
20
 createFrom
100.00% covered (success)
100.00%
15 / 15
100.00% covered (success)
100.00%
1 / 1
3
 createFromField
90.00% covered (success)
90.00%
27 / 30
0.00% covered (danger)
0.00%
0 / 1
8.06
1<?php
2namespace Apie\Common\Other;
3
4use Apie\Common\Enums\AccessDenied;
5use Apie\Core\Attributes\AnyApplies;
6use Apie\Core\Attributes\ApieContextAttribute;
7use Apie\Core\Attributes\RuntimeCheck;
8use Apie\Core\Context\ApieContext;
9use Apie\Core\Entities\EntityInterface;
10use Apie\Core\FileStorage\StoredFile;
11use Apie\Core\Metadata\Fields\FieldInterface;
12use Apie\Core\Metadata\GetterInterface;
13use Apie\Core\Metadata\MetadataFactory;
14use Apie\Core\ValueObjects\Interfaces\ValueObjectInterface;
15use Apie\Serializer\Context\ApieSerializerContext;
16use Apie\Serializer\Lists\SerializedHashmap;
17use Apie\TypeConverter\ReflectionTypeFactory;
18use Psr\Http\Message\UploadedFileInterface;
19use ReflectionAttribute;
20use ReflectionClass;
21use SensitiveParameter;
22
23/**
24 * Snapshot for audit log. Represents an object. It stored the field data and permission metadata.
25 */
26final class EntitySnapshot implements EntitySnapshotInstance
27{
28    public function __construct(
29        private readonly EntitySnapshotFieldMap $mapping,
30        private readonly ApieContextAttribute $context
31    ) {
32    }
33
34    public function applies(ApieSerializerContext $apieSerializerContext): bool
35    {
36        return $this->context->applies($apieSerializerContext->getContext());
37    }
38
39    public function normalize(ApieSerializerContext $apieSerializerContext): SerializedHashmap|AccessDenied
40    {
41        if (!$this->applies($apieSerializerContext)) {
42            return AccessDenied::Denied;
43        }
44        $map = [];
45        foreach ($this->mapping as $fieldName => $fieldData) {
46            $subcontext = $apieSerializerContext->visit($fieldName);
47            if ($fieldData->applies($subcontext)) {
48                $map[$fieldName] = $fieldData->normalize($subcontext);
49            }
50        }
51        return new SerializedHashmap($map);
52    }
53
54    public static function createFrom(EntityInterface $entity): self
55    {
56        $refl = new ReflectionClass($entity);
57        $apieContext = new ApieContext([]);
58        $metadata = MetadataFactory::getResultMetadata($refl, $apieContext);
59        $attributes = array_map(
60            fn (ReflectionAttribute $attr) => $attr->newInstance(),
61            $refl->getAttributes(RuntimeCheck::class)
62        );
63        $data = [];
64        foreach ($metadata->getHashmap()->filterOnContext($apieContext, getters: true) as $name => $field) {
65            if ($field->isField()) {
66                $data[$name] = self::createFromField($entity, $apieContext, $field);
67            }
68        }
69        return new EntitySnapshot(
70            new EntitySnapshotFieldMap($data),
71            new AnyApplies(...$attributes)
72        );
73    }
74
75    public static function createFromField(object $object, ApieContext $apieContext, GetterInterface&FieldInterface $fieldData): EntitySnapshot|EntitySnapshotFile|EntitySnapshotLeaf|EntitySnapshotHidden
76    {
77        $value = $fieldData->getValue($object, $apieContext);
78        if ($value instanceof ValueObjectInterface) {
79            $value = $value->toNative();
80        }
81        
82        $context =  new AnyApplies(...$fieldData->getAttributes(RuntimeCheck::class));
83        if ($fieldData->getAttributes(SensitiveParameter::class)) {
84            return new EntitySnapshotHidden($context);
85        }
86        if ($value instanceof UploadedFileInterface) {
87            $storagePath = null;
88            $originalFilename = $value->getClientFilename();
89            if ($value instanceof StoredFile) {
90                $storagePath = $value->getStoragePath();
91            }
92            return new EntitySnapshotFile(
93                $storagePath,
94                $originalFilename,
95                new AnyApplies(...$fieldData->getAttributes(RuntimeCheck::class))
96            );
97        }
98        if (is_scalar($value)) {
99            return new EntitySnapshotLeaf($value, $context);
100        }
101        
102        $metadata = MetadataFactory::getResultMetadata(
103            ReflectionTypeFactory::createReflectionType(get_debug_type($value)),
104            $apieContext
105        );
106    
107        $data = [];
108        foreach ($metadata->getHashmap()->filterOnContext($apieContext, getters: true) as $name => $field) {
109            if ($field->isField()) {
110                $data[$name] = self::createFromField($value, $apieContext, $field);
111            }
112        }
113        return new EntitySnapshot(
114            new EntitySnapshotFieldMap($data),
115            $context
116        );
117    }
118}