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