Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
70.18% covered (warning)
70.18%
40 / 57
54.55% covered (warning)
54.55%
6 / 11
CRAP
0.00% covered (danger)
0.00%
0 / 1
AuditLog
70.18% covered (warning)
70.18%
40 / 57
54.55% covered (warning)
54.55%
6 / 11
43.93
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
 createTranslations
78.95% covered (warning)
78.95%
15 / 19
0.00% covered (danger)
0.00%
0 / 1
5.23
1<?php
2namespace Apie\Common\Other;
3
4use Apie\Common\ActionDefinitions\RunResourceMethodDefinition;
5use Apie\Common\Enums\AuditLogEvent;
6use Apie\Common\Other\Audit\AuditCreate;
7use Apie\Common\Other\Audit\AuditEvent;
8use Apie\Common\Other\Audit\AuditMethodCalled;
9use Apie\Common\Other\Audit\AuditMigration;
10use Apie\Common\Other\Audit\AuditModified;
11use Apie\Common\Other\Audit\AuditRead;
12use Apie\Common\Other\Audit\AuditRemoved;
13use Apie\Core\ApieLib;
14use Apie\Core\Attributes\AlwaysDisabled;
15use Apie\Core\Attributes\CmsIcon;
16use Apie\Core\Attributes\Context;
17use Apie\Core\Attributes\FakeCount;
18use Apie\Core\Attributes\Internal;
19use Apie\Core\Attributes\Policy;
20use Apie\Core\Attributes\ProvideTranslationMethod;
21use Apie\Core\Attributes\RuntimeCheck;
22use Apie\Core\Attributes\SearchFilterOption;
23use Apie\Core\Attributes\StaticCheck;
24use Apie\Core\Attributes\StoreOptions;
25use Apie\Core\BoundedContext\BoundedContextHashmap;
26use Apie\Core\Context\ApieContext;
27use Apie\Core\ContextConstants;
28use Apie\Core\Entities\EntityInterface;
29use Apie\Core\Lists\PermissionList;
30use Apie\Core\Lists\StringSet;
31use Apie\Core\Permissions\RequiresPermissionsInterface;
32use Apie\Core\Translator\ApieTranslatorInterface;
33use Apie\Core\Translator\ValueObjects\AuditLogEventMessage;
34use Apie\Core\ValueObjects\IdFriendlyEntityReference;
35use Apie\Core\ValueObjects\NonEmptyString;
36use Apie\Core\ValueObjects\Utils;
37use Apie\Serializer\ValueObjects\SerializedPhpObject;
38
39#[FakeCount(0)]
40#[CmsIcon('tabler:history')]
41#[StaticCheck(new Policy('staticCanViewAny', enabledOnMissingRule: true))]
42#[RuntimeCheck(
43    new Policy('canView', 'canViewAny'),
44    new ShouldApplyAuditablePermission()
45)]
46#[ProvideTranslationMethod('createTranslations')]
47class AuditLog implements EntityInterface, RequiresPermissionsInterface
48{
49    private AuditLogIdentifier $id;
50
51    private StringSet $permissionSnapshot;
52
53    #[StoreOptions(alwaysMixedData: true)]
54    private EntitySnapshotInstance $snapshot;
55
56    // separate properties for optimization in the database
57    private readonly ?AuditCreate $createEvent;
58    private readonly ?AuditModified $modifiedEvent;
59    private readonly ?AuditRemoved $removedEvent;
60    private readonly ?AuditRead $readEvent;
61    private readonly ?AuditMethodCalled $methodCalledEvent;
62    private readonly ?AuditMigration $migrationEvent;
63
64    private const EVENT_MAPPING = [
65        AuditCreate::class => 'createEvent',
66        AuditModified::class => 'modifiedEvent',
67        AuditRemoved::class => 'removedEvent',
68        AuditRead::class => 'readEvent',
69        AuditMethodCalled::class=> 'methodCalledEvent',
70        AuditMigration::class => 'migrationEvent'
71    ];
72
73    #[StaticCheck(new AlwaysDisabled())]
74    public function __construct(
75        private readonly IdFriendlyEntityReference $reference,
76        private readonly SerializedPhpObject $serializedProperties,
77        AuditEvent $event,
78        private readonly AuditOrigin $origin,
79        #[Context('_ignored')]
80        private readonly ?SerializedPhpObject $createdBy = null,
81    ) {
82        $this->id = new AuditLogIdentifier((float) ApieLib::getPsrClock()->now()->format('U.u'), $reference);
83        $object = $serializedProperties->toPhpObject();
84
85        $this->snapshot = EntitySnapshot::createFrom($object);
86        $this->permissionSnapshot = $object instanceof RequiresPermissionsInterface ? $object->getRequiredPermissions()->toStringList() : new StringSet();
87        foreach (self::EVENT_MAPPING as $class => $property) {
88            $this->$property = ($event instanceof $class) ? $event : null;
89        }
90    }
91
92    #[SearchFilterOption(enabled: false)]
93    #[StaticCheck(new Policy('staticReadDescription', enabledOnMissingRule: true))]
94    #[RuntimeCheck(new Policy('readDescription'))]
95    public function getDescription(
96        ApieTranslatorInterface $translator,
97        ApieContext $context
98    ): NonEmptyString {
99        $context = $context->withContext(AuditLog::class, $this);
100        foreach (self::EVENT_MAPPING as $property) {
101            if ($this->$property !== null) {
102                return $this->$property->getDescription($translator, $context, $this->reference->getEntityClass()->toNative());
103            }
104        }
105        return NonEmptyString::fromNative(
106            $translator->getGeneralTranslation(
107                $context,
108                AuditLogEventMessage::createUnknownEvent($context)
109            )
110        );
111    }
112
113    #[RuntimeCheck(new Policy('readEvent'))]
114    #[StaticCheck(new Policy('staticReadEvent', enabledOnMissingRule: true))]
115    public function getEvent(): AuditLogEvent
116    {
117        foreach (self::EVENT_MAPPING as $property) {
118            if ($this->$property !== null) {
119                return $this->$property->getEvent();
120            }
121        }
122        throw new \LogicException('Unknown event type');
123    }
124
125    #[RuntimeCheck(new Policy('readCreatedBy'))]
126    #[StaticCheck(new Policy('staticReadCreatedBy', enabledOnMissingRule: true))]
127    public function getCreatedBy(): ?NonEmptyString
128    {
129        $object = $this->createdBy?->toPhpObject();
130        if ($object === null) {
131            return null;
132        }
133        if ($object instanceof EntityInterface) {
134            return NonEmptyString::fromNative(Utils::toString($object->getId()));
135        }
136
137        return NonEmptyString::fromNative(Utils::toString($object));
138
139    }
140
141    #[RuntimeCheck(new Policy('readRequiredPermissions'))]
142    #[StaticCheck(new Policy('staticReadRequiredPermissions', enabledOnMissingRule: true))]
143    public function getRequiredPermissions(): PermissionList
144    {
145        $object = $this->serializedProperties->toPhpObject();
146        if ($object instanceof RequiresPermissionsInterface) {
147            return $object->getRequiredPermissions();
148        }
149
150        return new PermissionList($this->permissionSnapshot->toArray());
151    }
152
153    public function getId(): AuditLogIdentifier
154    {
155        return $this->id;
156    }
157
158    #[StaticCheck(new Policy('staticReadOrigin', enabledOnMissingRule: true))]
159    #[RuntimeCheck(new Policy('readOrigin'))]
160    public function getOrigin(): AuditOrigin
161    {
162        return $this->origin;
163    }
164
165    #[StaticCheck(new Policy('staticReadReference', enabledOnMissingRule: true))]
166    #[RuntimeCheck(new Policy('readReference'))]
167    public function getReference(): IdFriendlyEntityReference
168    {
169        return $this->reference;
170    }
171
172    #[StaticCheck(new Policy('staticReadData', enabledOnMissingRule: true))]
173    #[RuntimeCheck(new Policy('readData'))]
174    public function getData(): EntityInterface|EntitySnapshotInstance
175    {
176        $entity = $this->serializedProperties->toPhpObject();
177        if ($entity instanceof EntityInterface) {
178            return $entity;
179        }
180        return $this->snapshot;
181    }
182
183    #[StaticCheck(new Policy('staticReadSnapshot', enabledOnMissingRule: true))]
184    #[RuntimeCheck(new Policy('readSnapshot'))]
185    public function getSnapshot(): EntitySnapshotInstance
186    {
187        return $this->snapshot;
188    }
189
190    /**
191     * @param ApieContext $context
192     * @return array<int, AuditLogEventMessage>
193     */
194    #[Internal]
195    public static function createTranslations(ApieContext $context): array
196    {
197        $boundedContextHashmap = $context->getContext(BoundedContextHashmap::class, false);
198        $boundedContextId = $context->getContext(ContextConstants::BOUNDED_CONTEXT_ID, false);
199        $methodCalls = [];
200        if ($boundedContextId && $boundedContextHashmap instanceof BoundedContextHashmap) {
201            $boundedContext = $boundedContextHashmap[$boundedContextId] ?? null;
202            if ($boundedContext) {
203                foreach (RunResourceMethodDefinition::provideActionDefinitions($boundedContext, $context, false) as $definition) {
204                    $methodCalls[] = AuditLogEventMessage::createResourceMethodCalledEvent($context, $definition->getMethod()->name);
205                }
206            }
207        }
208        return [
209            AuditLogEventMessage::createUnknownEvent($context),
210            AuditLogEventMessage::createMigrationEvent($context),
211            ...$methodCalls,
212            AuditLogEventMessage::createResourceCreatedEvent($context),
213            AuditLogEventMessage::createResourceModifiedEvent($context),
214            AuditLogEventMessage::createResourceRemovedEvent($context),
215            AuditLogEventMessage::createResourceReplacedEvent($context),
216            AuditLogEventMessage::createResourceRetrievedEvent($context),
217            AuditLogEventMessage::createResourceRetrievedInListEvent($context),
218        ];
219    }
220}