Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
65.79% covered (warning)
65.79%
25 / 38
60.00% covered (warning)
60.00%
6 / 10
CRAP
0.00% covered (danger)
0.00%
0 / 1
AuditLog
65.79% covered (warning)
65.79%
25 / 38
60.00% covered (warning)
60.00%
6 / 10
38.66
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
4
 getDescription
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
12
 getEvent
75.00% covered (warning)
75.00%
3 / 4
0.00% covered (danger)
0.00%
0 / 1
3.14
 getCreatedBy
83.33% covered (warning)
83.33%
5 / 6
0.00% covered (danger)
0.00%
0 / 1
3.04
 getRequiredPermissions
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 getId
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getOrigin
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getReference
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getData
75.00% covered (warning)
75.00%
3 / 4
0.00% covered (danger)
0.00%
0 / 1
2.06
 getSnapshot
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2namespace Apie\Common\Other;
3
4use Apie\Common\Enums\AuditLogEvent;
5use Apie\Common\Other\Audit\AuditCreate;
6use Apie\Common\Other\Audit\AuditEvent;
7use Apie\Common\Other\Audit\AuditMethodCalled;
8use Apie\Common\Other\Audit\AuditMigration;
9use Apie\Common\Other\Audit\AuditModified;
10use Apie\Common\Other\Audit\AuditRead;
11use Apie\Common\Other\Audit\AuditRemoved;
12use Apie\Core\ApieLib;
13use Apie\Core\Attributes\AlwaysDisabled;
14use Apie\Core\Attributes\Context;
15use Apie\Core\Attributes\FakeCount;
16use Apie\Core\Attributes\Policy;
17use Apie\Core\Attributes\RuntimeCheck;
18use Apie\Core\Attributes\SearchFilterOption;
19use Apie\Core\Attributes\StaticCheck;
20use Apie\Core\Attributes\StoreOptions;
21use Apie\Core\Context\ApieContext;
22use Apie\Core\Entities\EntityInterface;
23use Apie\Core\Lists\PermissionList;
24use Apie\Core\Lists\StringSet;
25use Apie\Core\Permissions\RequiresPermissionsInterface;
26use Apie\Core\Translator\ApieTranslatorInterface;
27use Apie\Core\Translator\ValueObjects\TranslationString;
28use Apie\Core\ValueObjects\IdFriendlyEntityReference;
29use Apie\Core\ValueObjects\NonEmptyString;
30use Apie\Core\ValueObjects\Utils;
31use Apie\Serializer\ValueObjects\SerializedPhpObject;
32
33#[FakeCount(0)]
34#[StaticCheck(new Policy('staticCanViewAny', enabledOnMissingRule: true))]
35#[RuntimeCheck(
36    new Policy('canView', 'canViewAny'),
37    new ShouldApplyAuditablePermission()
38)]
39class AuditLog implements EntityInterface, RequiresPermissionsInterface
40{
41    private AuditLogIdentifier $id;
42
43    private StringSet $permissionSnapshot;
44
45    #[StoreOptions(alwaysMixedData: true)]
46    private EntitySnapshotInstance $snapshot;
47
48    // separate properties for optimization in the database
49    private readonly ?AuditCreate $createEvent;
50    private readonly ?AuditModified $modifiedEvent;
51    private readonly ?AuditRemoved $removedEvent;
52    private readonly ?AuditRead $readEvent;
53    private readonly ?AuditMethodCalled $methodCalledEvent;
54    private readonly ?AuditMigration $migrationEvent;
55
56    private const EVENT_MAPPING = [
57        AuditCreate::class => 'createEvent',
58        AuditModified::class => 'modifiedEvent',
59        AuditRemoved::class => 'removedEvent',
60        AuditRead::class => 'readEvent',
61        AuditMethodCalled::class=> 'methodCalledEvent',
62        AuditMigration::class => 'migrationEvent'
63    ];
64
65    #[StaticCheck(new AlwaysDisabled())]
66    public function __construct(
67        private readonly IdFriendlyEntityReference $reference,
68        private readonly SerializedPhpObject $serializedProperties,
69        AuditEvent $event,
70        private readonly AuditOrigin $origin,
71        #[Context('_ignored')]
72        private readonly ?SerializedPhpObject $createdBy = null,
73    ) {
74        $this->id = new AuditLogIdentifier((float) ApieLib::getPsrClock()->now()->format('U.u'), $reference);
75        $object = $serializedProperties->toPhpObject();
76
77        $this->snapshot = EntitySnapshot::createFrom($object);
78        $this->permissionSnapshot = $object instanceof RequiresPermissionsInterface ? $object->getRequiredPermissions()->toStringList() : new StringSet();
79        foreach (self::EVENT_MAPPING as $class => $property) {
80            $this->$property = ($event instanceof $class) ? $event : null;
81        }
82    }
83
84    #[SearchFilterOption(enabled: false)]
85    #[StaticCheck(new Policy('staticReadDescription', enabledOnMissingRule: true))]
86    #[RuntimeCheck(new Policy('readDescription'))]
87    public function getDescription(
88        ApieTranslatorInterface $translator,
89        ApieContext $context
90    ): NonEmptyString {
91        $context = $context->withContext(AuditLog::class, $this);
92        foreach (self::EVENT_MAPPING as $property) {
93            if ($this->$property !== null) {
94                return $this->$property->getDescription($translator, $context, $this->reference->getEntityClass()->toNative());
95            }
96        }
97        return NonEmptyString::fromNative(
98            $translator->getGeneralTranslation(
99                $context,
100                new TranslationString('audit_log.unknown_event')
101            )
102        );
103    }
104
105    #[RuntimeCheck(new Policy('readEvent'))]
106    #[StaticCheck(new Policy('staticReadEvent', enabledOnMissingRule: true))]
107    public function getEvent(): AuditLogEvent
108    {
109        foreach (self::EVENT_MAPPING as $property) {
110            if ($this->$property !== null) {
111                return $this->$property->getEvent();
112            }
113        }
114        throw new \LogicException('Unknown event type');
115    }
116
117    #[RuntimeCheck(new Policy('readCreatedBy'))]
118    #[StaticCheck(new Policy('staticReadCreatedBy', enabledOnMissingRule: true))]
119    public function getCreatedBy(): ?NonEmptyString
120    {
121        $object = $this->createdBy?->toPhpObject();
122        if ($object === null) {
123            return null;
124        }
125        if ($object instanceof EntityInterface) {
126            return NonEmptyString::fromNative(Utils::toString($object->getId()));
127        }
128
129        return NonEmptyString::fromNative(Utils::toString($object));
130
131    }
132
133    #[RuntimeCheck(new Policy('readRequiredPermissions'))]
134    #[StaticCheck(new Policy('staticReadRequiredPermissions', enabledOnMissingRule: true))]
135    public function getRequiredPermissions(): PermissionList
136    {
137        $object = $this->serializedProperties->toPhpObject();
138        if ($object instanceof RequiresPermissionsInterface) {
139            return $object->getRequiredPermissions();
140        }
141
142        return new PermissionList($this->permissionSnapshot->toArray());
143    }
144
145    public function getId(): AuditLogIdentifier
146    {
147        return $this->id;
148    }
149
150    #[StaticCheck(new Policy('staticReadOrigin', enabledOnMissingRule: true))]
151    #[RuntimeCheck(new Policy('readOrigin'))]
152    public function getOrigin(): AuditOrigin
153    {
154        return $this->origin;
155    }
156
157    #[StaticCheck(new Policy('staticReadReference', enabledOnMissingRule: true))]
158    #[RuntimeCheck(new Policy('readReference'))]
159    public function getReference(): IdFriendlyEntityReference
160    {
161        return $this->reference;
162    }
163
164    #[StaticCheck(new Policy('staticReadData', enabledOnMissingRule: true))]
165    #[RuntimeCheck(new Policy('readData'))]
166    public function getData(): EntityInterface|EntitySnapshotInstance
167    {
168        $entity = $this->serializedProperties->toPhpObject();
169        if ($entity instanceof EntityInterface) {
170            return $entity;
171        }
172        return $this->snapshot;
173    }
174
175    #[StaticCheck(new Policy('staticReadSnapshot', enabledOnMissingRule: true))]
176    #[RuntimeCheck(new Policy('readSnapshot'))]
177    public function getSnapshot(): EntitySnapshotInstance
178    {
179        return $this->snapshot;
180    }
181}