diff --git a/.gitignore b/.gitignore index 325dd6f..4a59061 100644 --- a/.gitignore +++ b/.gitignore @@ -8,4 +8,5 @@ *.swo .DS_Store composer.lock -/public/ \ No newline at end of file +/public/ +.php-cs-fixer.cache \ No newline at end of file diff --git a/src/Contract/ListenerLoaderInterface.php b/src/Contract/ListenerLoaderInterface.php index bde31fb..413ff80 100644 --- a/src/Contract/ListenerLoaderInterface.php +++ b/src/Contract/ListenerLoaderInterface.php @@ -6,7 +6,7 @@ /** * Interface for classes that load event listeners into a ListenerProvider. - * + * * This interface allows for modular and configurable loading of event listeners, * making it easier to organize and maintain event listeners in different parts * of the application. @@ -15,11 +15,11 @@ interface ListenerLoaderInterface { /** * Loads event listeners into the provided ListenerProvider. - * + * * Implementations should use this method to register their event listeners * with the provider, typically using the provider's addListener method. - * + * * @param ListenerProvider $provider The provider to load listeners into */ public function loadListeners(ListenerProvider $provider): void; -} \ No newline at end of file +} diff --git a/src/DataObjectEventListener.php b/src/DataObjectEventListener.php index 91ab094..a12feba 100644 --- a/src/DataObjectEventListener.php +++ b/src/DataObjectEventListener.php @@ -9,7 +9,7 @@ /** * Event listener for DataObject events that filters events based on operation type and object class. - * + * * This listener can be configured to only handle specific operations (create, update, delete etc) * and specific DataObject classes. When an event matches the configured criteria, the callback * is executed with the event. @@ -19,9 +19,9 @@ class DataObjectEventListener /** * Creates a new DataObject event listener. * - * @param Closure(DataObjectEvent): void $callback Callback to execute when an event matches - * @param class-string[] $classes Array of DataObject class names to listen for - * @param Operation[]|null $operations Array of operations to listen for. If null, listens for all operations. + * @param Closure(DataObjectEvent): void $callback Callback to execute when an event matches + * @param class-string[] $classes Array of DataObject class names to listen for + * @param Operation[]|null $operations Array of operations to listen for. If null, listens for all operations. */ public function __construct( private Closure $callback, @@ -33,7 +33,7 @@ public function __construct( /** * Handles a DataObject event. - * + * * Checks if the event matches the configured operations and classes, * and executes the callback if it does. * @@ -57,10 +57,11 @@ public function __invoke(DataObjectEvent $event): void /** * Checks if the given class matches any of the configured target classes. - * + * * A match occurs if the class is either the same as or a subclass of any target class. * * @param string $class The class name to check + * * @return bool True if the class should be handled, false otherwise */ private function shouldHandleClass(string $class): bool diff --git a/src/Event/DataObjectEvent.php b/src/Event/DataObjectEvent.php index 47ff3d4..b2cf07e 100644 --- a/src/Event/DataObjectEvent.php +++ b/src/Event/DataObjectEvent.php @@ -9,18 +9,18 @@ /** * Event class representing operations performed on DataObjects. - * + * * This event is dispatched whenever a significant operation occurs on a DataObject, * such as creation, updates, deletion, or versioning operations. It captures key * information about the operation including: - * + * * - The ID of the affected DataObject * - The class of the DataObject * - The type of operation performed * - The version number (for versioned objects) * - The ID of the member who performed the operation * - The timestamp when the operation occurred - * + * * Example usage: * ```php * $event = DataObjectEvent::create( @@ -42,11 +42,11 @@ class DataObjectEvent private readonly int $timestamp; /** - * @param int $objectID The ID of the affected DataObject - * @param string $objectClass The class name 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 int $objectID The ID of the affected DataObject + * @param string $objectClass The class name 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 */ public function __construct( private readonly int $objectID, @@ -108,9 +108,9 @@ public function getTimestamp(): int /** * Get the DataObject associated with this event - * + * * @param bool $useVersion If true and the object is versioned, retrieves the specific version that was affected - * Note: This may return null if the object has been deleted since the event was created + * Note: This may return null if the object has been deleted since the event was created */ public function getObject(bool $useVersion = false): ?DataObject { @@ -119,12 +119,12 @@ public function getObject(bool $useVersion = false): ?DataObject } $object = DataObject::get_by_id($this->objectClass, $this->objectID); - + // If we want the specific version and the object is versioned if ($useVersion && $this->version && $object && $object->hasExtension(Versioned::class)) { /** @var Versioned|DataObject $object */ - return $object->Version == $this->version - ? $object + return $object->Version == $this->version + ? $object : $object->Versions()->byID($this->version); } @@ -133,7 +133,7 @@ public function getObject(bool $useVersion = false): ?DataObject /** * Get the Member who performed the operation - * + * * Note: This may return null if the member has been deleted since the event was created * or if the operation was performed by a system process */ @@ -163,20 +163,20 @@ public function serialize(): string /** * Unserialize the event from a string - * + * * @param string $data */ public function unserialize(string $data): void { $unserialized = unserialize($data); - + // Use reflection to set readonly properties $reflection = new \ReflectionClass($this); - + foreach ($unserialized as $property => $value) { $prop = $reflection->getProperty($property); $prop->setAccessible(true); $prop->setValue($this, $value); } } -} \ No newline at end of file +} diff --git a/src/Event/Operation.php b/src/Event/Operation.php index 679854c..b0975a8 100644 --- a/src/Event/Operation.php +++ b/src/Event/Operation.php @@ -4,10 +4,10 @@ /** * Represents the type of operation performed on a DataObject. - * + * * This enum is used to identify what kind of operation triggered a DataObjectEvent. * Each operation maps to a specific action in the Silverstripe CMS: - * + * * - CREATE: First time a DataObject is written to the database * - UPDATE: Subsequent writes to an existing DataObject * - DELETE: When a DataObject is deleted (both soft and hard deletes) @@ -25,4 +25,4 @@ enum Operation: string case UNPUBLISH = 'unpublish'; case ARCHIVE = 'archive'; case RESTORE = 'restore'; -} \ No newline at end of file +} diff --git a/src/Extension/EventDispatchExtension.php b/src/Extension/EventDispatchExtension.php index 79fbb55..6c35325 100644 --- a/src/Extension/EventDispatchExtension.php +++ b/src/Extension/EventDispatchExtension.php @@ -2,19 +2,20 @@ namespace ArchiPro\Silverstripe\EventDispatcher\Extension; -use SilverStripe\Core\Injector\Injector; -use SilverStripe\ORM\DataObject; -use SilverStripe\Versioned\Versioned; use ArchiPro\Silverstripe\EventDispatcher\Event\DataObjectEvent; use ArchiPro\Silverstripe\EventDispatcher\Event\Operation; use ArchiPro\Silverstripe\EventDispatcher\Service\EventService; use SilverStripe\Core\Extension; +use SilverStripe\Core\Injector\Injector; +use SilverStripe\ORM\DataObject; use SilverStripe\Security\Security; +use SilverStripe\Versioned\Versioned; /** * Extension that adds event dispatching capabilities to DataObjects. - * + * * @property DataObject|Versioned $owner + * * @method DataObject getOwner() */ class EventDispatchExtension extends Extension @@ -34,7 +35,7 @@ public function onAfterWrite(): void $owner->hasExtension(Versioned::class) ? $owner->Version : null, Security::getCurrentUser()?->ID ); - + $this->dispatchEvent($event); } @@ -51,7 +52,7 @@ public function onBeforeDelete(): void $owner->hasExtension(Versioned::class) ? $owner->Version : null, Security::getCurrentUser()?->ID ); - + $this->dispatchEvent($event); } @@ -72,7 +73,7 @@ public function onAfterPublish(): void $owner->Version, Security::getCurrentUser()?->ID ); - + $this->dispatchEvent($event); } @@ -93,7 +94,7 @@ public function onAfterUnpublish(): void $owner->Version, Security::getCurrentUser()?->ID ); - + $this->dispatchEvent($event); } @@ -114,7 +115,7 @@ public function onAfterArchive(): void $owner->Version, Security::getCurrentUser()?->ID ); - + $this->dispatchEvent($event); } @@ -135,7 +136,7 @@ public function onAfterRestore(): void $owner->Version, Security::getCurrentUser()?->ID ); - + $this->dispatchEvent($event); } @@ -146,4 +147,4 @@ protected function dispatchEvent(DataObjectEvent $event): DataObjectEvent { return Injector::inst()->get(EventService::class)->dispatch($event); } -} \ No newline at end of file +} diff --git a/src/Service/EventService.php b/src/Service/EventService.php index 9fb133b..b511604 100644 --- a/src/Service/EventService.php +++ b/src/Service/EventService.php @@ -5,12 +5,12 @@ use ArchiPro\EventDispatcher\AsyncEventDispatcher; use ArchiPro\EventDispatcher\ListenerProvider; use ArchiPro\Silverstripe\EventDispatcher\Contract\ListenerLoaderInterface; -use SilverStripe\Core\Injector\Injectable; use SilverStripe\Core\Config\Configurable; +use SilverStripe\Core\Injector\Injectable; /** * Core service class for handling event dispatching in Silverstripe. - * + * * This service wraps a PSR-14 compliant event dispatcher and provides * a centralized way to dispatch events throughout the application. */ @@ -21,12 +21,14 @@ class EventService /** * @config + * * @var array> Map of event class names to arrays of listener callbacks */ private static array $listeners = []; /** * @config + * * @var array Array of listener loaders */ private static array $loaders = []; @@ -76,6 +78,7 @@ public function addListener(string $event, callable $listener): void /** * Adds a listener loader to the event service + * * @throws \RuntimeException If the loader does not implement ListenerLoaderInterface */ public function addListenerLoader(ListenerLoaderInterface $loader): void @@ -104,4 +107,4 @@ public function getListenerProvider(): ListenerProvider { return $this->listenerProvider; } -} \ No newline at end of file +} diff --git a/tests/php/Event/AbstractDataObjectEventTest.php b/tests/php/Event/AbstractDataObjectEventTest.php index 7b297de..47ec31a 100644 --- a/tests/php/Event/AbstractDataObjectEventTest.php +++ b/tests/php/Event/AbstractDataObjectEventTest.php @@ -2,8 +2,8 @@ namespace ArchiPro\Silverstripe\EventDispatcher\Tests\Event; -use SilverStripe\Dev\SapphireTest; use ArchiPro\Silverstripe\EventDispatcher\Event\DataObjectWriteEvent; +use SilverStripe\Dev\SapphireTest; class AbstractDataObjectEventTest extends SapphireTest { @@ -40,4 +40,4 @@ public function testJsonSerialization(): void $this->assertArrayHasKey('changes', $data); $this->assertArrayHasKey('timestamp', $data); } -} \ No newline at end of file +} diff --git a/tests/php/Event/DataObjectEventTest.php b/tests/php/Event/DataObjectEventTest.php index ae6ffb7..31e47ed 100644 --- a/tests/php/Event/DataObjectEventTest.php +++ b/tests/php/Event/DataObjectEventTest.php @@ -8,7 +8,6 @@ use ArchiPro\Silverstripe\EventDispatcher\Tests\Mock\VersionedDataObject; use SilverStripe\Dev\SapphireTest; use SilverStripe\Security\Member; -use SilverStripe\Versioned\Versioned; class DataObjectEventTest extends SapphireTest { @@ -35,9 +34,9 @@ public function testGetObject(): void { /** @var SimpleDataObject $object */ $object = $this->objFromFixture(SimpleDataObject::class, 'object1'); - + $event = DataObjectEvent::create($object->ID, SimpleDataObject::class, Operation::UPDATE); - + $this->assertNotNull($event->getObject()); $this->assertEquals($object->ID, $event->getObject()->ID); } @@ -46,21 +45,21 @@ public function testGetVersionedObject(): void { /** @var VersionedDataObject $object */ $object = $this->objFromFixture(VersionedDataObject::class, 'versioned1'); - + // Create a new version $object->Title = 'Updated Title'; $object->write(); - + $event = DataObjectEvent::create($object->ID, VersionedDataObject::class, Operation::UPDATE, $object->Version); - + // Get current version $currentObject = $event->getObject(false); $this->assertEquals('Updated Title', $currentObject->Title); - + // Get specific version $versionedObject = $event->getObject(true); $this->assertEquals('Updated Title', $versionedObject->Title); - + // Get previous version $previousEvent = DataObjectEvent::create($object->ID, VersionedDataObject::class, Operation::UPDATE, $object->Version - 1); $previousVersion = $previousEvent->getObject(true); @@ -71,9 +70,9 @@ public function testGetMember(): void { /** @var Member $member */ $member = $this->objFromFixture(Member::class, 'member1'); - + $event = DataObjectEvent::create(1, SimpleDataObject::class, Operation::CREATE, null, $member->ID); - + $this->assertNotNull($event->getMember()); $this->assertEquals($member->ID, $event->getMember()->ID); } @@ -81,11 +80,11 @@ public function testGetMember(): void public function testSerialization(): void { $event = DataObjectEvent::create(1, SimpleDataObject::class, Operation::CREATE, 2, 3); - + $serialized = serialize($event); /** @var DataObjectEvent $unserialized */ $unserialized = unserialize($serialized); - + $this->assertEquals(1, $unserialized->getObjectID()); $this->assertEquals(SimpleDataObject::class, $unserialized->getObjectClass()); $this->assertEquals(Operation::CREATE, $unserialized->getOperation()); @@ -93,4 +92,4 @@ public function testSerialization(): void $this->assertEquals(3, $unserialized->getMemberID()); $this->assertEquals($event->getTimestamp(), $unserialized->getTimestamp()); } -} \ No newline at end of file +} diff --git a/tests/php/Event/DataObjectVersionEventTest.php b/tests/php/Event/DataObjectVersionEventTest.php index c50f971..c4a2eed 100644 --- a/tests/php/Event/DataObjectVersionEventTest.php +++ b/tests/php/Event/DataObjectVersionEventTest.php @@ -2,8 +2,8 @@ namespace ArchiPro\Silverstripe\EventDispatcher\Tests\Event; -use SilverStripe\Dev\SapphireTest; use ArchiPro\Silverstripe\EventDispatcher\Event\DataObjectVersionEvent; +use SilverStripe\Dev\SapphireTest; class DataObjectVersionEventTest extends SapphireTest { @@ -39,4 +39,4 @@ public function testVersionJsonSerialization(): void $this->assertArrayHasKey('version', $data); $this->assertEquals(2, $data['version']); } -} \ No newline at end of file +} diff --git a/tests/php/Extension/EventDispatchExtensionTest.php b/tests/php/Extension/EventDispatchExtensionTest.php index a0088c8..474b988 100644 --- a/tests/php/Extension/EventDispatchExtensionTest.php +++ b/tests/php/Extension/EventDispatchExtensionTest.php @@ -76,7 +76,7 @@ public function testDeleteEvent(): void $object = SimpleDataObject::create(['Title' => 'Test']); $object->write(); EventLoop::run(); - + static::$events = []; $object->delete(); EventLoop::run(); @@ -101,7 +101,7 @@ public function testVersionedEvents(): void // Test publish $object->publishRecursive(); EventLoop::run(); - + $this->assertCount(2, static::$events, 'Expected 2 events, 1 for create and 1 for publish'); $this->assertEquals(Operation::PUBLISH, static::$events[1]->getOperation()); $this->assertEquals($member->ID, static::$events[1]->getMemberID()); @@ -110,7 +110,7 @@ public function testVersionedEvents(): void static::$events = []; $object->doUnpublish(); EventLoop::run(); - + $this->assertCount(2, static::$events, 'Expected 2 events, 1 for deleting the live version and 1 for unpublish'); $this->assertEquals(Operation::UNPUBLISH, static::$events[1]->getOperation()); @@ -118,8 +118,8 @@ public function testVersionedEvents(): void static::$events = []; $object->doArchive(); EventLoop::run(); - + $this->assertCount(2, static::$events, 'Expected 2 events, 1 for deleting the draft version version and 1 for archive'); $this->assertEquals(Operation::ARCHIVE, static::$events[1]->getOperation()); } -} \ No newline at end of file +} diff --git a/tests/php/Mock/SimpleDataObject.php b/tests/php/Mock/SimpleDataObject.php index ac713e3..827d00f 100644 --- a/tests/php/Mock/SimpleDataObject.php +++ b/tests/php/Mock/SimpleDataObject.php @@ -20,4 +20,4 @@ class SimpleDataObject extends DataObject implements TestOnly private static array $extensions = [ EventDispatchExtension::class, ]; -} \ No newline at end of file +} diff --git a/tests/php/Mock/VersionedDataObject.php b/tests/php/Mock/VersionedDataObject.php index a5236dd..dc3030f 100644 --- a/tests/php/Mock/VersionedDataObject.php +++ b/tests/php/Mock/VersionedDataObject.php @@ -9,6 +9,7 @@ /** * @property string $Title + * * @mixin Versioned */ class VersionedDataObject extends DataObject implements TestOnly @@ -23,4 +24,4 @@ class VersionedDataObject extends DataObject implements TestOnly EventDispatchExtension::class, Versioned::class, ]; -} \ No newline at end of file +} diff --git a/tests/php/Service/EventServiceTest.php b/tests/php/Service/EventServiceTest.php index b7ee230..4fe9416 100644 --- a/tests/php/Service/EventServiceTest.php +++ b/tests/php/Service/EventServiceTest.php @@ -5,20 +5,20 @@ use ArchiPro\Silverstripe\EventDispatcher\Service\EventService; use ArchiPro\Silverstripe\EventDispatcher\Tests\TestListenerLoader; use Revolt\EventLoop; -use SilverStripe\Dev\SapphireTest; use SilverStripe\Core\Injector\Injector; +use SilverStripe\Dev\SapphireTest; + class EventServiceTest extends SapphireTest { - protected function setUp(): void { parent::setUp(); } - + public function testEventDispatch(): void { // Create test event - $event = new class { + $event = new class () { public bool $handled = false; }; @@ -42,17 +42,17 @@ public function testEventDispatch(): void public function testEventDispatchWithConfiguredListener(): void { // Create test event - $event = new class { + $event = new class () { public bool $handled = false; }; // Configure listener via config $eventClass = get_class($event); EventService::config()->set('listeners', [ $eventClass => [ - function($event) { + function ($event) { $event->handled = true; - } - ] + }, + ], ]); // Get fresh service instance with config applied @@ -70,10 +70,10 @@ function($event) { public function testEventDispatchWithConfiguredLoader(): void { // Create test event - $event = new class { + $event = new class () { public bool $handled = false; }; - + // Create test loader $loader = new TestListenerLoader(get_class($event)); @@ -83,13 +83,13 @@ public function testEventDispatchWithConfiguredLoader(): void // Get fresh service instance with config applied $service = Injector::inst()->get(EventService::class); $this->assertTrue($loader->loaded, 'Loader should have been used'); - + // Dispatch event $result = $service->dispatch($event); - + EventLoop::run(); // Assert loader was used and listener was called $this->assertTrue($loader->eventFired, 'Configured event listener should have been called'); } -} \ No newline at end of file +} diff --git a/tests/php/TestListenerLoader.php b/tests/php/TestListenerLoader.php index 77c87ae..c8e6301 100644 --- a/tests/php/TestListenerLoader.php +++ b/tests/php/TestListenerLoader.php @@ -16,7 +16,8 @@ class TestListenerLoader implements ListenerLoaderInterface public function __construct( private string $eventName - ) {} + ) { + } public function loadListeners(ListenerProvider $provider): void {