diff --git a/src/Event/DataObjectEvent.php b/src/Event/DataObjectEvent.php index 0b6bdc4..3bc229c 100644 --- a/src/Event/DataObjectEvent.php +++ b/src/Event/DataObjectEvent.php @@ -21,42 +21,52 @@ * - The ID of the member who performed the operation * - The timestamp when the operation occurred * - * Example usage: - * ```php - * $event = DataObjectEvent::create( - * get_class($dataObject), - * $dataObject->ID, - * Operation::UPDATE, - * $dataObject->Version, - * Security::getCurrentUser()?->ID - * ); - * ``` - * * @template T of DataObject */ class DataObjectEvent { use Injectable; + /** + * @var class-string + */ + private readonly string $objectClass; + + /** + * @var int + */ + private readonly int $objectID; + + /** + * @var array + */ + private readonly array $record; + + /** + * @var int|null + */ + private readonly ?int $version; + /** * @var int Unix timestamp when the event was created */ private readonly int $timestamp; /** - * @param class-string $objectClass The class name of the affected DataObject - * @param int $objectID The ID of the affected DataObject - * @param Operation $operation The type of operation performed - * @param int|null $version The version number (for versioned objects) - * @param int|null $memberID The ID of the member who performed the operation + * @param T $object The DataObject instance this event relates to + * @param Operation $operation The type of operation performed + * @param int|null $memberID The ID of the member who performed the operation */ public function __construct( - private readonly string $objectClass, - private readonly int $objectID, + DataObject $object, private readonly Operation $operation, - private readonly ?int $version = null, private readonly ?int $memberID = null ) { + $this->objectClass = get_class($object); + $this->objectID = $object->ID; + $this->record = $object->getQueriedDatabaseFields(); + // @phpstan-ignore property.notFound + $this->version = $object->hasExtension(Versioned::class) ? $object->Version : null; $this->timestamp = time(); } @@ -70,6 +80,8 @@ public function getObjectID(): int /** * Get the class name of the affected DataObject + * + * @return class-string */ public function getObjectClass(): string { @@ -146,6 +158,16 @@ public function getMember(): ?Member return Member::get()->byID($this->memberID); } + /** + * Get the record data at the time of the event + * + * @return array + */ + public function getRecord(): array + { + return $this->record; + } + /** * Serialize the event to a string */ @@ -154,6 +176,7 @@ public function serialize(): string return serialize([ 'objectID' => $this->objectID, 'objectClass' => $this->objectClass, + 'record' => $this->record, 'operation' => $this->operation, 'version' => $this->version, 'memberID' => $this->memberID, diff --git a/src/Extension/EventDispatchExtension.php b/src/Extension/EventDispatchExtension.php index 23481d4..e27825d 100644 --- a/src/Extension/EventDispatchExtension.php +++ b/src/Extension/EventDispatchExtension.php @@ -28,12 +28,10 @@ public function onAfterWrite(): void { $owner = $this->getOwner(); $event = DataObjectEvent::create( - get_class($owner), - $owner->ID, + $owner, // By this point isInDB() will return true even for new records since the ID is already set // Instead check if the ID field was changed which indicates this is a new record $owner->isChanged('ID') ? Operation::CREATE : Operation::UPDATE, - $this->getVersion(), Security::getCurrentUser()?->ID ); @@ -47,10 +45,8 @@ public function onBeforeDelete(): void { $owner = $this->getOwner(); $event = DataObjectEvent::create( - get_class($owner), - $owner->ID, + $owner, Operation::DELETE, - $this->getVersion(), Security::getCurrentUser()?->ID ); @@ -68,10 +64,8 @@ public function onAfterPublish(): void } $event = DataObjectEvent::create( - get_class($owner), - $owner->ID, + $owner, Operation::PUBLISH, - $this->getVersion(), Security::getCurrentUser()?->ID ); @@ -89,10 +83,8 @@ public function onAfterUnpublish(): void } $event = DataObjectEvent::create( - get_class($owner), - $owner->ID, + $owner, Operation::UNPUBLISH, - $this->getVersion(), Security::getCurrentUser()?->ID ); @@ -110,10 +102,8 @@ public function onAfterArchive(): void } $event = DataObjectEvent::create( - get_class($owner), - $owner->ID, + $owner, Operation::ARCHIVE, - $this->getVersion(), Security::getCurrentUser()?->ID ); @@ -131,10 +121,8 @@ public function onAfterRestore(): void } $event = DataObjectEvent::create( - get_class($owner), - $owner->ID, + $owner, Operation::RESTORE, - $this->getVersion(), Security::getCurrentUser()?->ID ); @@ -152,15 +140,4 @@ protected function dispatchEvent(DataObjectEvent $event): Future { return Injector::inst()->get(EventService::class)->dispatch($event); } - - private function getVersion(): ?int - { - $owner = $this->getOwner(); - if (!$owner->hasExtension(Versioned::class)) { - return null; - } - - /** @var Versioned $owner */ - return $owner->Version; - } } diff --git a/tests/php/Event/DataObjectEventTest.php b/tests/php/Event/DataObjectEventTest.php index 6c159b1..57d3435 100644 --- a/tests/php/Event/DataObjectEventTest.php +++ b/tests/php/Event/DataObjectEventTest.php @@ -22,9 +22,11 @@ class DataObjectEventTest extends SapphireTest public function testEventCreation(): void { - $event = DataObjectEvent::create(SimpleDataObject::class, 1, Operation::CREATE, null, 1); + /** @var SimpleDataObject $object */ + $object = $this->objFromFixture(SimpleDataObject::class, 'object1'); + $event = DataObjectEvent::create($object, Operation::CREATE, 1); - $this->assertEquals(1, $event->getObjectID()); + $this->assertEquals($object->ID, $event->getObjectID()); $this->assertEquals(SimpleDataObject::class, $event->getObjectClass()); $this->assertEquals(Operation::CREATE, $event->getOperation()); $this->assertNull($event->getVersion()); @@ -36,8 +38,7 @@ public function testGetObject(): void { /** @var SimpleDataObject $object */ $object = $this->objFromFixture(SimpleDataObject::class, 'object1'); - - $event = DataObjectEvent::create(SimpleDataObject::class, $object->ID, Operation::UPDATE); + $event = DataObjectEvent::create($object, Operation::UPDATE); $this->assertNotNull($event->getObject()); $this->assertEquals($object->ID, $event->getObject()->ID); @@ -53,7 +54,7 @@ public function testGetVersionedObject(): void $object->write(); /** @var DataObjectEvent $event */ - $event = DataObjectEvent::create(VersionedDataObject::class, $object->ID, Operation::UPDATE, $object->Version); + $event = DataObjectEvent::create($object, Operation::UPDATE); // Get current version /** @var VersionedDataObject $currentObject */ @@ -66,8 +67,10 @@ public function testGetVersionedObject(): void $this->assertEquals('Updated Title', $versionedObject->Title); // Get previous version + $previousObject = $object; + $previousObject->Version--; /** @var DataObjectEvent $previousEvent */ - $previousEvent = DataObjectEvent::create(VersionedDataObject::class, $object->ID, Operation::UPDATE, $object->Version - 1); + $previousEvent = DataObjectEvent::create($previousObject, Operation::UPDATE); /** @var VersionedDataObject $previousVersion */ $previousVersion = $previousEvent->getObject(true); $this->assertEquals('Original Title', $previousVersion->Title); @@ -77,8 +80,10 @@ public function testGetMember(): void { /** @var Member $member */ $member = $this->objFromFixture(Member::class, 'member1'); + /** @var SimpleDataObject $object */ + $object = $this->objFromFixture(SimpleDataObject::class, 'object1'); - $event = DataObjectEvent::create(SimpleDataObject::class, 1, Operation::CREATE, null, $member->ID); + $event = DataObjectEvent::create($object, Operation::CREATE, $member->ID); $this->assertNotNull($event->getMember()); $this->assertEquals($member->ID, $event->getMember()->ID); @@ -86,16 +91,20 @@ public function testGetMember(): void public function testSerialization(): void { - $event = DataObjectEvent::create(SimpleDataObject::class, 1, Operation::CREATE, 2, 3); + $do = new SimpleDataObject(); + $do->Title = 'Test alpha'; + $do->write(); + $event = DataObjectEvent::create($do, Operation::CREATE, 3); $serialized = serialize($event); /** @var DataObjectEvent $unserialized */ $unserialized = unserialize($serialized); - $this->assertEquals(1, $unserialized->getObjectID()); + $this->assertEquals($do->ID, $unserialized->getObjectID()); $this->assertEquals(SimpleDataObject::class, $unserialized->getObjectClass()); + $this->assertEquals($do->getQueriedDatabaseFields(), $unserialized->getRecord()); $this->assertEquals(Operation::CREATE, $unserialized->getOperation()); - $this->assertEquals(2, $unserialized->getVersion()); + $this->assertNull($unserialized->getVersion()); $this->assertEquals(3, $unserialized->getMemberID()); $this->assertEquals($event->getTimestamp(), $unserialized->getTimestamp()); } diff --git a/tests/php/Listener/DataObjectEventListenerTest.php b/tests/php/Listener/DataObjectEventListenerTest.php index 9467958..9a42af0 100644 --- a/tests/php/Listener/DataObjectEventListenerTest.php +++ b/tests/php/Listener/DataObjectEventListenerTest.php @@ -45,19 +45,34 @@ function (DataObjectEvent $event) { [SimpleDataObject::class] ); + /** @var SimpleDataObject $simpleObject */ + $simpleObject = SimpleDataObject::create(); + $simpleObject->ID = 1; + /** @var VersionedDataObject $versionedObject */ + $versionedObject = VersionedDataObject::create(); + $versionedObject->ID = 1; + // Should handle SimpleDataObject event - $simpleEvent = DataObjectEvent::create(SimpleDataObject::class, 1, Operation::CREATE); + $simpleEvent = DataObjectEvent::create($simpleObject, Operation::CREATE); $listener($simpleEvent); $this->assertCount(1, $this->receivedEvents, 'Listener should handle SimpleDataObject events'); // Should not handle VersionedDataObject event - $versionedEvent = DataObjectEvent::create(VersionedDataObject::class, 1, Operation::CREATE); + $versionedEvent = DataObjectEvent::create($versionedObject, Operation::CREATE); $listener($versionedEvent); $this->assertCount(1, $this->receivedEvents, 'Listener should not handle VersionedDataObject events'); } public function testListenerHandlesInheritedClasses(): void { + /** @var SimpleDataObject $simpleObject */ + $simpleObject = SimpleDataObject::create(); + $simpleObject->write(); + + /** @var VersionedDataObject $versionedObject */ + $versionedObject = VersionedDataObject::create(); + $versionedObject->write(); + // Create listener that handles all DataObject events $listener = DataObjectEventListener::create( function (DataObjectEvent $event) { @@ -67,8 +82,8 @@ function (DataObjectEvent $event) { ); // Should handle both SimpleDataObject and VersionedDataObject events - $simpleEvent = DataObjectEvent::create(SimpleDataObject::class, 1, Operation::CREATE); - $versionedEvent = DataObjectEvent::create(VersionedDataObject::class, 1, Operation::CREATE); + $simpleEvent = DataObjectEvent::create($simpleObject, Operation::CREATE); + $versionedEvent = DataObjectEvent::create($versionedObject, Operation::CREATE); $listener($simpleEvent); $listener($versionedEvent); @@ -78,6 +93,10 @@ function (DataObjectEvent $event) { public function testListenerFiltersByOperation(): void { + /** @var SimpleDataObject $simpleObject */ + $simpleObject = SimpleDataObject::create(); + $simpleObject->write(); + // Create listener that only handles CREATE and UPDATE operations $listener = DataObjectEventListener::create( function (DataObjectEvent $event) { @@ -88,23 +107,27 @@ function (DataObjectEvent $event) { ); // Should handle CREATE event - $createEvent = DataObjectEvent::create(SimpleDataObject::class, 1, Operation::CREATE); + $createEvent = DataObjectEvent::create($simpleObject, Operation::CREATE); $listener($createEvent); $this->assertCount(1, $this->receivedEvents, 'Listener should handle CREATE events'); // Should handle UPDATE event - $updateEvent = DataObjectEvent::create(SimpleDataObject::class, 1, Operation::UPDATE); + $updateEvent = DataObjectEvent::create($simpleObject, Operation::UPDATE); $listener($updateEvent); $this->assertCount(2, $this->receivedEvents, 'Listener should handle UPDATE events'); // Should not handle DELETE event - $deleteEvent = DataObjectEvent::create(SimpleDataObject::class, 1, Operation::DELETE); + $deleteEvent = DataObjectEvent::create($simpleObject, Operation::DELETE); $listener($deleteEvent); $this->assertCount(2, $this->receivedEvents, 'Listener should not handle DELETE events'); } public function testListenerHandlesAllOperationsWhenNotSpecified(): void { + /** @var SimpleDataObject $simpleObject */ + $simpleObject = SimpleDataObject::create(); + $simpleObject->write(); + // Create listener without specifying operations $listener = DataObjectEventListener::create( function (DataObjectEvent $event) { @@ -115,7 +138,7 @@ function (DataObjectEvent $event) { // Should handle all operations foreach (Operation::cases() as $operation) { - $event = DataObjectEvent::create(SimpleDataObject::class, 1, $operation); + $event = DataObjectEvent::create($simpleObject, $operation); $listener($event); }