From c1a7c16a68369fa7b9b288a715c03eb8eb2e87b5 Mon Sep 17 00:00:00 2001 From: javierdfm Date: Fri, 9 Jan 2026 16:38:23 +0100 Subject: [PATCH 1/3] feat: add channel batch updates support --- .../StreamChat/ChannelBatchUpdater.php | 242 ++++++++++++++++++ lib/GetStream/StreamChat/Client.php | 19 ++ tests/integration/IntegrationTest.php | 183 +++++++++++++ 3 files changed, 444 insertions(+) create mode 100644 lib/GetStream/StreamChat/ChannelBatchUpdater.php diff --git a/lib/GetStream/StreamChat/ChannelBatchUpdater.php b/lib/GetStream/StreamChat/ChannelBatchUpdater.php new file mode 100644 index 0000000..bf87719 --- /dev/null +++ b/lib/GetStream/StreamChat/ChannelBatchUpdater.php @@ -0,0 +1,242 @@ +client = $client; + } + + /** + * Adds members to channels matching the filter. + * @param array $filter Filter to match channels (keys: cids, types, filter_tags) + * @param array $members Array of member arrays, each with user_id (required) and optional channel_role + * @return StreamResponse + * @throws StreamException + */ + public function addMembers(array $filter, array $members): StreamResponse + { + $options = [ + "operation" => "addMembers", + "filter" => $filter, + "members" => $members, + ]; + return $this->client->updateChannelsBatch($options); + } + + /** + * Removes members from channels matching the filter. + * @param array $filter Filter to match channels (keys: cids, types, filter_tags) + * @param array $members Array of member arrays, each with user_id (required) and optional channel_role + * @return StreamResponse + * @throws StreamException + */ + public function removeMembers(array $filter, array $members): StreamResponse + { + $options = [ + "operation" => "removeMembers", + "filter" => $filter, + "members" => $members, + ]; + return $this->client->updateChannelsBatch($options); + } + + /** + * Invites members to channels matching the filter. + * @param array $filter Filter to match channels (keys: cids, types, filter_tags) + * @param array $members Array of member arrays, each with user_id (required) and optional channel_role + * @return StreamResponse + * @throws StreamException + */ + public function inviteMembers(array $filter, array $members): StreamResponse + { + $options = [ + "operation" => "inviteMembers", + "filter" => $filter, + "members" => $members, + ]; + return $this->client->updateChannelsBatch($options); + } + + /** + * Assigns roles to members in channels matching the filter. + * @param array $filter Filter to match channels (keys: cids, types, filter_tags) + * @param array $members Array of member arrays, each with user_id (required) and optional channel_role + * @return StreamResponse + * @throws StreamException + */ + public function assignRoles(array $filter, array $members): StreamResponse + { + $options = [ + "operation" => "assignRoles", + "filter" => $filter, + "members" => $members, + ]; + return $this->client->updateChannelsBatch($options); + } + + /** + * Adds moderators to channels matching the filter. + * @param array $filter Filter to match channels (keys: cids, types, filter_tags) + * @param array $members Array of member arrays, each with user_id (required) and optional channel_role + * @return StreamResponse + * @throws StreamException + */ + public function addModerators(array $filter, array $members): StreamResponse + { + $options = [ + "operation" => "addModerators", + "filter" => $filter, + "members" => $members, + ]; + return $this->client->updateChannelsBatch($options); + } + + /** + * Removes moderator role from members in channels matching the filter. + * @param array $filter Filter to match channels (keys: cids, types, filter_tags) + * @param array $members Array of member arrays, each with user_id (required) and optional channel_role + * @return StreamResponse + * @throws StreamException + */ + public function demoteModerators(array $filter, array $members): StreamResponse + { + $options = [ + "operation" => "demoteModerators", + "filter" => $filter, + "members" => $members, + ]; + return $this->client->updateChannelsBatch($options); + } + + /** + * Hides channels matching the filter for the specified members. + * @param array $filter Filter to match channels (keys: cids, types, filter_tags) + * @param array $members Array of member arrays, each with user_id (required) and optional channel_role + * @return StreamResponse + * @throws StreamException + */ + public function hide(array $filter, array $members): StreamResponse + { + $options = [ + "operation" => "hide", + "filter" => $filter, + "members" => $members, + ]; + return $this->client->updateChannelsBatch($options); + } + + /** + * Shows channels matching the filter for the specified members. + * @param array $filter Filter to match channels (keys: cids, types, filter_tags) + * @param array $members Array of member arrays, each with user_id (required) and optional channel_role + * @return StreamResponse + * @throws StreamException + */ + public function show(array $filter, array $members): StreamResponse + { + $options = [ + "operation" => "show", + "filter" => $filter, + "members" => $members, + ]; + return $this->client->updateChannelsBatch($options); + } + + /** + * Archives channels matching the filter for the specified members. + * @param array $filter Filter to match channels (keys: cids, types, filter_tags) + * @param array $members Array of member arrays, each with user_id (required) and optional channel_role + * @return StreamResponse + * @throws StreamException + */ + public function archive(array $filter, array $members): StreamResponse + { + $options = [ + "operation" => "archive", + "filter" => $filter, + "members" => $members, + ]; + return $this->client->updateChannelsBatch($options); + } + + /** + * Unarchives channels matching the filter for the specified members. + * @param array $filter Filter to match channels (keys: cids, types, filter_tags) + * @param array $members Array of member arrays, each with user_id (required) and optional channel_role + * @return StreamResponse + * @throws StreamException + */ + public function unarchive(array $filter, array $members): StreamResponse + { + $options = [ + "operation" => "unarchive", + "filter" => $filter, + "members" => $members, + ]; + return $this->client->updateChannelsBatch($options); + } + + /** + * Updates data on channels matching the filter. + * @param array $filter Filter to match channels (keys: cids, types, filter_tags) + * @param array $data Data to update (keys: frozen, disabled, custom, team, config_overrides, auto_translation_enabled, auto_translation_language) + * @return StreamResponse + * @throws StreamException + */ + public function updateData(array $filter, array $data): StreamResponse + { + $options = [ + "operation" => "updateData", + "filter" => $filter, + "data" => $data, + ]; + return $this->client->updateChannelsBatch($options); + } + + /** + * Adds filter tags to channels matching the filter. + * @param array $filter Filter to match channels (keys: cids, types, filter_tags) + * @param array $tags Array of filter tag strings + * @return StreamResponse + * @throws StreamException + */ + public function addFilterTags(array $filter, array $tags): StreamResponse + { + $options = [ + "operation" => "addFilterTags", + "filter" => $filter, + "filter_tags_update" => $tags, + ]; + return $this->client->updateChannelsBatch($options); + } + + /** + * Removes filter tags from channels matching the filter. + * @param array $filter Filter to match channels (keys: cids, types, filter_tags) + * @param array $tags Array of filter tag strings + * @return StreamResponse + * @throws StreamException + */ + public function removeFilterTags(array $filter, array $tags): StreamResponse + { + $options = [ + "operation" => "removeFilterTags", + "filter" => $filter, + "filter_tags_update" => $tags, + ]; + return $this->client->updateChannelsBatch($options); + } +} diff --git a/lib/GetStream/StreamChat/Client.php b/lib/GetStream/StreamChat/Client.php index 07ac6ef..b532a02 100644 --- a/lib/GetStream/StreamChat/Client.php +++ b/lib/GetStream/StreamChat/Client.php @@ -432,6 +432,25 @@ public function deleteChannels(array $cids, ?array $options = null): StreamRespo return $this->post("channels/delete", $options); } + /** Updates channels in batch based on the provided options. + * This is an asynchronous operation and the returned value is a task Id. + * You can use `getTask` to check the status of the task. + * @link https://getstream.io/chat/docs/php/channel_update/?language=php + * @throws StreamException + */ + public function updateChannelsBatch(array $options): StreamResponse + { + return $this->put("channels/batch", $options); + } + + /** Returns a ChannelBatchUpdater instance for batch channel operations. + * @return ChannelBatchUpdater + */ + public function channelBatchUpdater(): ChannelBatchUpdater + { + return new ChannelBatchUpdater($this); + } + /** Creates a guest user. * @link https://getstream.io/chat/docs/php/authless_users/?language=php * @throws StreamException diff --git a/tests/integration/IntegrationTest.php b/tests/integration/IntegrationTest.php index ad43dcf..360651a 100644 --- a/tests/integration/IntegrationTest.php +++ b/tests/integration/IntegrationTest.php @@ -1853,4 +1853,187 @@ public function testMarkDelivered() $this->assertInstanceOf(\GetStream\StreamChat\StreamResponse::class, $response); } + + public function testUpdateChannelsBatch() + { + $user = ["id" => $this->generateGuid()]; + $this->client->upsertUser($user); + + $c1 = $this->getChannel(); + $c2 = $this->getChannel(); + + $filter = [ + "cids" => [ + "$in" => [$c1->getCID(), $c2->getCID()] + ] + ]; + + $options = [ + "operation" => "addMembers", + "filter" => $filter, + "members" => [ + ["user_id" => $user["id"]] + ] + ]; + + $response = $this->client->updateChannelsBatch($options); + $this->assertTrue(array_key_exists("task_id", (array)$response)); + $this->assertNotEmpty($response["task_id"]); + } + + public function testChannelBatchUpdaterAddMembers() + { + $user1 = ["id" => $this->generateGuid()]; + $user2 = ["id" => $this->generateGuid()]; + $this->client->upsertUser($user1); + $this->client->upsertUser($user2); + + $c1 = $this->getChannel(); + $c2 = $this->getChannel(); + + $filter = [ + "cids" => [ + "$in" => [$c1->getCID(), $c2->getCID()] + ] + ]; + + $members = [ + ["user_id" => $user1["id"]], + ["user_id" => $user2["id"]] + ]; + + $updater = $this->client->channelBatchUpdater(); + $response = $updater->addMembers($filter, $members); + + $this->assertTrue(array_key_exists("task_id", (array)$response)); + $taskId = $response["task_id"]; + + $completed = false; + while (!$completed) { + $response = $this->client->getTask($taskId); + if ($response["status"] == "completed") { + $completed = true; + } + usleep(500000); + } + + // Wait a bit for changes to be visible + usleep(2000000); + + // Verify members were added to both channels + $c1Members = $c1->queryMembers(); + $c2Members = $c2->queryMembers(); + + $c1MemberIds = array_map(function ($member) { + return $member["user"]["id"]; + }, $c1Members["members"]); + + $c2MemberIds = array_map(function ($member) { + return $member["user"]["id"]; + }, $c2Members["members"]); + + $this->assertContains($user1["id"], $c1MemberIds); + $this->assertContains($user2["id"], $c1MemberIds); + $this->assertContains($user1["id"], $c2MemberIds); + $this->assertContains($user2["id"], $c2MemberIds); + } + + public function testChannelBatchUpdaterRemoveMembers() + { + $user1 = ["id" => $this->generateGuid()]; + $user2 = ["id" => $this->generateGuid()]; + $this->client->upsertUser($user1); + $this->client->upsertUser($user2); + + $c1 = $this->getChannel(); + $c1->addMembers([$user1["id"], $user2["id"]]); + + $filter = [ + "cids" => [ + "$in" => [$c1->getCID()] + ] + ]; + + $members = [ + ["user_id" => $user1["id"]] + ]; + + $updater = $this->client->channelBatchUpdater(); + $response = $updater->removeMembers($filter, $members); + + $this->assertTrue(array_key_exists("task_id", (array)$response)); + $taskId = $response["task_id"]; + + $completed = false; + while (!$completed) { + $response = $this->client->getTask($taskId); + if ($response["status"] == "completed") { + $completed = true; + } + usleep(500000); + } + + // Wait a bit for changes to be visible + usleep(2000000); + + // Verify member was removed + $c1Members = $c1->queryMembers(); + $c1MemberIds = array_map(function ($member) { + return $member["user"]["id"]; + }, $c1Members["members"]); + + $this->assertNotContains($user1["id"], $c1MemberIds); + $this->assertContains($user2["id"], $c1MemberIds); + } + + public function testChannelBatchUpdaterArchive() + { + $user1 = ["id" => $this->generateGuid()]; + $this->client->upsertUser($user1); + + $c1 = $this->getChannel(); + $c1->addMembers([$user1["id"]]); + + $filter = [ + "cids" => [ + "$in" => [$c1->getCID()] + ] + ]; + + $members = [ + ["user_id" => $user1["id"]] + ]; + + $updater = $this->client->channelBatchUpdater(); + $response = $updater->archive($filter, $members); + + $this->assertTrue(array_key_exists("task_id", (array)$response)); + $taskId = $response["task_id"]; + + $completed = false; + while (!$completed) { + $response = $this->client->getTask($taskId); + if ($response["status"] == "completed") { + $completed = true; + } + usleep(500000); + } + + // Wait a bit for changes to be visible + usleep(2000000); + + // Verify channel was archived + $c1Members = $c1->queryMembers(); + $archivedMember = null; + foreach ($c1Members["members"] as $member) { + if ($member["user"]["id"] == $user1["id"]) { + $archivedMember = $member; + break; + } + } + + $this->assertNotNull($archivedMember); + $this->assertTrue(array_key_exists("archived_at", $archivedMember)); + $this->assertNotNull($archivedMember["archived_at"]); + } } From b72194c31e33c4d42df6451d16e695ae884eb132 Mon Sep 17 00:00:00 2001 From: javierdfm Date: Fri, 9 Jan 2026 16:57:58 +0100 Subject: [PATCH 2/3] fix: fixed quotes --- tests/integration/IntegrationTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/integration/IntegrationTest.php b/tests/integration/IntegrationTest.php index 360651a..8c68d01 100644 --- a/tests/integration/IntegrationTest.php +++ b/tests/integration/IntegrationTest.php @@ -1864,7 +1864,7 @@ public function testUpdateChannelsBatch() $filter = [ "cids" => [ - "$in" => [$c1->getCID(), $c2->getCID()] + '$in' => [$c1->getCID(), $c2->getCID()] ] ]; @@ -1893,7 +1893,7 @@ public function testChannelBatchUpdaterAddMembers() $filter = [ "cids" => [ - "$in" => [$c1->getCID(), $c2->getCID()] + '$in' => [$c1->getCID(), $c2->getCID()] ] ]; From f70e05fd918a07797525722393635e42e370d47a Mon Sep 17 00:00:00 2001 From: javierdfm Date: Mon, 12 Jan 2026 16:42:55 +0100 Subject: [PATCH 3/3] fix: updated quotes --- tests/integration/IntegrationTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/integration/IntegrationTest.php b/tests/integration/IntegrationTest.php index 8c68d01..9114841 100644 --- a/tests/integration/IntegrationTest.php +++ b/tests/integration/IntegrationTest.php @@ -1950,7 +1950,7 @@ public function testChannelBatchUpdaterRemoveMembers() $filter = [ "cids" => [ - "$in" => [$c1->getCID()] + '$in' => [$c1->getCID()] ] ]; @@ -1996,7 +1996,7 @@ public function testChannelBatchUpdaterArchive() $filter = [ "cids" => [ - "$in" => [$c1->getCID()] + '$in' => [$c1->getCID()] ] ];