Skip to content

Commit

Permalink
Merge pull request #3 from archiprocode/pulls/0/make-DataObject-event…
Browse files Browse the repository at this point in the history
…-constructor-more-flexible

Refactor how DataObjectEvent are created.
  • Loading branch information
maxime-rainville authored Nov 20, 2024
2 parents 5430498 + 90fdf25 commit 38c064f
Show file tree
Hide file tree
Showing 4 changed files with 98 additions and 66 deletions.
61 changes: 42 additions & 19 deletions src/Event/DataObjectEvent.php
Original file line number Diff line number Diff line change
Expand Up @@ -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<T>
*/
private readonly string $objectClass;

/**
* @var int
*/
private readonly int $objectID;

/**
* @var array<string,mixed>
*/
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<T> $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();
}

Expand All @@ -70,6 +80,8 @@ public function getObjectID(): int

/**
* Get the class name of the affected DataObject
*
* @return class-string<T>
*/
public function getObjectClass(): string
{
Expand Down Expand Up @@ -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<string,mixed>
*/
public function getRecord(): array
{
return $this->record;
}

/**
* Serialize the event to a string
*/
Expand All @@ -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,
Expand Down
35 changes: 6 additions & 29 deletions src/Extension/EventDispatchExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
);

Expand All @@ -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
);

Expand All @@ -68,10 +64,8 @@ public function onAfterPublish(): void
}

$event = DataObjectEvent::create(
get_class($owner),
$owner->ID,
$owner,
Operation::PUBLISH,
$this->getVersion(),
Security::getCurrentUser()?->ID
);

Expand All @@ -89,10 +83,8 @@ public function onAfterUnpublish(): void
}

$event = DataObjectEvent::create(
get_class($owner),
$owner->ID,
$owner,
Operation::UNPUBLISH,
$this->getVersion(),
Security::getCurrentUser()?->ID
);

Expand All @@ -110,10 +102,8 @@ public function onAfterArchive(): void
}

$event = DataObjectEvent::create(
get_class($owner),
$owner->ID,
$owner,
Operation::ARCHIVE,
$this->getVersion(),
Security::getCurrentUser()?->ID
);

Expand All @@ -131,10 +121,8 @@ public function onAfterRestore(): void
}

$event = DataObjectEvent::create(
get_class($owner),
$owner->ID,
$owner,
Operation::RESTORE,
$this->getVersion(),
Security::getCurrentUser()?->ID
);

Expand All @@ -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;
}
}
29 changes: 19 additions & 10 deletions tests/php/Event/DataObjectEventTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand All @@ -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);
Expand All @@ -53,7 +54,7 @@ public function testGetVersionedObject(): void
$object->write();

/** @var DataObjectEvent<VersionedDataObject> $event */
$event = DataObjectEvent::create(VersionedDataObject::class, $object->ID, Operation::UPDATE, $object->Version);
$event = DataObjectEvent::create($object, Operation::UPDATE);

// Get current version
/** @var VersionedDataObject $currentObject */
Expand All @@ -66,8 +67,10 @@ public function testGetVersionedObject(): void
$this->assertEquals('Updated Title', $versionedObject->Title);

// Get previous version
$previousObject = $object;
$previousObject->Version--;
/** @var DataObjectEvent<VersionedDataObject> $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);
Expand All @@ -77,25 +80,31 @@ 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);
}

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<SimpleDataObject> $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());
}
Expand Down
39 changes: 31 additions & 8 deletions tests/php/Listener/DataObjectEventListenerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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);
Expand All @@ -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) {
Expand All @@ -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) {
Expand All @@ -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);
}

Expand Down

0 comments on commit 38c064f

Please sign in to comment.