From 9d55a21a6617896eeb8d0d33d780f12dd92959c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Thu, 13 Aug 2026 17:28:24 +0200 Subject: [PATCH 1/4] Expose the options that passed the filters MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit run() answers "which one option should I use", and the filtered set it picks from was unreachable: $filters is private, so a caller that has to act on every qualifying option — fanning a request out rather than balancing between them — had to re-implement filtering outside the class, or subclass to record filters as they were registered. getFilteredOptions() is that set, extracted from run() unchanged. run() now calls it and behaves exactly as before. Co-Authored-By: Claude Opus 5 (1M context) --- src/Balancer/Balancer.php | 18 +++++++++++++++-- tests/Balancer/BalancerTest.php | 36 +++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 2 deletions(-) diff --git a/src/Balancer/Balancer.php b/src/Balancer/Balancer.php index c481393..a8aeec2 100644 --- a/src/Balancer/Balancer.php +++ b/src/Balancer/Balancer.php @@ -46,7 +46,16 @@ public function addFilter(callable $filter): self return $this; } - public function run(): ?Option + /** + * Every option that passed all filters, in the order they were added. + * + * `run()` narrows this to one option through the algorithm. Callers that + * have to act on all of them — fanning a request out to every option that + * qualifies, rather than balancing between them — read them here. + * + * @return Option[] + */ + public function getFilteredOptions(): array { $options = $this->options; @@ -54,7 +63,12 @@ public function run(): ?Option $options = \array_filter($options, $filter); } - $options = \array_values($options); + return \array_values($options); + } + + public function run(): ?Option + { + $options = $this->getFilteredOptions(); if (\count($options) === 0) { return null; diff --git a/tests/Balancer/BalancerTest.php b/tests/Balancer/BalancerTest.php index c18df7d..3722a77 100644 --- a/tests/Balancer/BalancerTest.php +++ b/tests/Balancer/BalancerTest.php @@ -244,4 +244,40 @@ public function testGetOptions(): void $this->assertEquals('fra-2', $groupOptions[4]->getState('dataCenter')); $this->assertEquals('lon-1', $groupOptions[5]->getState('dataCenter')); } + + public function testFilteredOptions(): void + { + $balancer = new Balancer(new First()); + + $balancer + ->addOption(new Option(['hostname' => 'worker-1', 'isOnline' => true, 'cpu' => 80])) + ->addOption(new Option(['hostname' => 'worker-2', 'isOnline' => false, 'cpu' => 20])) + ->addOption(new Option(['hostname' => 'worker-3', 'isOnline' => true, 'cpu' => 35])); + + // Unfiltered, every option qualifies + $this->assertCount(3, $balancer->getFilteredOptions()); + + $balancer->addFilter(fn ($option) => $option->getState('isOnline') === true); + + $filtered = $balancer->getFilteredOptions(); + + // All survivors, not just the one the algorithm would pick + $this->assertCount(2, $filtered); + $this->assertEquals('worker-1', $filtered[0]->getState('hostname')); + $this->assertEquals('worker-3', $filtered[1]->getState('hostname')); + $this->assertEquals('worker-1', ($balancer->run() ?? new Option([]))->getState('hostname')); + + $balancer->addFilter(fn ($option) => $option->getState('cpu') < 50); + + $filtered = $balancer->getFilteredOptions(); + + // Filters compose, and the keys are reindexed from zero + $this->assertCount(1, $filtered); + $this->assertEquals('worker-3', $filtered[0]->getState('hostname')); + + $balancer->addFilter(fn ($option) => false); + + $this->assertSame([], $balancer->getFilteredOptions()); + $this->assertNull($balancer->run()); + } } From 88a2e7e20037e3aedb6a3434efe9563a7d0d70b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Thu, 13 Aug 2026 17:32:05 +0200 Subject: [PATCH 2/4] Allow telemetry 0.4 alongside 0.1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Group's telemetry pin at 0.1.* makes this library uninstallable next to anything on the current utopia telemetry line: utopia-php/pools requires ^0.4.6, so any consumer pulling in pools — through utopia-php/client, for one — cannot also require balancer. The whole surface Group touches is unchanged between the two lines. The Adapter::createHistogram and Histogram::record signatures are identical, and Adapter\None exists in both, so the constraint is widened rather than the code changed. Verified against 0.4.6: tests, PHPStan --level max and PSR-12 all pass. Co-Authored-By: Claude Opus 5 (1M context) --- composer.json | 2 +- composer.lock | 41 +++++++++++++++++++++-------------------- 2 files changed, 22 insertions(+), 21 deletions(-) diff --git a/composer.json b/composer.json index 5867305..cb463bd 100644 --- a/composer.json +++ b/composer.json @@ -15,7 +15,7 @@ }, "require": { "php": ">=8.1", - "utopia-php/telemetry": "0.1.*" + "utopia-php/telemetry": "0.1.*|0.4.*" }, "require-dev": { "phpunit/phpunit": "^9.3", diff --git a/composer.lock b/composer.lock index cb40d43..c0c8bdb 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "c5518a6e1914ae22a3b56aa56ebed804", + "content-hash": "56c6440b6e5a6029d2bcfe95dae82b08", "packages": [ { "name": "brick/math", @@ -1810,43 +1810,44 @@ }, { "name": "utopia-php/telemetry", - "version": "0.1.0", + "version": "0.4.6", "source": { "type": "git", "url": "https://github.com/utopia-php/telemetry.git", - "reference": "d35f2f0632f4ee0be63fb7ace6a94a6adda71a80" + "reference": "f96778a01792c32df0876fe1f38a79b9588445d8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/telemetry/zipball/d35f2f0632f4ee0be63fb7ace6a94a6adda71a80", - "reference": "d35f2f0632f4ee0be63fb7ace6a94a6adda71a80", + "url": "https://api.github.com/repos/utopia-php/telemetry/zipball/f96778a01792c32df0876fe1f38a79b9588445d8", + "reference": "f96778a01792c32df0876fe1f38a79b9588445d8", "shasum": "" }, "require": { - "ext-opentelemetry": "*", "ext-protobuf": "*", - "nyholm/psr7": "^1.8", - "open-telemetry/exporter-otlp": "^1.1", - "open-telemetry/sdk": "^1.1", + "nyholm/psr7": "1.*", + "open-telemetry/exporter-otlp": "1.*", + "open-telemetry/sdk": "1.*", "php": ">=8.0", - "symfony/http-client": "^7.1" + "symfony/http-client": "7.*" }, "require-dev": { - "laravel/pint": "^1.2", - "phpbench/phpbench": "^1.2", - "phpstan/phpstan": "^1.10", - "phpunit/phpunit": "^9.5.25" + "swoole/ide-helper": "6.*" + }, + "suggest": { + "ext-sockets": "Required for the Swoole transport implementation", + "ext-swoole": "Required for the Swoole transport implementation" }, "type": "library", "autoload": { "psr-4": { - "Utopia\\": "src/" + "Utopia\\Telemetry\\": "src/Telemetry" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], + "description": "A lite & fast telemetry library, with adapters for OpenTelemetry", "keywords": [ "framework", "php", @@ -1854,9 +1855,9 @@ ], "support": { "issues": "https://github.com/utopia-php/telemetry/issues", - "source": "https://github.com/utopia-php/telemetry/tree/0.1.0" + "source": "https://github.com/utopia-php/telemetry/tree/0.4.6" }, - "time": "2024-11-13T10:29:53+00:00" + "time": "2026-08-05T17:56:48+00:00" } ], "packages-dev": [ @@ -3732,12 +3733,12 @@ ], "aliases": [], "minimum-stability": "dev", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, "platform": { "php": ">=8.1" }, - "platform-dev": [], - "plugin-api-version": "2.6.0" + "platform-dev": {}, + "plugin-api-version": "2.9.0" } From dc23e88f8f41ee18f1c7c4b12d4a3bab50ae6f3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Thu, 13 Aug 2026 21:26:23 +0200 Subject: [PATCH 3/4] Spell the telemetry constraint with carets Review preferred caret syntax. ^0.1.0 on its own cannot work: below 1.0.0 Composer's caret is patch-only, so it resolves 0.1.0 or 0.1.1 and still conflicts with utopia-php/pools at ^0.4.6. Both series have to be named. Same resolution as 0.1.*|0.4.*, in the requested style. Co-Authored-By: Claude Opus 5 (1M context) --- composer.json | 2 +- composer.lock | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index cb463bd..e44d6cc 100644 --- a/composer.json +++ b/composer.json @@ -15,7 +15,7 @@ }, "require": { "php": ">=8.1", - "utopia-php/telemetry": "0.1.*|0.4.*" + "utopia-php/telemetry": "^0.1.0 || ^0.4.0" }, "require-dev": { "phpunit/phpunit": "^9.3", diff --git a/composer.lock b/composer.lock index c0c8bdb..4f08631 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "56c6440b6e5a6029d2bcfe95dae82b08", + "content-hash": "a5b43ff8bcd9833dfcf576053edd9376", "packages": [ { "name": "brick/math", From 75dfb5c2b558db1f00a20e9ebb8f59a5b92c8564 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Thu, 13 Aug 2026 21:32:39 +0200 Subject: [PATCH 4/4] Require telemetry 0.4 Drops the 0.1 series rather than supporting both. 0.1 was already unreachable for any consumer that also pulls in utopia-php/pools, which requires ^0.4.6, so keeping it alive supported a combination nothing could install. Requirements narrow rather than widen: telemetry 0.1.1 needed ext-protobuf and ext-opentelemetry, 0.4.6 needs only ext-protobuf. Breaking for a consumer pinning telemetry to 0.1. Co-Authored-By: Claude Opus 5 (1M context) --- composer.json | 2 +- composer.lock | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index e44d6cc..f498aee 100644 --- a/composer.json +++ b/composer.json @@ -15,7 +15,7 @@ }, "require": { "php": ">=8.1", - "utopia-php/telemetry": "^0.1.0 || ^0.4.0" + "utopia-php/telemetry": "^0.4.0" }, "require-dev": { "phpunit/phpunit": "^9.3", diff --git a/composer.lock b/composer.lock index 4f08631..a08b170 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "a5b43ff8bcd9833dfcf576053edd9376", + "content-hash": "0645b381d7c313486286bcfa6efaf025", "packages": [ { "name": "brick/math",