From 6de577191b8bb231f8279ff60a0e64ddf480ebb4 Mon Sep 17 00:00:00 2001 From: Torsten Dittmann Date: Tue, 29 Mar 2022 11:30:57 +0300 Subject: [PATCH] feat: event model parsing --- src/Appwrite/Event/Event.php | 177 +++++++++++++++++++++++++++------ tests/unit/Event/EventTest.php | 69 +++++++++++-- 2 files changed, 202 insertions(+), 44 deletions(-) diff --git a/src/Appwrite/Event/Event.php b/src/Appwrite/Event/Event.php index 2bd73248c3..3985a807f4 100644 --- a/src/Appwrite/Event/Event.php +++ b/src/Appwrite/Event/Event.php @@ -2,12 +2,13 @@ namespace Appwrite\Event; +use Exception; +use InvalidArgumentException; use Resque; class Event { - - const DATABASE_QUEUE_NAME= 'v1-database'; + const DATABASE_QUEUE_NAME = 'v1-database'; const DATABASE_CLASS_NAME = 'DatabaseV1'; const DELETE_QUEUE_NAME = 'v1-deletes'; @@ -33,27 +34,15 @@ class Event const BUILDS_QUEUE_NAME = 'v1-builds'; const BUILDS_CLASS_NAME = 'BuildsV1'; - - /** - * @var string - */ - protected $queue = ''; + + protected string $queue = ''; + protected string $class = ''; + protected array $params = []; /** - * @var string - */ - protected $class = ''; - - /** - * @var array - */ - protected $params = []; - - /** - * Event constructor. - * * @param string $queue * @param string $class + * @return void */ public function __construct(string $queue, string $class) { @@ -62,8 +51,10 @@ class Event } /** + * Set queue used for this event. + * * @param string $queue - * return $this + * @return Event */ public function setQueue(string $queue): self { @@ -72,16 +63,19 @@ class Event } /** + * Get queue used for this event. + * * @return string */ - public function getQueue() + public function getQueue(): string { return $this->queue; } /** + * Set class used for this event. * @param string $class - * return $this + * @return Event */ public function setClass(string $class): self { @@ -90,20 +84,23 @@ class Event } /** + * Get class used for this event. + * * @return string */ - public function getClass() + public function getClass(): string { return $this->class; } /** - * @param string $key - * @param mixed $value + * Set param of event. * - * @return $this + * @param string $key + * @param mixed $value + * @return Event */ - public function setParam(string $key, $value): self + public function setParam(string $key, mixed $value): self { $this->params[$key] = $value; @@ -111,29 +108,143 @@ class Event } /** - * @param string $key + * Get param of event. * - * @return mixed|null + * @param string $key + * @return mixed */ - public function getParam(string $key) + public function getParam(string $key): mixed { - return (isset($this->params[$key])) ? $this->params[$key] : null; + return $this->params[$key] ?? null; } /** * Execute Event. + * + * @return Event + * @throws InvalidArgumentException */ - public function trigger(): void + public function trigger(): self { Resque::enqueue($this->queue, $this->class, $this->params); - $this->reset(); + return $this->reset(); } + /** + * Resets event. + * + * @return Event + */ public function reset(): self { $this->params = []; return $this; } + + static function generateEvents(string $pattern, array $params = []): array + { + $parts = \explode('.', $pattern); + $count = \count($parts); + + if ($count < 2 || $count > 6) { + throw new Exception("Patten incorrect."); + } + + /** + * Identify all sestions of the pattern. + */ + $type = $parts[0]; + $action = match ($count) { + 2 => $parts[1], + 3, 4 => $parts[2], + 5, 6 => $parts[4] + }; + + if ($count > 4) { + $subType = $parts[2]; + $subResource = $parts[3]; + if ($count === 6) { + $attribute = $parts[5]; + } + } + if ($count > 2) { + $resource = $parts[1]; + if ($count === 4) { + $attribute = $parts[3]; + } + } + + $paramKeys = \array_keys($params); + $paramValues = \array_values($params); + + $patterns = []; + $resource ??= false; + $subResource ??= false; + $attribute ??= false; + + if (empty($params) && ($type ?? false) && !$resource) { + return [$pattern]; + } + + if ($resource && !\in_array(\trim($resource, '[]'), $paramKeys)) { + throw new InvalidArgumentException("{$resource} is missing from the params."); + } + + if ($subResource && !\in_array(\trim($subResource, '[]'), $paramKeys)) { + throw new InvalidArgumentException("{$subResource} is missing from the params."); + } + + /** + * Create all possible patterns including placeholders. + */ + if ($action) { + if ($subResource) { + if ($attribute) { + $patterns[] = \implode('.', [$type, $resource, $subType, $subResource, $action, $attribute]); + } + $patterns[] = \implode('.', [$type, $resource, $subType, $subResource, $action]); + $patterns[] = \implode('.', [$type, $resource, $subType, $subResource]); + } else { + if ($attribute) { + $patterns[] = \implode('.', [$type, $resource, $action, $attribute]); + } + $patterns[] = \implode('.', [$type, $resource, $action]); + $patterns[] = \implode('.', [$type, $resource]); + } + } + if ($subResource) { + $patterns[] = \implode('.', [$type, $resource, $subType, $subResource]); + } + + /** + * Removes all duplicates. + */ + $patterns = \array_unique($patterns); + + /** + * Set all possible values of the patterns and replace placeholders. + */ + $events = []; + foreach ($patterns as $eventPattern) { + $events[] = \str_replace($paramKeys, $paramValues, $eventPattern); + $events[] = \str_replace($paramKeys, '*', $eventPattern); + foreach ($paramKeys as $key) { + foreach ($paramKeys as $current) { + if ($current === $key) continue; + + $filtered = \array_filter($paramKeys, fn(string $k) => $k === $current); + $events[] = \str_replace($paramKeys, $paramValues, \str_replace($filtered, '*', $eventPattern)); + } + } + } + + /** + * Remove [] from the events. + */ + $events = \array_map(fn (string $event) => \str_replace(['[', ']'], '', $event), $events); + + return $events; + } } diff --git a/tests/unit/Event/EventTest.php b/tests/unit/Event/EventTest.php index 58d7a1c045..52afc58d5c 100644 --- a/tests/unit/Event/EventTest.php +++ b/tests/unit/Event/EventTest.php @@ -3,6 +3,7 @@ namespace Appwrite\Tests; use Appwrite\Event\Event; +use InvalidArgumentException; use PHPUnit\Framework\TestCase; use Utopia\App; @@ -12,7 +13,7 @@ class EventTest extends TestCase * @var Event */ protected $object = null; - + /** * @var string */ @@ -22,8 +23,8 @@ class EventTest extends TestCase { $redisHost = App::getEnv('_APP_REDIS_HOST', ''); $redisPort = App::getEnv('_APP_REDIS_PORT', ''); - \Resque::setBackend($redisHost.':'.$redisPort); - + \Resque::setBackend($redisHost . ':' . $redisPort); + $this->queue = 'v1-tests' . uniqid(); $this->object = new Event($this->queue, 'TestsV1'); } @@ -37,9 +38,9 @@ class EventTest extends TestCase $this->assertEquals($this->queue, $this->object->getQueue()); $this->object->setQueue('demo'); - + $this->assertEquals('demo', $this->object->getQueue()); - + $this->object->setQueue($this->queue); } @@ -48,9 +49,9 @@ class EventTest extends TestCase $this->assertEquals('TestsV1', $this->object->getClass()); $this->object->setClass('TestsV2'); - + $this->assertEquals('TestsV2', $this->object->getClass()); - + $this->object->setClass('TestsV1'); } @@ -58,8 +59,7 @@ class EventTest extends TestCase { $this->object ->setParam('eventKey1', 'eventValue1') - ->setParam('eventKey2', 'eventValue2') - ; + ->setParam('eventKey2', 'eventValue2'); $this->object->trigger(); @@ -73,8 +73,7 @@ class EventTest extends TestCase { $this->object ->setParam('eventKey1', 'eventValue1') - ->setParam('eventKey2', 'eventValue2') - ; + ->setParam('eventKey2', 'eventValue2'); $this->assertEquals('eventValue1', $this->object->getParam('eventKey1')); $this->assertEquals('eventValue2', $this->object->getParam('eventKey2')); @@ -85,4 +84,52 @@ class EventTest extends TestCase $this->assertEquals(null, $this->object->getParam('eventKey2')); $this->assertEquals(null, $this->object->getParam('eventKey3')); } + + public function testGenerateEvents() + { + $event = Event::generateEvents('users.create'); + $this->assertCount(1, $event); + $this->assertContains('users.create', $event); + + $event = Event::generateEvents('users.[userId].update.email', [ + 'userId' => 'torsten' + ]); + $this->assertCount(6, $event); + $this->assertContains('users.torsten.update.email', $event); + $this->assertContains('users.torsten.update', $event); + $this->assertContains('users.torsten', $event); + $this->assertContains('users.*.update.email', $event); + $this->assertContains('users.*.update', $event); + $this->assertContains('users.*', $event); + + $event = Event::generateEvents('collections.[collectionId].documents.[documentId].create', [ + 'collectionId' => 'chapters', + 'documentId' => 'prolog', + ]); + $this->assertCount(8, $event); + $this->assertContains('collections.chapters.documents.prolog.create', $event); + $this->assertContains('collections.chapters.documents.prolog', $event); + $this->assertContains('collections.chapters.documents.*.create', $event); + $this->assertContains('collections.chapters.documents.*', $event); + $this->assertContains('collections.*.documents.prolog.create', $event); + $this->assertContains('collections.*.documents.prolog', $event); + $this->assertContains('collections.*.documents.*.create', $event); + $this->assertContains('collections.*.documents.*', $event); + + try { + $event = Event::generateEvents('collections.[collectionId].documents.[documentId].create', [ + 'collectionId' => 'chapters' + ]); + $this->fail(); + } catch (\Throwable $th) { + $this->assertInstanceOf(InvalidArgumentException::class, $th, 'An invalid exception was thrown'); + } + + try { + $event = Event::generateEvents('collections.[collectionId].documents.[documentId].create'); + $this->fail(); + } catch (\Throwable $th) { + $this->assertInstanceOf(InvalidArgumentException::class, $th, 'An invalid exception was thrown'); + } + } }